clang-tools 22.0.0git
DeclRefExprUtils.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
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
22template <typename S>
23static bool isSetDifferenceEmpty(const S &S1, const S &S2) {
24 return llvm::none_of(S1, [&S2](const auto &E) { return !S2.contains(E); });
25}
26
27// Extracts all Nodes keyed by ID from Matches and inserts them into Nodes.
28template <typename Node>
29static void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
30 SmallPtrSet<const Node *, 16> &Nodes) {
31 for (const auto &Match : Matches)
32 Nodes.insert(Match.getNodeAs<Node>(ID));
33}
34
35// Returns true if both types refer to the same type,
36// ignoring the const-qualifier.
37static bool isSameTypeIgnoringConst(QualType A, QualType B) {
38 A = A.getCanonicalType();
39 B = B.getCanonicalType();
40 A.addConst();
41 B.addConst();
42 return A == B;
43}
44
45// Returns true if `D` and `O` have the same parameter types.
46static bool hasSameParameterTypes(const CXXMethodDecl &D,
47 const CXXMethodDecl &O) {
48 if (D.getNumParams() != O.getNumParams())
49 return false;
50 for (int I = 0, E = D.getNumParams(); I < E; ++I)
51 if (!isSameTypeIgnoringConst(D.getParamDecl(I)->getType(),
52 O.getParamDecl(I)->getType()))
53 return false;
54 return true;
55}
56
57// If `D` has a const-qualified overload with otherwise identical
58// ref-qualifiers and parameter types, returns that overload.
59static const CXXMethodDecl *findConstOverload(const CXXMethodDecl &D) {
60 assert(!D.isConst());
61
62 const DeclContext::lookup_result LookupResult =
63 D.getParent()->lookup(D.getNameInfo().getName());
64 if (LookupResult.isSingleResult()) {
65 // No overload.
66 return nullptr;
67 }
68 for (const Decl *Overload : LookupResult) {
69 const auto *O = dyn_cast<CXXMethodDecl>(Overload);
70 if (O && !O->isDeleted() && O->isConst() &&
71 O->getRefQualifier() == D.getRefQualifier() &&
73 return O;
74 }
75 return nullptr;
76}
77
78// Returns true if both types are pointers or reference to the same type,
79// ignoring the const-qualifier.
80static bool pointsToSameTypeIgnoringConst(QualType A, QualType B) {
81 assert(A->isPointerType() || A->isReferenceType());
82 assert(B->isPointerType() || B->isReferenceType());
83 return isSameTypeIgnoringConst(A->getPointeeType(), B->getPointeeType());
84}
85
86// Return true if non-const member function `M` likely does not mutate `*this`.
87//
88// Note that if the member call selects a method/operator `f` that
89// is not const-qualified, then we also consider that the object is
90// not mutated if:
91// - (A) there is a const-qualified overload `cf` of `f` that has
92// the
93// same ref-qualifiers;
94// - (B) * `f` returns a value, or
95// * if `f` returns a `T&`, `cf` returns a `const T&` (up to
96// possible aliases such as `reference` and
97// `const_reference`), or
98// * if `f` returns a `T*`, `cf` returns a `const T*` (up to
99// possible aliases).
100// - (C) the result of the call is not mutated.
101//
102// The assumption that `cf` has the same semantics as `f`.
103// For example:
104// - In `std::vector<T> v; const T t = v[...];`, we consider that
105// expression `v[...]` does not mutate `v` as
106// `T& std::vector<T>::operator[]` has a const overload
107// `const T& std::vector<T>::operator[] const`, and the
108// result expression of type `T&` is only used as a `const T&`;
109// - In `std::map<K, V> m; V v = m.at(...);`, we consider
110// `m.at(...)` to be an immutable access for the same reason.
111// However:
112// - In `std::map<K, V> m; const V v = m[...];`, We consider that
113// `m[...]` mutates `m` as `V& std::map<K, V>::operator[]` does
114// not have a const overload.
115// - In `std::vector<T> v; T& t = v[...];`, we consider that
116// expression `v[...]` mutates `v` as the result is kept as a
117// mutable reference.
118//
119// This function checks (A) ad (B), but the caller should make sure that the
120// object is not mutated through the return value.
121static bool isLikelyShallowConst(const CXXMethodDecl &M) {
122 assert(!M.isConst());
123 // The method can mutate our variable.
124
125 // (A)
126 const CXXMethodDecl *ConstOverload = findConstOverload(M);
127 if (ConstOverload == nullptr)
128 return false;
129
130 // (B)
131 const QualType CallTy = M.getReturnType().getCanonicalType();
132 const QualType OverloadTy = ConstOverload->getReturnType().getCanonicalType();
133 if (CallTy->isReferenceType()) {
134 return OverloadTy->isReferenceType() &&
135 pointsToSameTypeIgnoringConst(CallTy, OverloadTy);
136 }
137 if (CallTy->isPointerType()) {
138 return OverloadTy->isPointerType() &&
139 pointsToSameTypeIgnoringConst(CallTy, OverloadTy);
140 }
141 return isSameTypeIgnoringConst(CallTy, OverloadTy);
142}
143
144namespace {
145
146// A matcher that matches DeclRefExprs that are used in ways such that the
147// underlying declaration is not modified.
148// If the declaration is of pointer type, `Indirections` specifies the level
149// of indirection of the object whose mutations we are tracking.
150//
151// For example, given:
152// ```
153// int i;
154// int* p;
155// p = &i; // (A)
156// *p = 3; // (B)
157// ```
158//
159// `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches
160// (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))`
161// matches (A).
162//
163AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
164 // We walk up the parents of the DeclRefExpr recursively. There are a few
165 // kinds of expressions:
166 // - Those that cannot be used to mutate the underlying variable. We can stop
167 // recursion there.
168 // - Those that can be used to mutate the underlying variable in analyzable
169 // ways (such as taking the address or accessing a subobject). We have to
170 // examine the parents.
171 // - Those that we don't know how to analyze. In that case we stop there and
172 // we assume that they can modify the expression.
173
174 struct StackEntry {
175 StackEntry(const Expr *E, int Indirections)
176 : E(E), Indirections(Indirections) {}
177 // The expression to analyze.
178 const Expr *E;
179 // The number of pointer indirections of the object being tracked (how
180 // many times an address was taken).
181 int Indirections;
182 };
183
184 llvm::SmallVector<StackEntry, 4> Stack;
185 Stack.emplace_back(&Node, Indirections);
186 ASTContext &Ctx = Finder->getASTContext();
187
188 while (!Stack.empty()) {
189 const StackEntry Entry = Stack.back();
190 Stack.pop_back();
191
192 // If the expression type is const-qualified at the appropriate indirection
193 // level then we can not mutate the object.
194 QualType Ty = Entry.E->getType().getCanonicalType();
195 for (int I = 0; I < Entry.Indirections; ++I) {
196 assert(Ty->isPointerType());
197 Ty = Ty->getPointeeType().getCanonicalType();
198 }
199 if (Ty->isVoidType() || Ty.isConstQualified())
200 continue;
201
202 // Otherwise we have to look at the parents to see how the expression is
203 // used.
204 const DynTypedNodeList Parents = Ctx.getParents(*Entry.E);
205 // Note: most nodes have a single parents, but there exist nodes that have
206 // several parents, such as `InitListExpr` that have semantic and syntactic
207 // forms.
208 for (const auto &Parent : Parents) {
209 if (Parent.get<CompoundStmt>()) {
210 // Unused block-scope statement.
211 continue;
212 }
213 const Expr *const P = Parent.get<Expr>();
214 if (P == nullptr) {
215 // `Parent` is not an expr (e.g. a `VarDecl`).
216 // The case of binding to a `const&` or `const*` variable is handled by
217 // the fact that there is going to be a `NoOp` cast to const below the
218 // `VarDecl`, so we're not even going to get there.
219 // The case of copying into a value-typed variable is handled by the
220 // rvalue cast.
221 // This triggers only when binding to a mutable reference/ptr variable.
222 // FIXME: When we take a mutable reference we could keep checking the
223 // new variable for const usage only.
224 return false;
225 }
226 // Cosmetic nodes.
227 if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
228 Stack.emplace_back(P, Entry.Indirections);
229 continue;
230 }
231 if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
232 switch (Cast->getCastKind()) {
233 // NoOp casts are used to add `const`. We'll check whether adding that
234 // const prevents modification when we process the cast.
235 case CK_NoOp:
236 // These do nothing w.r.t. to mutability.
237 case CK_BaseToDerived:
238 case CK_DerivedToBase:
239 case CK_UncheckedDerivedToBase:
240 case CK_Dynamic:
241 case CK_BaseToDerivedMemberPointer:
242 case CK_DerivedToBaseMemberPointer:
243 Stack.emplace_back(Cast, Entry.Indirections);
244 continue;
245 case CK_ToVoid:
246 case CK_PointerToBoolean:
247 // These do not mutate the underlying variable.
248 continue;
249 case CK_LValueToRValue: {
250 // An rvalue is immutable.
251 if (Entry.Indirections == 0)
252 continue;
253 Stack.emplace_back(Cast, Entry.Indirections);
254 continue;
255 }
256 default:
257 // Bail out on casts that we cannot analyze.
258 return false;
259 }
260 }
261 if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
262 if (const auto *const Method =
263 dyn_cast<CXXMethodDecl>(Member->getMemberDecl())) {
264 if (Method->isConst() || Method->isStatic()) {
265 // The method call cannot mutate our variable.
266 continue;
267 }
268 if (isLikelyShallowConst(*Method)) {
269 // We still have to check that the object is not modified through
270 // the method's return value (C).
271 const auto MemberParents = Ctx.getParents(*Member);
272 assert(MemberParents.size() == 1);
273 const auto *Call = MemberParents[0].get<CallExpr>();
274 // If `o` is an object of class type and `f` is a member function,
275 // then `o.f` has to be used as part of a call expression.
276 assert(Call != nullptr && "member function has to be called");
277 Stack.emplace_back(
278 Call,
279 Method->getReturnType().getCanonicalType()->isPointerType()
280 ? 1
281 : 0);
282 continue;
283 }
284 return false;
285 }
286 Stack.emplace_back(Member, 0);
287 continue;
288 }
289 if (const auto *const OpCall = dyn_cast<CXXOperatorCallExpr>(P)) {
290 // Operator calls have function call syntax. The `*this` parameter
291 // is the first parameter.
292 if (OpCall->getNumArgs() == 0 || OpCall->getArg(0) != Entry.E)
293 return false;
294 const auto *const Method =
295 dyn_cast_or_null<CXXMethodDecl>(OpCall->getDirectCallee());
296
297 if (Method == nullptr) {
298 // This is not a member operator. Typically, a friend operator. These
299 // are handled like function calls.
300 return false;
301 }
302
303 if (Method->isConst() || Method->isStatic())
304 continue;
305 if (isLikelyShallowConst(*Method)) {
306 // We still have to check that the object is not modified through
307 // the operator's return value (C).
308 Stack.emplace_back(
309 OpCall,
310 Method->getReturnType().getCanonicalType()->isPointerType() ? 1
311 : 0);
312 continue;
313 }
314 return false;
315 }
316
317 if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
318 switch (Op->getOpcode()) {
319 case UO_AddrOf:
320 Stack.emplace_back(Op, Entry.Indirections + 1);
321 continue;
322 case UO_Deref:
323 assert(Entry.Indirections > 0);
324 Stack.emplace_back(Op, Entry.Indirections - 1);
325 continue;
326 default:
327 // Bail out on unary operators that we cannot analyze.
328 return false;
329 }
330 }
331
332 // Assume any other expression can modify the underlying variable.
333 return false;
334 }
335 }
336
337 // No parent can modify the variable.
338 return true;
339}
340
341} // namespace
342
343SmallPtrSet<const DeclRefExpr *, 16>
344constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
345 ASTContext &Context, int Indirections) {
346 auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))),
347 doesNotMutateObject(Indirections))
348 .bind("declRef")),
349 Stmt, Context);
350 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
351 extractNodesByIdTo(Matches, "declRef", DeclRefs);
352
353 return DeclRefs;
354}
355
356bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
357 ASTContext &Context, int Indirections) {
358 // Collect all DeclRefExprs to the loop variable and all CallExprs and
359 // CXXConstructExprs where the loop variable is used as argument to a const
360 // reference parameter.
361 // If the difference is empty it is safe for the loop variable to be a const
362 // reference.
363 auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context);
364 auto ConstReferenceDeclRefs =
365 constReferenceDeclRefExprs(Var, Stmt, Context, Indirections);
366 return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs);
367}
368
369SmallPtrSet<const DeclRefExpr *, 16>
370allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context) {
371 auto Matches = match(
372 findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef")),
373 Stmt, Context);
374 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
375 extractNodesByIdTo(Matches, "declRef", DeclRefs);
376 return DeclRefs;
377}
378
379SmallPtrSet<const DeclRefExpr *, 16>
380allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context) {
381 auto Matches = match(
382 decl(forEachDescendant(
383 declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef"))),
384 Decl, Context);
385 SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
386 extractNodesByIdTo(Matches, "declRef", DeclRefs);
387 return DeclRefs;
388}
389
390bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
391 ASTContext &Context) {
392 auto UsedAsConstRefArg = forEachArgumentWithParam(
393 declRefExpr(equalsNode(&DeclRef)),
394 parmVarDecl(hasType(matchers::isReferenceToConst())));
395 auto Matches = match(
396 decl(hasDescendant(
397 cxxConstructExpr(UsedAsConstRefArg, hasDeclaration(cxxConstructorDecl(
398 isCopyConstructor())))
399 .bind("constructExpr"))),
400 Decl, Context);
401 return !Matches.empty();
402}
403
404bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl,
405 ASTContext &Context) {
406 auto UsedAsConstRefArg = forEachArgumentWithParam(
407 declRefExpr(equalsNode(&DeclRef)),
408 parmVarDecl(hasType(matchers::isReferenceToConst())));
409 auto Matches = match(
410 decl(hasDescendant(
411 cxxOperatorCallExpr(UsedAsConstRefArg, hasOverloadedOperatorName("="),
412 callee(cxxMethodDecl(isCopyAssignmentOperator())))
413 .bind("operatorCallExpr"))),
414 Decl, Context);
415 return !Matches.empty();
416}
417
418} // namespace clang::tidy::utils::decl_ref_expr
static bool hasSameParameterTypes(const CXXMethodDecl &D, const CXXMethodDecl &O)
static const CXXMethodDecl * findConstOverload(const CXXMethodDecl &D)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
static bool isLikelyShallowConst(const CXXMethodDecl &M)
static bool pointsToSameTypeIgnoringConst(QualType A, QualType B)
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.
static bool isSameTypeIgnoringConst(QualType A, QualType B)
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. See constReferenceDeclR...
static void extractNodesByIdTo(ArrayRef< BoundNodes > Matches, StringRef ID, SmallPtrSet< const Node *, 16 > &Nodes)
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.
static bool isSetDifferenceEmpty(const S &S1, const S &S2)
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 ...
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P