xref: /llvm-project/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
189f1321fSMichael Wyman //===--- NSInvocationArgumentLifetimeCheck.cpp - clang-tidy ---------===//
289f1321fSMichael Wyman //
389f1321fSMichael Wyman // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
489f1321fSMichael Wyman // See https://llvm.org/LICENSE.txt for license information.
589f1321fSMichael Wyman // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
689f1321fSMichael Wyman //
789f1321fSMichael Wyman //===----------------------------------------------------------------------===//
889f1321fSMichael Wyman 
989f1321fSMichael Wyman #include "NSInvocationArgumentLifetimeCheck.h"
1089f1321fSMichael Wyman #include "clang/AST/ASTContext.h"
1189f1321fSMichael Wyman #include "clang/AST/ComputeDependence.h"
1289f1321fSMichael Wyman #include "clang/AST/Decl.h"
1389f1321fSMichael Wyman #include "clang/AST/Expr.h"
1489f1321fSMichael Wyman #include "clang/AST/ExprObjC.h"
1589f1321fSMichael Wyman #include "clang/AST/Type.h"
1689f1321fSMichael Wyman #include "clang/AST/TypeLoc.h"
1789f1321fSMichael Wyman #include "clang/ASTMatchers/ASTMatchFinder.h"
1889f1321fSMichael Wyman #include "clang/ASTMatchers/ASTMatchers.h"
1989f1321fSMichael Wyman #include "clang/ASTMatchers/ASTMatchersMacros.h"
2089f1321fSMichael Wyman #include "clang/Basic/Diagnostic.h"
2189f1321fSMichael Wyman #include "clang/Basic/LLVM.h"
2289f1321fSMichael Wyman #include "clang/Basic/LangOptions.h"
2389f1321fSMichael Wyman #include "clang/Basic/SourceLocation.h"
2489f1321fSMichael Wyman #include "clang/Basic/SourceManager.h"
25860aefd0SNathan James #include "clang/Lex/Lexer.h"
2689f1321fSMichael Wyman #include "llvm/ADT/StringRef.h"
2771f55735SKazu Hirata #include <optional>
2889f1321fSMichael Wyman 
2989f1321fSMichael Wyman using namespace clang::ast_matchers;
3089f1321fSMichael Wyman 
31*7d2ea6c4SCarlos Galvez namespace clang::tidy::objc {
3289f1321fSMichael Wyman namespace {
3389f1321fSMichael Wyman 
3489f1321fSMichael Wyman static constexpr StringRef WeakText = "__weak";
3589f1321fSMichael Wyman static constexpr StringRef StrongText = "__strong";
3689f1321fSMichael Wyman static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained";
3789f1321fSMichael Wyman 
3889f1321fSMichael Wyman /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
3989f1321fSMichael Wyman /// Objective-C object (or block) variables or fields whose object lifetimes
4089f1321fSMichael Wyman /// are not __unsafe_unretained.
AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,AST_POLYMORPHIC_SUPPORTED_TYPES (ObjCIvarRefExpr,DeclRefExpr,MemberExpr))4189f1321fSMichael Wyman AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,
4289f1321fSMichael Wyman                         AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr,
4389f1321fSMichael Wyman                                                         DeclRefExpr,
4489f1321fSMichael Wyman                                                         MemberExpr)) {
4589f1321fSMichael Wyman   QualType QT = Node.getType();
4689f1321fSMichael Wyman   return QT->isScalarType() &&
4789f1321fSMichael Wyman          (QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer ||
4889f1321fSMichael Wyman           QT->getScalarTypeKind() == Type::STK_BlockPointer) &&
4989f1321fSMichael Wyman          QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone;
5089f1321fSMichael Wyman }
5189f1321fSMichael Wyman 
52f71ffd3bSKazu Hirata static std::optional<FixItHint>
fixItHintReplacementForOwnershipString(StringRef Text,CharSourceRange Range,StringRef Ownership)5389f1321fSMichael Wyman fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,
5489f1321fSMichael Wyman                                        StringRef Ownership) {
5589f1321fSMichael Wyman   size_t Index = Text.find(Ownership);
5689f1321fSMichael Wyman   if (Index == StringRef::npos)
57cd8702efSKazu Hirata     return std::nullopt;
5889f1321fSMichael Wyman 
5989f1321fSMichael Wyman   SourceLocation Begin = Range.getBegin().getLocWithOffset(Index);
6089f1321fSMichael Wyman   SourceLocation End = Begin.getLocWithOffset(Ownership.size());
6189f1321fSMichael Wyman   return FixItHint::CreateReplacement(SourceRange(Begin, End),
6289f1321fSMichael Wyman                                       UnsafeUnretainedText);
6389f1321fSMichael Wyman }
6489f1321fSMichael Wyman 
65f71ffd3bSKazu Hirata static std::optional<FixItHint>
fixItHintForVarDecl(const VarDecl * VD,const SourceManager & SM,const LangOptions & LangOpts)6689f1321fSMichael Wyman fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM,
6789f1321fSMichael Wyman                     const LangOptions &LangOpts) {
6889f1321fSMichael Wyman   assert(VD && "VarDecl parameter must not be null");
6989f1321fSMichael Wyman   // Don't provide fix-its for any parameter variables at this time.
7089f1321fSMichael Wyman   if (isa<ParmVarDecl>(VD))
71cd8702efSKazu Hirata     return std::nullopt;
7289f1321fSMichael Wyman 
7389f1321fSMichael Wyman   // Currently there is no way to directly get the source range for the
7489f1321fSMichael Wyman   // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string
7589f1321fSMichael Wyman   // search in the source code.
7689f1321fSMichael Wyman   CharSourceRange Range = Lexer::makeFileCharRange(
7789f1321fSMichael Wyman       CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts);
7889f1321fSMichael Wyman   if (Range.isInvalid()) {
7989f1321fSMichael Wyman     // An invalid range likely means inside a macro, in which case don't supply
8089f1321fSMichael Wyman     // a fix-it.
81cd8702efSKazu Hirata     return std::nullopt;
8289f1321fSMichael Wyman   }
8389f1321fSMichael Wyman 
8489f1321fSMichael Wyman   StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts);
85f71ffd3bSKazu Hirata   if (std::optional<FixItHint> Hint =
8689f1321fSMichael Wyman           fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText))
8789f1321fSMichael Wyman     return Hint;
8889f1321fSMichael Wyman 
89f71ffd3bSKazu Hirata   if (std::optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(
9089f1321fSMichael Wyman           VarDeclText, Range, StrongText))
9189f1321fSMichael Wyman     return Hint;
9289f1321fSMichael Wyman 
9389f1321fSMichael Wyman   return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained ");
9489f1321fSMichael Wyman }
9589f1321fSMichael Wyman 
9689f1321fSMichael Wyman } // namespace
9789f1321fSMichael Wyman 
registerMatchers(MatchFinder * Finder)9889f1321fSMichael Wyman void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {
9989f1321fSMichael Wyman   Finder->addMatcher(
100a72307c3SStephen Kelly       traverse(
101027899daSAlexander Kornienko           TK_AsIs,
10289f1321fSMichael Wyman           objcMessageExpr(
10389f1321fSMichael Wyman               hasReceiverType(asString("NSInvocation *")),
10489f1321fSMichael Wyman               anyOf(hasSelector("getArgument:atIndex:"),
10589f1321fSMichael Wyman                     hasSelector("getReturnValue:")),
10689f1321fSMichael Wyman               hasArgument(
107a72307c3SStephen Kelly                   0,
108a72307c3SStephen Kelly                   anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
10989f1321fSMichael Wyman                         hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
11089f1321fSMichael Wyman                         hasDescendant(
11189f1321fSMichael Wyman                             // Reference to variables, but when dereferencing
11289f1321fSMichael Wyman                             // to ivars/fields a more-descendent variable
11389f1321fSMichael Wyman                             // reference (e.g. self) may match with strong
11489f1321fSMichael Wyman                             // object lifetime, leading to an incorrect match.
11589f1321fSMichael Wyman                             // Exclude these conditions.
11689f1321fSMichael Wyman                             declRefExpr(to(varDecl().bind("var")),
11789f1321fSMichael Wyman                                         unless(hasParent(implicitCastExpr())),
11889f1321fSMichael Wyman                                         isObjCManagedLifetime())))))
119a72307c3SStephen Kelly               .bind("call")),
12089f1321fSMichael Wyman       this);
12189f1321fSMichael Wyman }
12289f1321fSMichael Wyman 
check(const MatchFinder::MatchResult & Result)12389f1321fSMichael Wyman void NSInvocationArgumentLifetimeCheck::check(
12489f1321fSMichael Wyman     const MatchFinder::MatchResult &Result) {
12589f1321fSMichael Wyman   const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");
12689f1321fSMichael Wyman 
12789f1321fSMichael Wyman   auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),
12889f1321fSMichael Wyman                    "NSInvocation %objcinstance0 should only pass pointers to "
12989f1321fSMichael Wyman                    "objects with ownership __unsafe_unretained")
13089f1321fSMichael Wyman               << MatchedExpr->getSelector();
13189f1321fSMichael Wyman 
13289f1321fSMichael Wyman   // Only provide fix-it hints for references to local variables; fixes for
13389f1321fSMichael Wyman   // instance variable references don't have as clear an automated fix.
13489f1321fSMichael Wyman   const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
13589f1321fSMichael Wyman   if (!VD)
13689f1321fSMichael Wyman     return;
13789f1321fSMichael Wyman 
13889f1321fSMichael Wyman   if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,
13989f1321fSMichael Wyman                                       Result.Context->getLangOpts()))
14089f1321fSMichael Wyman     Diag << *Hint;
14189f1321fSMichael Wyman }
14289f1321fSMichael Wyman 
143*7d2ea6c4SCarlos Galvez } // namespace clang::tidy::objc
144