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