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