| //===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "NSInvocationArgumentLifetimeCheck.h" | 
 | #include "clang/AST/ASTContext.h" | 
 | #include "clang/AST/ComputeDependence.h" | 
 | #include "clang/AST/Decl.h" | 
 | #include "clang/AST/Expr.h" | 
 | #include "clang/AST/ExprObjC.h" | 
 | #include "clang/AST/Type.h" | 
 | #include "clang/AST/TypeLoc.h" | 
 | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
 | #include "clang/ASTMatchers/ASTMatchers.h" | 
 | #include "clang/ASTMatchers/ASTMatchersMacros.h" | 
 | #include "clang/Basic/Diagnostic.h" | 
 | #include "clang/Basic/LLVM.h" | 
 | #include "clang/Basic/LangOptions.h" | 
 | #include "clang/Basic/SourceLocation.h" | 
 | #include "clang/Basic/SourceManager.h" | 
 | #include "clang/Lex/Lexer.h" | 
 | #include "llvm/ADT/StringRef.h" | 
 | #include <optional> | 
 |  | 
 | using namespace clang::ast_matchers; | 
 |  | 
 | namespace clang::tidy::objc { | 
 | namespace { | 
 |  | 
 | static constexpr StringRef WeakText = "__weak"; | 
 | static constexpr StringRef StrongText = "__strong"; | 
 | static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained"; | 
 |  | 
 | /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference | 
 | /// Objective-C object (or block) variables or fields whose object lifetimes | 
 | /// are not __unsafe_unretained. | 
 | AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime, | 
 |                         AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr, | 
 |                                                         DeclRefExpr, | 
 |                                                         MemberExpr)) { | 
 |   QualType QT = Node.getType(); | 
 |   return QT->isScalarType() && | 
 |          (QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer || | 
 |           QT->getScalarTypeKind() == Type::STK_BlockPointer) && | 
 |          QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone; | 
 | } | 
 |  | 
 | static std::optional<FixItHint> | 
 | fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range, | 
 |                                        StringRef Ownership) { | 
 |   size_t Index = Text.find(Ownership); | 
 |   if (Index == StringRef::npos) | 
 |     return std::nullopt; | 
 |  | 
 |   SourceLocation Begin = Range.getBegin().getLocWithOffset(Index); | 
 |   SourceLocation End = Begin.getLocWithOffset(Ownership.size()); | 
 |   return FixItHint::CreateReplacement(SourceRange(Begin, End), | 
 |                                       UnsafeUnretainedText); | 
 | } | 
 |  | 
 | static std::optional<FixItHint> | 
 | fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM, | 
 |                     const LangOptions &LangOpts) { | 
 |   assert(VD && "VarDecl parameter must not be null"); | 
 |   // Don't provide fix-its for any parameter variables at this time. | 
 |   if (isa<ParmVarDecl>(VD)) | 
 |     return std::nullopt; | 
 |  | 
 |   // Currently there is no way to directly get the source range for the | 
 |   // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string | 
 |   // search in the source code. | 
 |   CharSourceRange Range = Lexer::makeFileCharRange( | 
 |       CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts); | 
 |   if (Range.isInvalid()) { | 
 |     // An invalid range likely means inside a macro, in which case don't supply | 
 |     // a fix-it. | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts); | 
 |   if (std::optional<FixItHint> Hint = | 
 |           fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText)) | 
 |     return Hint; | 
 |  | 
 |   if (std::optional<FixItHint> Hint = fixItHintReplacementForOwnershipString( | 
 |           VarDeclText, Range, StrongText)) | 
 |     return Hint; | 
 |  | 
 |   return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained "); | 
 | } | 
 |  | 
 | } // namespace | 
 |  | 
 | void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) { | 
 |   Finder->addMatcher( | 
 |       traverse( | 
 |           TK_AsIs, | 
 |           objcMessageExpr( | 
 |               hasReceiverType(asString("NSInvocation *")), | 
 |               anyOf(hasSelector("getArgument:atIndex:"), | 
 |                     hasSelector("getReturnValue:")), | 
 |               hasArgument( | 
 |                   0, | 
 |                   anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())), | 
 |                         hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())), | 
 |                         hasDescendant( | 
 |                             // Reference to variables, but when dereferencing | 
 |                             // to ivars/fields a more-descendent variable | 
 |                             // reference (e.g. self) may match with strong | 
 |                             // object lifetime, leading to an incorrect match. | 
 |                             // Exclude these conditions. | 
 |                             declRefExpr(to(varDecl().bind("var")), | 
 |                                         unless(hasParent(implicitCastExpr())), | 
 |                                         isObjCManagedLifetime()))))) | 
 |               .bind("call")), | 
 |       this); | 
 | } | 
 |  | 
 | void NSInvocationArgumentLifetimeCheck::check( | 
 |     const MatchFinder::MatchResult &Result) { | 
 |   const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call"); | 
 |  | 
 |   auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(), | 
 |                    "NSInvocation %objcinstance0 should only pass pointers to " | 
 |                    "objects with ownership __unsafe_unretained") | 
 |               << MatchedExpr->getSelector(); | 
 |  | 
 |   // Only provide fix-it hints for references to local variables; fixes for | 
 |   // instance variable references don't have as clear an automated fix. | 
 |   const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var"); | 
 |   if (!VD) | 
 |     return; | 
 |  | 
 |   if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager, | 
 |                                       Result.Context->getLangOpts())) | 
 |     Diag << *Hint; | 
 | } | 
 |  | 
 | } // namespace clang::tidy::objc |