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