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() || Method->isStatic()) {
159 // The method call cannot mutate our variable.
160 continue;
161 }
162 return false;
163 }
164 Stack.emplace_back(Member, 0);
165 continue;
166 }
167
168 if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
169 switch (Op->getOpcode()) {
170 case UO_AddrOf:
171 Stack.emplace_back(Op, Entry.Indirections + 1);
172 continue;
173 case UO_Deref:
174 assert(Entry.Indirections > 0);
175 Stack.emplace_back(Op, Entry.Indirections - 1);
176 continue;
177 default:
178 // Bail out on unary operators that we cannot analyze.
179 return false;
180 }
181 }
182
183 // Assume any other expression can modify the underlying variable.
184 return false;
185 }
186 }
187
188 // No parent can modify the variable.
189 return true;
190}
191
192} // namespace
193
194SmallPtrSet<const DeclRefExpr *, 16>
195constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
196 ASTContext &Context, int Indirections) {
197 auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))),
198 doesNotMutateObject(Indirections))
199 .bind("declRef")),
200 Stmt, Context);
201 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
202 extractNodesByIdTo(Matches, "declRef", DeclRefs);
203
204 return DeclRefs;
205}
206
207bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
208 ASTContext &Context, int Indirections) {
209 // Collect all DeclRefExprs to the loop variable and all CallExprs and
210 // CXXConstructExprs where the loop variable is used as argument to a const
211 // reference parameter.
212 // If the difference is empty it is safe for the loop variable to be a const
213 // reference.
214 auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context);
215 auto ConstReferenceDeclRefs =
216 constReferenceDeclRefExprs(Var, Stmt, Context, Indirections);
217 return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs);
218}
219
220SmallPtrSet<const DeclRefExpr *, 16>
221allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
222 auto Matches = match(
223 findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
224 Stmt, Context);
225 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
226 extractNodesByIdTo(Matches, "declRef", DeclRefs);
227 return DeclRefs;
228}
229
230SmallPtrSet<const DeclRefExpr *, 16>
231allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) {
232 auto Matches = match(
233 decl(forEachDescendant(
234 declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"))),
235 Decl, Context);
236 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
237 extractNodesByIdTo(Matches, "declRef", DeclRefs);
238 return DeclRefs;
239}
240
241bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
242 ASTContext &Context) {
243 auto UsedAsConstRefArg = forEachArgumentWithParam(
244 declRefExpr(equalsNode(&DeclRef)),
245 parmVarDecl(hasType(matchers::isReferenceToConst())));
246 auto Matches = match(
247 decl(hasDescendant(
248 cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(cxxConstructorDecl(
249 isCopyConstructor())))
250 .bind("constructExpr"))),
251 Decl, Context);
252 return !Matches.empty();
253}
254
255bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
256 ASTContext &Context) {
257 auto UsedAsConstRefArg = forEachArgumentWithParam(
258 declRefExpr(equalsNode(&DeclRef)),
259 parmVarDecl(hasType(matchers::isReferenceToConst())));
260 auto Matches = match(
261 decl(hasDescendant(
262 cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName("="),
263 callee(cxxMethodDecl(isCopyAssignmentOperator())))
264 .bind("operatorCallExpr"))),
265 Decl, Context);
266 return !Matches.empty();
267}
268
269} // 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 ...