1 //===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8
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/None.h"
27 #include "llvm/ADT/Optional.h"
28 #include "llvm/ADT/StringRef.h"
29
30 using namespace clang::ast_matchers;
31
32 namespace clang {
33 namespace tidy {
34 namespace objc {
35 namespace {
36
37 static constexpr StringRef WeakText = "__weak";
38 static constexpr StringRef StrongText = "__strong";
39 static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained";
40
41 /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
42 /// Objective-C object (or block) variables or fields whose object lifetimes
43 /// are not __unsafe_unretained.
AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,AST_POLYMORPHIC_SUPPORTED_TYPES (ObjCIvarRefExpr,DeclRefExpr,MemberExpr))44 AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,
45 AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr,
46 DeclRefExpr,
47 MemberExpr)) {
48 QualType QT = Node.getType();
49 return QT->isScalarType() &&
50 (QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer ||
51 QT->getScalarTypeKind() == Type::STK_BlockPointer) &&
52 QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone;
53 }
54
55 static llvm::Optional<FixItHint>
fixItHintReplacementForOwnershipString(StringRef Text,CharSourceRange Range,StringRef Ownership)56 fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,
57 StringRef Ownership) {
58 size_t Index = Text.find(Ownership);
59 if (Index == StringRef::npos)
60 return llvm::None;
61
62 SourceLocation Begin = Range.getBegin().getLocWithOffset(Index);
63 SourceLocation End = Begin.getLocWithOffset(Ownership.size());
64 return FixItHint::CreateReplacement(SourceRange(Begin, End),
65 UnsafeUnretainedText);
66 }
67
68 static llvm::Optional<FixItHint>
fixItHintForVarDecl(const VarDecl * VD,const SourceManager & SM,const LangOptions & LangOpts)69 fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM,
70 const LangOptions &LangOpts) {
71 assert(VD && "VarDecl parameter must not be null");
72 // Don't provide fix-its for any parameter variables at this time.
73 if (isa<ParmVarDecl>(VD))
74 return llvm::None;
75
76 // Currently there is no way to directly get the source range for the
77 // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string
78 // search in the source code.
79 CharSourceRange Range = Lexer::makeFileCharRange(
80 CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts);
81 if (Range.isInvalid()) {
82 // An invalid range likely means inside a macro, in which case don't supply
83 // a fix-it.
84 return llvm::None;
85 }
86
87 StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts);
88 if (llvm::Optional<FixItHint> Hint =
89 fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText))
90 return Hint;
91
92 if (llvm::Optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(
93 VarDeclText, Range, StrongText))
94 return Hint;
95
96 return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained ");
97 }
98
99 } // namespace
100
registerMatchers(MatchFinder * Finder)101 void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {
102 Finder->addMatcher(
103 traverse(
104 TK_AsIs,
105 objcMessageExpr(
106 hasReceiverType(asString("NSInvocation *")),
107 anyOf(hasSelector("getArgument:atIndex:"),
108 hasSelector("getReturnValue:")),
109 hasArgument(
110 0,
111 anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
112 hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
113 hasDescendant(
114 // Reference to variables, but when dereferencing
115 // to ivars/fields a more-descendent variable
116 // reference (e.g. self) may match with strong
117 // object lifetime, leading to an incorrect match.
118 // Exclude these conditions.
119 declRefExpr(to(varDecl().bind("var")),
120 unless(hasParent(implicitCastExpr())),
121 isObjCManagedLifetime())))))
122 .bind("call")),
123 this);
124 }
125
check(const MatchFinder::MatchResult & Result)126 void NSInvocationArgumentLifetimeCheck::check(
127 const MatchFinder::MatchResult &Result) {
128 const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");
129
130 auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),
131 "NSInvocation %objcinstance0 should only pass pointers to "
132 "objects with ownership __unsafe_unretained")
133 << MatchedExpr->getSelector();
134
135 // Only provide fix-it hints for references to local variables; fixes for
136 // instance variable references don't have as clear an automated fix.
137 const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
138 if (!VD)
139 return;
140
141 if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,
142 Result.Context->getLangOpts()))
143 Diag << *Hint;
144 }
145
146 } // namespace objc
147 } // namespace tidy
148 } // namespace clang
149