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. 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> 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> 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 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 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