clang-tools 19.0.0git
DeclRefExprUtils.cpp
Go to the documentation of this file.
1//===--- DeclRefExprUtils.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 "DeclRefExprUtils.h"
10#include "Matchers.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/DeclCXX.h"
13#include "clang/AST/ExprCXX.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include <cassert>
16
18
19using namespace ::clang::ast_matchers;
20using llvm::SmallPtrSet;
21
22namespace {
23
24template <typename S> bool isSetDifferenceEmpty(const S &S1, const S &S2) {
25 for (auto E : S1)
26 if (S2.count(E) == 0)
27 return false;
28 return true;
29}
30
31// Extracts all Nodes keyed by ID from Matches and inserts them into Nodes.
32template <typename Node>
33void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
34 SmallPtrSet<const Node *, 16> &Nodes) {
35 for (const auto &Match : Matches)
36 Nodes.insert(Match.getNodeAs<Node>(ID));
37}
38
39// A matcher that matches DeclRefExprs that are used in ways such that the
40// underlying declaration is not modified.
41// If the declaration is of pointer type, `Indirections` specifies the level
42// of indirection of the object whose mutations we are tracking.
43//
44// For example, given:
45// ```
46// int i;
47// int* p;
48// p = &i; // (A)
49// *p = 3; // (B)
50// ```
51//
52// `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches
53// (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))`
54// matches (A).
55//
56AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
57 // We walk up the parents of the DeclRefExpr recursively until we end up on a
58 // parent that cannot modify the underlying object. There are a few kinds of
59 // expressions:
60 // - Those that cannot be used to mutate the underlying object. We can stop
61 // recursion there.
62 // - Those that can be used to mutate the underlying object in analyzable
63 // ways (such as taking the address or accessing a subobject). We have to
64 // examine the parents.
65 // - Those that we don't know how to analyze. In that case we stop there and
66 // we assume that they can mutate the underlying expression.
67
68 struct StackEntry {
69 StackEntry(const Expr *E, int Indirections)
70 : E(E), Indirections(Indirections) {}
71 // The expression to analyze.
72 const Expr *E;
73 // The number of pointer indirections of the object being tracked (how
74 // many times an address was taken).
75 int Indirections;
76 };
77
78 llvm::SmallVector<StackEntry, 4> Stack;
79 Stack.emplace_back(&Node, Indirections);
80 ASTContext &Ctx = Finder->getASTContext();
81
82 while (!Stack.empty()) {
83 const StackEntry Entry = Stack.back();
84 Stack.pop_back();
85
86 // If the expression type is const-qualified at the appropriate indirection
87 // level then we can not mutate the object.
88 QualType Ty = Entry.E->getType().getCanonicalType();
89 for (int I = 0; I < Entry.Indirections; ++I) {
90 assert(Ty->isPointerType());
91 Ty = Ty->getPointeeType().getCanonicalType();
92 }
93 if (Ty.isConstQualified())
94 continue;
95
96 // Otherwise we have to look at the parents to see how the expression is
97 // used.
98 const DynTypedNodeList Parents = Ctx.getParents(*Entry.E);
99 // Note: most nodes have a single parents, but there exist nodes that have
100 // several parents, such as `InitListExpr` that have semantic and syntactic
101 // forms.
102 for (const auto &Parent : Parents) {
103 if (Parent.get<CompoundStmt>()) {
104 // Unused block-scope statement.
105 continue;
106 }
107 const Expr *const P = Parent.get<Expr>();
108 if (P == nullptr) {
109 // `Parent` is not an expr (e.g. a `VarDecl`).
110 // The case of binding to a `const&` or `const*` variable is handled by
111 // the fact that there is going to be a `NoOp` cast to const below the
112 // `VarDecl`, so we're not even going to get there.
113 // The case of copying into a value-typed variable is handled by the
114 // rvalue cast.
115 // This triggers only when binding to a mutable reference/ptr variable.
116 // FIXME: When we take a mutable reference we could keep checking the
117 // new variable for const usage only.
118 return false;
119 }
120 // Cosmetic nodes.
121 if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
122 Stack.emplace_back(P, Entry.Indirections);
123 continue;
124 }
125 if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
126 switch (Cast->getCastKind()) {
127 // NoOp casts are used to add `const`. We'll check whether adding that
128 // const prevents modification when we process the cast.
129 case CK_NoOp:
130 // These do nothing w.r.t. to mutability.
131 case CK_BaseToDerived:
132 case CK_DerivedToBase:
133 case CK_UncheckedDerivedToBase:
134 case CK_Dynamic:
135 case CK_BaseToDerivedMemberPointer:
136 case CK_DerivedToBaseMemberPointer:
137 Stack.emplace_back(Cast, Entry.Indirections);
138 continue;
139 case CK_ToVoid:
140 case CK_PointerToBoolean:
141 // These do not mutate the underlying variable.
142 continue;
143 case CK_LValueToRValue: {
144 // An rvalue is immutable.
145 if (Entry.Indirections == 0)
146 continue;
147 Stack.emplace_back(Cast, Entry.Indirections);
148 continue;
149 }
150 default:
151 // Bail out on casts that we cannot analyze.
152 return false;
153 }
154 }
155 if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
156 if (const auto *const Method =
157 dyn_cast<CXXMethodDecl>(Member->getMemberDecl())) {
158 if (!Method->isConst()) {
159 // The method can mutate our variable.
160 return false;
161 }
162 continue;
163 }
164 Stack.emplace_back(Member, 0);
165 continue;
166 }
167 if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
168 switch (Op->getOpcode()) {
169 case UO_AddrOf:
170 Stack.emplace_back(Op, Entry.Indirections + 1);
171 continue;
172 case UO_Deref:
173 assert(Entry.Indirections > 0);
174 Stack.emplace_back(Op, Entry.Indirections - 1);
175 continue;
176 default:
177 // Bail out on unary operators that we cannot analyze.
178 return false;
179 }
180 }
181
182 // Assume any other expression can modify the underlying variable.
183 return false;
184 }
185 }
186
187 // No parent can modify the variable.
188 return true;
189}
190
191} // namespace
192
193SmallPtrSet<const DeclRefExpr *, 16>
194constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
195 ASTContext &Context, int Indirections) {
196 auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))),
197 doesNotMutateObject(Indirections))
198 .bind("declRef")),
199 Stmt, Context);
200 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
201 extractNodesByIdTo(Matches, "declRef", DeclRefs);
202
203 return DeclRefs;
204}
205
206bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
207 ASTContext &Context, int Indirections) {
208 // Collect all DeclRefExprs to the loop variable and all CallExprs and
209 // CXXConstructExprs where the loop variable is used as argument to a const
210 // reference parameter.
211 // If the difference is empty it is safe for the loop variable to be a const
212 // reference.
213 auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context);
214 auto ConstReferenceDeclRefs =
215 constReferenceDeclRefExprs(Var, Stmt, Context, Indirections);
216 return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs);
217}
218
219SmallPtrSet<const DeclRefExpr *, 16>
220allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
221 auto Matches = match(
222 findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
223 Stmt, Context);
224 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
225 extractNodesByIdTo(Matches, "declRef", DeclRefs);
226 return DeclRefs;
227}
228
229SmallPtrSet<const DeclRefExpr *, 16>
230allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) {
231 auto Matches = match(
232 decl(forEachDescendant(
233 declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"))),
234 Decl, Context);
235 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
236 extractNodesByIdTo(Matches, "declRef", DeclRefs);
237 return DeclRefs;
238}
239
240bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
241 ASTContext &Context) {
242 auto UsedAsConstRefArg = forEachArgumentWithParam(
243 declRefExpr(equalsNode(&DeclRef)),
244 parmVarDecl(hasType(matchers::isReferenceToConst())));
245 auto Matches = match(
246 decl(hasDescendant(
247 cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(cxxConstructorDecl(
248 isCopyConstructor())))
249 .bind("constructExpr"))),
250 Decl, Context);
251 return !Matches.empty();
252}
253
254bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
255 ASTContext &Context) {
256 auto UsedAsConstRefArg = forEachArgumentWithParam(
257 declRefExpr(equalsNode(&DeclRef)),
258 parmVarDecl(hasType(matchers::isReferenceToConst())));
259 auto Matches = match(
260 decl(hasDescendant(
261 cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName("="),
262 callee(cxxMethodDecl(isCopyAssignmentOperator())))
263 .bind("operatorCallExpr"))),
264 Decl, Context);
265 return !Matches.empty();
266}
267
268} // namespace clang::tidy::utils::decl_ref_expr
const Expr * E
const FunctionDecl * Decl
const Node * Parent
::clang::DynTypedNode Node
const DeclRefExpr * DeclRef
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, ASTContext &Context)
Returns true if DeclRefExpr is the argument of a copy-constructor call expression within Decl.
bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, ASTContext &Context, int Indirections)
Returns true if all DeclRefExpr to the variable within Stmt do not modify it.
bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, ASTContext &Context)
Returns true if DeclRefExpr is the argument of a copy-assignment operator CallExpr within Decl.
SmallPtrSet< const DeclRefExpr *, 16 > constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context, int Indirections)
Returns set of all DeclRefExprs to VarDecl within Stmt where VarDecl is guaranteed to be accessed in ...