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       objcMessageExpr(
105           hasReceiverType(asString("NSInvocation *")),
106           anyOf(hasSelector("getArgument:atIndex:"),
107                 hasSelector("getReturnValue:")),
108           hasArgument(
109               0, anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
110                        hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
111                        hasDescendant(
112                            // Reference to variables, but when dereferencing
113                            // to ivars/fields a more-descendent variable
114                            // reference (e.g. self) may match with strong
115                            // object lifetime, leading to an incorrect match.
116                            // Exclude these conditions.
117                            declRefExpr(to(varDecl().bind("var")),
118                                        unless(hasParent(implicitCastExpr())),
119                                        isObjCManagedLifetime())))))
120           .bind("call"),
121       this);
122 }
123 
124 void NSInvocationArgumentLifetimeCheck::check(
125     const MatchFinder::MatchResult &Result) {
126   const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");
127 
128   auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),
129                    "NSInvocation %objcinstance0 should only pass pointers to "
130                    "objects with ownership __unsafe_unretained")
131               << MatchedExpr->getSelector();
132 
133   // Only provide fix-it hints for references to local variables; fixes for
134   // instance variable references don't have as clear an automated fix.
135   const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
136   if (!VD)
137     return;
138 
139   if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,
140                                       Result.Context->getLangOpts()))
141     Diag << *Hint;
142 }
143 
144 } // namespace objc
145 } // namespace tidy
146 } // namespace clang
147