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
29using namespace clang::ast_matchers;
30
31namespace clang::tidy::objc {
32namespace {
33
34static constexpr StringRef WeakText = "__weak";
35static constexpr StringRef StrongText = "__strong";
36static 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.
41AST_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
52static std::optional<FixItHint>
53fixItHintReplacementForOwnershipString(StringRef Text, CharSourceRange Range,
54 StringRef Ownership) {
55 size_t Index = Text.find(Str: Ownership);
56 if (Index == StringRef::npos)
57 return std::nullopt;
58
59 SourceLocation Begin = Range.getBegin().getLocWithOffset(Offset: Index);
60 SourceLocation End = Begin.getLocWithOffset(Offset: Ownership.size());
61 return FixItHint::CreateReplacement(RemoveRange: SourceRange(Begin, End),
62 Code: UnsafeUnretainedText);
63}
64
65static std::optional<FixItHint>
66fixItHintForVarDecl(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>(Val: 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 Range: CharSourceRange::getTokenRange(R: 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(Text: VarDeclText, Range, Ownership: WeakText))
87 return Hint;
88
89 if (std::optional<FixItHint> Hint = fixItHintReplacementForOwnershipString(
90 Text: VarDeclText, Range, Ownership: StrongText))
91 return Hint;
92
93 return FixItHint::CreateInsertion(InsertionLoc: Range.getBegin(), Code: "__unsafe_unretained ");
94}
95
96} // namespace
97
98void NSInvocationArgumentLifetimeCheck::registerMatchers(MatchFinder *Finder) {
99 Finder->addMatcher(
100 NodeMatch: traverse(
101 TK: TK_AsIs,
102 InnerMatcher: objcMessageExpr(
103 hasReceiverType(InnerMatcher: asString(Name: "NSInvocation *")),
104 anyOf(hasSelector(BaseName: "getArgument:atIndex:"),
105 hasSelector(BaseName: "getReturnValue:")),
106 hasArgument(
107 N: 0,
108 InnerMatcher: 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(InnerMatcher: varDecl().bind(ID: "var")),
117 unless(hasParent(implicitCastExpr())),
118 isObjCManagedLifetime())))))
119 .bind(ID: "call")),
120 Action: this);
121}
122
123void NSInvocationArgumentLifetimeCheck::check(
124 const MatchFinder::MatchResult &Result) {
125 const auto *MatchedExpr = Result.Nodes.getNodeAs<ObjCMessageExpr>(ID: "call");
126
127 auto Diag = diag(MatchedExpr->getArg(Arg: 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>(ID: "var");
135 if (!VD)
136 return;
137
138 if (auto Hint = fixItHintForVarDecl(VD, SM: *Result.SourceManager,
139 LangOpts: Result.Context->getLangOpts()))
140 Diag << *Hint;
141}
142
143} // namespace clang::tidy::objc
144

source code of clang-tools-extra/clang-tidy/objc/NSInvocationArgumentLifetimeCheck.cpp