123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- //===--- 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
|