| //===--- 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/None.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/StringRef.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace 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 llvm::Optional<FixItHint> |
| fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range, |
| StringRef Ownership) { |
| size_t Index = Text.find(Ownership); |
| if (Index == StringRef::npos) |
| return llvm::None; |
| |
| SourceLocation Begin = Range.getBegin().getLocWithOffset(Index); |
| SourceLocation End = Begin.getLocWithOffset(Ownership.size()); |
| return FixItHint::CreateReplacement(SourceRange(Begin, End), |
| UnsafeUnretainedText); |
| } |
| |
| static llvm::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 llvm::None; |
| |
| // 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 llvm::None; |
| } |
| |
| StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts); |
| if (llvm::Optional<FixItHint> Hint = |
| fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText)) |
| return Hint; |
| |
| if (llvm::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 objc |
| } // namespace tidy |
| } // namespace clang |