1 //===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "NSInvocationArgumentLifetimeCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/ComputeDependence.h"
12 #include "clang/AST/Decl.h"
13 #include "clang/AST/Expr.h"
14 #include "clang/AST/ExprObjC.h"
15 #include "clang/AST/Type.h"
16 #include "clang/AST/TypeLoc.h"
17 #include "clang/ASTMatchers/ASTMatchFinder.h"
18 #include "clang/ASTMatchers/ASTMatchers.h"
19 #include "clang/ASTMatchers/ASTMatchersMacros.h"
20 #include "clang/Basic/Diagnostic.h"
21 #include "clang/Basic/LLVM.h"
22 #include "clang/Basic/LangOptions.h"
23 #include "clang/Basic/SourceLocation.h"
24 #include "clang/Basic/SourceManager.h"
25 #include "clang/Lex/Lexer.h"
26 #include "llvm/ADT/StringRef.h"
29 using namespace clang::ast_matchers
;
31 namespace clang::tidy::objc
{
34 static constexpr StringRef WeakText
= "__weak";
35 static constexpr StringRef StrongText
= "__strong";
36 static constexpr StringRef UnsafeUnretainedText
= "__unsafe_unretained";
38 /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
39 /// Objective-C object (or block) variables or fields whose object lifetimes
40 /// are not __unsafe_unretained.
41 AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime
,
42 AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr
,
45 QualType QT
= Node
.getType();
46 return QT
->isScalarType() &&
47 (QT
->getScalarTypeKind() == Type::STK_ObjCObjectPointer
||
48 QT
->getScalarTypeKind() == Type::STK_BlockPointer
) &&
49 QT
.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone
;
52 static std::optional
<FixItHint
>
53 fixItHintReplacementForOwnershipString(StringRef Text
, CharSourceRange Range
,
54 StringRef Ownership
) {
55 size_t Index
= Text
.find(Ownership
);
56 if (Index
== StringRef::npos
)
59 SourceLocation Begin
= Range
.getBegin().getLocWithOffset(Index
);
60 SourceLocation End
= Begin
.getLocWithOffset(Ownership
.size());
61 return FixItHint::CreateReplacement(SourceRange(Begin
, End
),
62 UnsafeUnretainedText
);
65 static std::optional
<FixItHint
>
66 fixItHintForVarDecl(const VarDecl
*VD
, const SourceManager
&SM
,
67 const LangOptions
&LangOpts
) {
68 assert(VD
&& "VarDecl parameter must not be null");
69 // Don't provide fix-its for any parameter variables at this time.
70 if (isa
<ParmVarDecl
>(VD
))
73 // Currently there is no way to directly get the source range for the
74 // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string
75 // search in the source code.
76 CharSourceRange Range
= Lexer::makeFileCharRange(
77 CharSourceRange::getTokenRange(VD
->getSourceRange()), SM
, LangOpts
);
78 if (Range
.isInvalid()) {
79 // An invalid range likely means inside a macro, in which case don't supply
84 StringRef VarDeclText
= Lexer::getSourceText(Range
, SM
, LangOpts
);
85 if (std::optional
<FixItHint
> Hint
=
86 fixItHintReplacementForOwnershipString(VarDeclText
, Range
, WeakText
))
89 if (std::optional
<FixItHint
> Hint
= fixItHintReplacementForOwnershipString(
90 VarDeclText
, Range
, StrongText
))
93 return FixItHint::CreateInsertion(Range
.getBegin(), "__unsafe_unretained ");
98 void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder
*Finder
) {
103 hasReceiverType(asString("NSInvocation *")),
104 anyOf(hasSelector("getArgument:atIndex:"),
105 hasSelector("getReturnValue:")),
108 anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
109 hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
111 // Reference to variables, but when dereferencing
112 // to ivars/fields a more-descendent variable
113 // reference (e.g. self) may match with strong
114 // object lifetime, leading to an incorrect match.
115 // Exclude these conditions.
116 declRefExpr(to(varDecl().bind("var")),
117 unless(hasParent(implicitCastExpr())),
118 isObjCManagedLifetime())))))
123 void NSInvocationArgumentLifetimeCheck::check(
124 const MatchFinder::MatchResult
&Result
) {
125 const auto *MatchedExpr
= Result
.Nodes
.getNodeAs
<ObjCMessageExpr
>("call");
127 auto Diag
= diag(MatchedExpr
->getArg(0)->getBeginLoc(),
128 "NSInvocation %objcinstance0 should only pass pointers to "
129 "objects with ownership __unsafe_unretained")
130 << MatchedExpr
->getSelector();
132 // Only provide fix-it hints for references to local variables; fixes for
133 // instance variable references don't have as clear an automated fix.
134 const auto *VD
= Result
.Nodes
.getNodeAs
<VarDecl
>("var");
138 if (auto Hint
= fixItHintForVarDecl(VD
, *Result
.SourceManager
,
139 Result
.Context
->getLangOpts()))
143 } // namespace clang::tidy::objc