xref: /llvm-project/clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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/ComputeDependence.h"
12 #include "clang/AST/Decl.h"
13 #include "clang/AST/Expr.h"
14 #include "clang/AST/ExprObjC.h"
15 #include "clang/AST/Type.h"
16 #include "clang/AST/TypeLoc.h"
17 #include "clang/ASTMatchers/ASTMatchFinder.h"
18 #include "clang/ASTMatchers/ASTMatchers.h"
19 #include "clang/ASTMatchers/ASTMatchersMacros.h"
20 #include "clang/Basic/Diagnostic.h"
21 #include "clang/Basic/LLVM.h"
22 #include "clang/Basic/LangOptions.h"
23 #include "clang/Basic/SourceLocation.h"
24 #include "clang/Basic/SourceManager.h"
25 #include "clang/Lex/Lexer.h"
26 #include "llvm/ADT/StringRef.h"
27 #include <optional>
28 
29 using namespace clang::ast_matchers;
30 
31 namespace clang::tidy::objc {
32 namespace {
33 
34 static constexpr StringRef WeakText = "__weak";
35 static constexpr StringRef StrongText = "__strong";
36 static constexpr StringRef UnsafeUnretainedText = "__unsafe_unretained";
37 
38 /// Matches ObjCIvarRefExpr, DeclRefExpr, or MemberExpr that reference
39 /// Objective-C object (or block) variables or fields whose object lifetimes
40 /// are not __unsafe_unretained.
AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,AST_POLYMORPHIC_SUPPORTED_TYPES (ObjCIvarRefExpr,DeclRefExpr,MemberExpr))41 AST_POLYMORPHIC_MATCHER(isObjCManagedLifetime,
42                         AST_POLYMORPHIC_SUPPORTED_TYPES(ObjCIvarRefExpr,
43                                                         DeclRefExpr,
44                                                         MemberExpr)) {
45   QualType QT = Node.getType();
46   return QT->isScalarType() &&
47          (QT->getScalarTypeKind() == Type::STK_ObjCObjectPointer ||
48           QT->getScalarTypeKind() == Type::STK_BlockPointer) &&
49          QT.getQualifiers().getObjCLifetime() > Qualifiers::OCL_ExplicitNone;
50 }
51 
52 static std::optional<FixItHint>
fixItHintReplacementForOwnershipString(StringRef Text,CharSourceRange Range,StringRef Ownership)53 fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,
54                                        StringRef Ownership) {
55   size_t Index = Text.find(Ownership);
56   if (Index == StringRef::npos)
57     return std::nullopt;
58 
59   SourceLocation Begin = Range.getBegin().getLocWithOffset(Index);
60   SourceLocation End = Begin.getLocWithOffset(Ownership.size());
61   return FixItHint::CreateReplacement(SourceRange(Begin, End),
62                                       UnsafeUnretainedText);
63 }
64 
65 static std::optional<FixItHint>
fixItHintForVarDecl(const VarDecl * VD,const SourceManager & SM,const LangOptions & LangOpts)66 fixItHintForVarDecl(const VarDecl *VD, const SourceManager &SM,
67                     const LangOptions &LangOpts) {
68   assert(VD && "VarDecl parameter must not be null");
69   // Don't provide fix-its for any parameter variables at this time.
70   if (isa<ParmVarDecl>(VD))
71     return std::nullopt;
72 
73   // Currently there is no way to directly get the source range for the
74   // __weak/__strong ObjC lifetime qualifiers, so it's necessary to string
75   // search in the source code.
76   CharSourceRange Range = Lexer::makeFileCharRange(
77       CharSourceRange::getTokenRange(VD->getSourceRange()), SM, LangOpts);
78   if (Range.isInvalid()) {
79     // An invalid range likely means inside a macro, in which case don't supply
80     // a fix-it.
81     return std::nullopt;
82   }
83 
84   StringRef VarDeclText = Lexer::getSourceText(Range, SM, LangOpts);
85   if (std::optional<FixItHint> Hint =
86           fixItHintReplacementForOwnershipString(VarDeclText, Range, WeakText))
87     return Hint;
88 
89   if (std::optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(
90           VarDeclText, Range, StrongText))
91     return Hint;
92 
93   return FixItHint::CreateInsertion(Range.getBegin(), "__unsafe_unretained ");
94 }
95 
96 } // namespace
97 
registerMatchers(MatchFinder * Finder)98 void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {
99   Finder->addMatcher(
100       traverse(
101           TK_AsIs,
102           objcMessageExpr(
103               hasReceiverType(asString("NSInvocation *")),
104               anyOf(hasSelector("getArgument:atIndex:"),
105                     hasSelector("getReturnValue:")),
106               hasArgument(
107                   0,
108                   anyOf(hasDescendant(memberExpr(isObjCManagedLifetime())),
109                         hasDescendant(objcIvarRefExpr(isObjCManagedLifetime())),
110                         hasDescendant(
111                             // Reference to variables, but when dereferencing
112                             // to ivars/fields a more-descendent variable
113                             // reference (e.g. self) may match with strong
114                             // object lifetime, leading to an incorrect match.
115                             // Exclude these conditions.
116                             declRefExpr(to(varDecl().bind("var")),
117                                         unless(hasParent(implicitCastExpr())),
118                                         isObjCManagedLifetime())))))
119               .bind("call")),
120       this);
121 }
122 
check(const MatchFinder::MatchResult & Result)123 void NSInvocationArgumentLifetimeCheck::check(
124     const MatchFinder::MatchResult &Result) {
125   const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>("call");
126 
127   auto Diag = diag(MatchedExpr->getArg(0)->getBeginLoc(),
128                    "NSInvocation %objcinstance0 should only pass pointers to "
129                    "objects with ownership __unsafe_unretained")
130               << MatchedExpr->getSelector();
131 
132   // Only provide fix-it hints for references to local variables; fixes for
133   // instance variable references don't have as clear an automated fix.
134   const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
135   if (!VD)
136     return;
137 
138   if (auto Hint = fixItHintForVarDecl(VD, *Result.SourceManager,
139                                       Result.Context->getLangOpts()))
140     Diag << *Hint;
141 }
142 
143 } // namespace clang::tidy::objc
144