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