Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp
35234 views
1
//===---------- ExprMutationAnalyzer.cpp ----------------------------------===//
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
#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
9
#include "clang/AST/Expr.h"
10
#include "clang/AST/OperationKinds.h"
11
#include "clang/ASTMatchers/ASTMatchFinder.h"
12
#include "clang/ASTMatchers/ASTMatchers.h"
13
#include "llvm/ADT/STLExtras.h"
14
15
namespace clang {
16
using namespace ast_matchers;
17
18
// Check if result of Source expression could be a Target expression.
19
// Checks:
20
// - Implicit Casts
21
// - Binary Operators
22
// - ConditionalOperator
23
// - BinaryConditionalOperator
24
static bool canExprResolveTo(const Expr *Source, const Expr *Target) {
25
26
const auto IgnoreDerivedToBase = [](const Expr *E, auto Matcher) {
27
if (Matcher(E))
28
return true;
29
if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
30
if ((Cast->getCastKind() == CK_DerivedToBase ||
31
Cast->getCastKind() == CK_UncheckedDerivedToBase) &&
32
Matcher(Cast->getSubExpr()))
33
return true;
34
}
35
return false;
36
};
37
38
const auto EvalCommaExpr = [](const Expr *E, auto Matcher) {
39
const Expr *Result = E;
40
while (const auto *BOComma =
41
dyn_cast_or_null<BinaryOperator>(Result->IgnoreParens())) {
42
if (!BOComma->isCommaOp())
43
break;
44
Result = BOComma->getRHS();
45
}
46
47
return Result != E && Matcher(Result);
48
};
49
50
// The 'ConditionalOperatorM' matches on `<anything> ? <expr> : <expr>`.
51
// This matching must be recursive because `<expr>` can be anything resolving
52
// to the `InnerMatcher`, for example another conditional operator.
53
// The edge-case `BaseClass &b = <cond> ? DerivedVar1 : DerivedVar2;`
54
// is handled, too. The implicit cast happens outside of the conditional.
55
// This is matched by `IgnoreDerivedToBase(canResolveToExpr(InnerMatcher))`
56
// below.
57
const auto ConditionalOperatorM = [Target](const Expr *E) {
58
if (const auto *OP = dyn_cast<ConditionalOperator>(E)) {
59
if (const auto *TE = OP->getTrueExpr()->IgnoreParens())
60
if (canExprResolveTo(TE, Target))
61
return true;
62
if (const auto *FE = OP->getFalseExpr()->IgnoreParens())
63
if (canExprResolveTo(FE, Target))
64
return true;
65
}
66
return false;
67
};
68
69
const auto ElvisOperator = [Target](const Expr *E) {
70
if (const auto *OP = dyn_cast<BinaryConditionalOperator>(E)) {
71
if (const auto *TE = OP->getTrueExpr()->IgnoreParens())
72
if (canExprResolveTo(TE, Target))
73
return true;
74
if (const auto *FE = OP->getFalseExpr()->IgnoreParens())
75
if (canExprResolveTo(FE, Target))
76
return true;
77
}
78
return false;
79
};
80
81
const Expr *SourceExprP = Source->IgnoreParens();
82
return IgnoreDerivedToBase(SourceExprP,
83
[&](const Expr *E) {
84
return E == Target || ConditionalOperatorM(E) ||
85
ElvisOperator(E);
86
}) ||
87
EvalCommaExpr(SourceExprP, [&](const Expr *E) {
88
return IgnoreDerivedToBase(
89
E->IgnoreParens(), [&](const Expr *EE) { return EE == Target; });
90
});
91
}
92
93
namespace {
94
95
AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {
96
return llvm::is_contained(Node.capture_inits(), E);
97
}
98
99
AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
100
ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
101
const DeclStmt *const Range = Node.getRangeStmt();
102
return InnerMatcher.matches(*Range, Finder, Builder);
103
}
104
105
AST_MATCHER_P(Stmt, canResolveToExpr, const Stmt *, Inner) {
106
auto *Exp = dyn_cast<Expr>(&Node);
107
if (!Exp)
108
return true;
109
auto *Target = dyn_cast<Expr>(Inner);
110
if (!Target)
111
return false;
112
return canExprResolveTo(Exp, Target);
113
}
114
115
// Similar to 'hasAnyArgument', but does not work because 'InitListExpr' does
116
// not have the 'arguments()' method.
117
AST_MATCHER_P(InitListExpr, hasAnyInit, ast_matchers::internal::Matcher<Expr>,
118
InnerMatcher) {
119
for (const Expr *Arg : Node.inits()) {
120
ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);
121
if (InnerMatcher.matches(*Arg, Finder, &Result)) {
122
*Builder = std::move(Result);
123
return true;
124
}
125
}
126
return false;
127
}
128
129
const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>
130
cxxTypeidExpr;
131
132
AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) {
133
return Node.isPotentiallyEvaluated();
134
}
135
136
AST_MATCHER(CXXMemberCallExpr, isConstCallee) {
137
const Decl *CalleeDecl = Node.getCalleeDecl();
138
const auto *VD = dyn_cast_or_null<ValueDecl>(CalleeDecl);
139
if (!VD)
140
return false;
141
const QualType T = VD->getType().getCanonicalType();
142
const auto *MPT = dyn_cast<MemberPointerType>(T);
143
const auto *FPT = MPT ? cast<FunctionProtoType>(MPT->getPointeeType())
144
: dyn_cast<FunctionProtoType>(T);
145
if (!FPT)
146
return false;
147
return FPT->isConst();
148
}
149
150
AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr,
151
ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
152
if (Node.isTypePredicate())
153
return false;
154
return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder);
155
}
156
157
template <typename T>
158
ast_matchers::internal::Matcher<T>
159
findFirst(const ast_matchers::internal::Matcher<T> &Matcher) {
160
return anyOf(Matcher, hasDescendant(Matcher));
161
}
162
163
const auto nonConstReferenceType = [] {
164
return hasUnqualifiedDesugaredType(
165
referenceType(pointee(unless(isConstQualified()))));
166
};
167
168
const auto nonConstPointerType = [] {
169
return hasUnqualifiedDesugaredType(
170
pointerType(pointee(unless(isConstQualified()))));
171
};
172
173
const auto isMoveOnly = [] {
174
return cxxRecordDecl(
175
hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))),
176
hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))),
177
unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(),
178
unless(isDeleted()))),
179
hasMethod(cxxMethodDecl(isCopyAssignmentOperator(),
180
unless(isDeleted()))))));
181
};
182
183
template <class T> struct NodeID;
184
template <> struct NodeID<Expr> { static constexpr StringRef value = "expr"; };
185
template <> struct NodeID<Decl> { static constexpr StringRef value = "decl"; };
186
constexpr StringRef NodeID<Expr>::value;
187
constexpr StringRef NodeID<Decl>::value;
188
189
template <class T,
190
class F = const Stmt *(ExprMutationAnalyzer::Analyzer::*)(const T *)>
191
const Stmt *tryEachMatch(ArrayRef<ast_matchers::BoundNodes> Matches,
192
ExprMutationAnalyzer::Analyzer *Analyzer, F Finder) {
193
const StringRef ID = NodeID<T>::value;
194
for (const auto &Nodes : Matches) {
195
if (const Stmt *S = (Analyzer->*Finder)(Nodes.getNodeAs<T>(ID)))
196
return S;
197
}
198
return nullptr;
199
}
200
201
} // namespace
202
203
const Stmt *ExprMutationAnalyzer::Analyzer::findMutation(const Expr *Exp) {
204
return findMutationMemoized(
205
Exp,
206
{&ExprMutationAnalyzer::Analyzer::findDirectMutation,
207
&ExprMutationAnalyzer::Analyzer::findMemberMutation,
208
&ExprMutationAnalyzer::Analyzer::findArrayElementMutation,
209
&ExprMutationAnalyzer::Analyzer::findCastMutation,
210
&ExprMutationAnalyzer::Analyzer::findRangeLoopMutation,
211
&ExprMutationAnalyzer::Analyzer::findReferenceMutation,
212
&ExprMutationAnalyzer::Analyzer::findFunctionArgMutation},
213
Memorized.Results);
214
}
215
216
const Stmt *ExprMutationAnalyzer::Analyzer::findMutation(const Decl *Dec) {
217
return tryEachDeclRef(Dec, &ExprMutationAnalyzer::Analyzer::findMutation);
218
}
219
220
const Stmt *
221
ExprMutationAnalyzer::Analyzer::findPointeeMutation(const Expr *Exp) {
222
return findMutationMemoized(Exp, {/*TODO*/}, Memorized.PointeeResults);
223
}
224
225
const Stmt *
226
ExprMutationAnalyzer::Analyzer::findPointeeMutation(const Decl *Dec) {
227
return tryEachDeclRef(Dec,
228
&ExprMutationAnalyzer::Analyzer::findPointeeMutation);
229
}
230
231
const Stmt *ExprMutationAnalyzer::Analyzer::findMutationMemoized(
232
const Expr *Exp, llvm::ArrayRef<MutationFinder> Finders,
233
Memoized::ResultMap &MemoizedResults) {
234
const auto Memoized = MemoizedResults.find(Exp);
235
if (Memoized != MemoizedResults.end())
236
return Memoized->second;
237
238
// Assume Exp is not mutated before analyzing Exp.
239
MemoizedResults[Exp] = nullptr;
240
if (isUnevaluated(Exp))
241
return nullptr;
242
243
for (const auto &Finder : Finders) {
244
if (const Stmt *S = (this->*Finder)(Exp))
245
return MemoizedResults[Exp] = S;
246
}
247
248
return nullptr;
249
}
250
251
const Stmt *
252
ExprMutationAnalyzer::Analyzer::tryEachDeclRef(const Decl *Dec,
253
MutationFinder Finder) {
254
const auto Refs = match(
255
findAll(
256
declRefExpr(to(
257
// `Dec` or a binding if `Dec` is a decomposition.
258
anyOf(equalsNode(Dec),
259
bindingDecl(forDecomposition(equalsNode(Dec))))
260
//
261
))
262
.bind(NodeID<Expr>::value)),
263
Stm, Context);
264
for (const auto &RefNodes : Refs) {
265
const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);
266
if ((this->*Finder)(E))
267
return E;
268
}
269
return nullptr;
270
}
271
272
bool ExprMutationAnalyzer::Analyzer::isUnevaluated(const Stmt *Exp,
273
const Stmt &Stm,
274
ASTContext &Context) {
275
return selectFirst<Stmt>(
276
NodeID<Expr>::value,
277
match(
278
findFirst(
279
stmt(canResolveToExpr(Exp),
280
anyOf(
281
// `Exp` is part of the underlying expression of
282
// decltype/typeof if it has an ancestor of
283
// typeLoc.
284
hasAncestor(typeLoc(unless(
285
hasAncestor(unaryExprOrTypeTraitExpr())))),
286
hasAncestor(expr(anyOf(
287
// `UnaryExprOrTypeTraitExpr` is unevaluated
288
// unless it's sizeof on VLA.
289
unaryExprOrTypeTraitExpr(unless(sizeOfExpr(
290
hasArgumentOfType(variableArrayType())))),
291
// `CXXTypeidExpr` is unevaluated unless it's
292
// applied to an expression of glvalue of
293
// polymorphic class type.
294
cxxTypeidExpr(
295
unless(isPotentiallyEvaluated())),
296
// The controlling expression of
297
// `GenericSelectionExpr` is unevaluated.
298
genericSelectionExpr(hasControllingExpr(
299
hasDescendant(equalsNode(Exp)))),
300
cxxNoexceptExpr())))))
301
.bind(NodeID<Expr>::value)),
302
Stm, Context)) != nullptr;
303
}
304
305
bool ExprMutationAnalyzer::Analyzer::isUnevaluated(const Expr *Exp) {
306
return isUnevaluated(Exp, Stm, Context);
307
}
308
309
const Stmt *
310
ExprMutationAnalyzer::Analyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {
311
return tryEachMatch<Expr>(Matches, this,
312
&ExprMutationAnalyzer::Analyzer::findMutation);
313
}
314
315
const Stmt *
316
ExprMutationAnalyzer::Analyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {
317
return tryEachMatch<Decl>(Matches, this,
318
&ExprMutationAnalyzer::Analyzer::findMutation);
319
}
320
321
const Stmt *ExprMutationAnalyzer::Analyzer::findExprPointeeMutation(
322
ArrayRef<ast_matchers::BoundNodes> Matches) {
323
return tryEachMatch<Expr>(
324
Matches, this, &ExprMutationAnalyzer::Analyzer::findPointeeMutation);
325
}
326
327
const Stmt *ExprMutationAnalyzer::Analyzer::findDeclPointeeMutation(
328
ArrayRef<ast_matchers::BoundNodes> Matches) {
329
return tryEachMatch<Decl>(
330
Matches, this, &ExprMutationAnalyzer::Analyzer::findPointeeMutation);
331
}
332
333
const Stmt *
334
ExprMutationAnalyzer::Analyzer::findDirectMutation(const Expr *Exp) {
335
// LHS of any assignment operators.
336
const auto AsAssignmentLhs =
337
binaryOperator(isAssignmentOperator(), hasLHS(canResolveToExpr(Exp)));
338
339
// Operand of increment/decrement operators.
340
const auto AsIncDecOperand =
341
unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
342
hasUnaryOperand(canResolveToExpr(Exp)));
343
344
// Invoking non-const member function.
345
// A member function is assumed to be non-const when it is unresolved.
346
const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
347
348
const auto AsNonConstThis = expr(anyOf(
349
cxxMemberCallExpr(on(canResolveToExpr(Exp)), unless(isConstCallee())),
350
cxxOperatorCallExpr(callee(NonConstMethod),
351
hasArgument(0, canResolveToExpr(Exp))),
352
// In case of a templated type, calling overloaded operators is not
353
// resolved and modelled as `binaryOperator` on a dependent type.
354
// Such instances are considered a modification, because they can modify
355
// in different instantiations of the template.
356
binaryOperator(isTypeDependent(),
357
hasEitherOperand(ignoringImpCasts(canResolveToExpr(Exp)))),
358
// A fold expression may contain `Exp` as it's initializer.
359
// We don't know if the operator modifies `Exp` because the
360
// operator is type dependent due to the parameter pack.
361
cxxFoldExpr(hasFoldInit(ignoringImpCasts(canResolveToExpr(Exp)))),
362
// Within class templates and member functions the member expression might
363
// not be resolved. In that case, the `callExpr` is considered to be a
364
// modification.
365
callExpr(callee(expr(anyOf(
366
unresolvedMemberExpr(hasObjectExpression(canResolveToExpr(Exp))),
367
cxxDependentScopeMemberExpr(
368
hasObjectExpression(canResolveToExpr(Exp))))))),
369
// Match on a call to a known method, but the call itself is type
370
// dependent (e.g. `vector<T> v; v.push(T{});` in a templated function).
371
callExpr(allOf(
372
isTypeDependent(),
373
callee(memberExpr(hasDeclaration(NonConstMethod),
374
hasObjectExpression(canResolveToExpr(Exp))))))));
375
376
// Taking address of 'Exp'.
377
// We're assuming 'Exp' is mutated as soon as its address is taken, though in
378
// theory we can follow the pointer and see whether it escaped `Stm` or is
379
// dereferenced and then mutated. This is left for future improvements.
380
const auto AsAmpersandOperand =
381
unaryOperator(hasOperatorName("&"),
382
// A NoOp implicit cast is adding const.
383
unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
384
hasUnaryOperand(canResolveToExpr(Exp)));
385
const auto AsPointerFromArrayDecay = castExpr(
386
hasCastKind(CK_ArrayToPointerDecay),
387
unless(hasParent(arraySubscriptExpr())), has(canResolveToExpr(Exp)));
388
// Treat calling `operator->()` of move-only classes as taking address.
389
// These are typically smart pointers with unique ownership so we treat
390
// mutation of pointee as mutation of the smart pointer itself.
391
const auto AsOperatorArrowThis = cxxOperatorCallExpr(
392
hasOverloadedOperatorName("->"),
393
callee(
394
cxxMethodDecl(ofClass(isMoveOnly()), returns(nonConstPointerType()))),
395
argumentCountIs(1), hasArgument(0, canResolveToExpr(Exp)));
396
397
// Used as non-const-ref argument when calling a function.
398
// An argument is assumed to be non-const-ref when the function is unresolved.
399
// Instantiated template functions are not handled here but in
400
// findFunctionArgMutation which has additional smarts for handling forwarding
401
// references.
402
const auto NonConstRefParam = forEachArgumentWithParamType(
403
anyOf(canResolveToExpr(Exp),
404
memberExpr(hasObjectExpression(canResolveToExpr(Exp)))),
405
nonConstReferenceType());
406
const auto NotInstantiated = unless(hasDeclaration(isInstantiated()));
407
408
const auto AsNonConstRefArg =
409
anyOf(callExpr(NonConstRefParam, NotInstantiated),
410
cxxConstructExpr(NonConstRefParam, NotInstantiated),
411
// If the call is type-dependent, we can't properly process any
412
// argument because required type conversions and implicit casts
413
// will be inserted only after specialization.
414
callExpr(isTypeDependent(), hasAnyArgument(canResolveToExpr(Exp))),
415
cxxUnresolvedConstructExpr(hasAnyArgument(canResolveToExpr(Exp))),
416
// Previous False Positive in the following Code:
417
// `template <typename T> void f() { int i = 42; new Type<T>(i); }`
418
// Where the constructor of `Type` takes its argument as reference.
419
// The AST does not resolve in a `cxxConstructExpr` because it is
420
// type-dependent.
421
parenListExpr(hasDescendant(expr(canResolveToExpr(Exp)))),
422
// If the initializer is for a reference type, there is no cast for
423
// the variable. Values are cast to RValue first.
424
initListExpr(hasAnyInit(expr(canResolveToExpr(Exp)))));
425
426
// Captured by a lambda by reference.
427
// If we're initializing a capture with 'Exp' directly then we're initializing
428
// a reference capture.
429
// For value captures there will be an ImplicitCastExpr <LValueToRValue>.
430
const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));
431
432
// Returned as non-const-ref.
433
// If we're returning 'Exp' directly then it's returned as non-const-ref.
434
// For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
435
// For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
436
// adding const.)
437
const auto AsNonConstRefReturn =
438
returnStmt(hasReturnValue(canResolveToExpr(Exp)));
439
440
// It is used as a non-const-reference for initializing a range-for loop.
441
const auto AsNonConstRefRangeInit = cxxForRangeStmt(hasRangeInit(declRefExpr(
442
allOf(canResolveToExpr(Exp), hasType(nonConstReferenceType())))));
443
444
const auto Matches = match(
445
traverse(
446
TK_AsIs,
447
findFirst(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
448
AsAmpersandOperand, AsPointerFromArrayDecay,
449
AsOperatorArrowThis, AsNonConstRefArg,
450
AsLambdaRefCaptureInit, AsNonConstRefReturn,
451
AsNonConstRefRangeInit))
452
.bind("stmt"))),
453
Stm, Context);
454
return selectFirst<Stmt>("stmt", Matches);
455
}
456
457
const Stmt *
458
ExprMutationAnalyzer::Analyzer::findMemberMutation(const Expr *Exp) {
459
// Check whether any member of 'Exp' is mutated.
460
const auto MemberExprs = match(
461
findAll(expr(anyOf(memberExpr(hasObjectExpression(canResolveToExpr(Exp))),
462
cxxDependentScopeMemberExpr(
463
hasObjectExpression(canResolveToExpr(Exp))),
464
binaryOperator(hasOperatorName(".*"),
465
hasLHS(equalsNode(Exp)))))
466
.bind(NodeID<Expr>::value)),
467
Stm, Context);
468
return findExprMutation(MemberExprs);
469
}
470
471
const Stmt *
472
ExprMutationAnalyzer::Analyzer::findArrayElementMutation(const Expr *Exp) {
473
// Check whether any element of an array is mutated.
474
const auto SubscriptExprs = match(
475
findAll(arraySubscriptExpr(
476
anyOf(hasBase(canResolveToExpr(Exp)),
477
hasBase(implicitCastExpr(allOf(
478
hasCastKind(CK_ArrayToPointerDecay),
479
hasSourceExpression(canResolveToExpr(Exp)))))))
480
.bind(NodeID<Expr>::value)),
481
Stm, Context);
482
return findExprMutation(SubscriptExprs);
483
}
484
485
const Stmt *ExprMutationAnalyzer::Analyzer::findCastMutation(const Expr *Exp) {
486
// If the 'Exp' is explicitly casted to a non-const reference type the
487
// 'Exp' is considered to be modified.
488
const auto ExplicitCast =
489
match(findFirst(stmt(castExpr(hasSourceExpression(canResolveToExpr(Exp)),
490
explicitCastExpr(hasDestinationType(
491
nonConstReferenceType()))))
492
.bind("stmt")),
493
Stm, Context);
494
495
if (const auto *CastStmt = selectFirst<Stmt>("stmt", ExplicitCast))
496
return CastStmt;
497
498
// If 'Exp' is casted to any non-const reference type, check the castExpr.
499
const auto Casts = match(
500
findAll(expr(castExpr(hasSourceExpression(canResolveToExpr(Exp)),
501
anyOf(explicitCastExpr(hasDestinationType(
502
nonConstReferenceType())),
503
implicitCastExpr(hasImplicitDestinationType(
504
nonConstReferenceType())))))
505
.bind(NodeID<Expr>::value)),
506
Stm, Context);
507
508
if (const Stmt *S = findExprMutation(Casts))
509
return S;
510
// Treat std::{move,forward} as cast.
511
const auto Calls =
512
match(findAll(callExpr(callee(namedDecl(
513
hasAnyName("::std::move", "::std::forward"))),
514
hasArgument(0, canResolveToExpr(Exp)))
515
.bind("expr")),
516
Stm, Context);
517
return findExprMutation(Calls);
518
}
519
520
const Stmt *
521
ExprMutationAnalyzer::Analyzer::findRangeLoopMutation(const Expr *Exp) {
522
// Keep the ordering for the specific initialization matches to happen first,
523
// because it is cheaper to match all potential modifications of the loop
524
// variable.
525
526
// The range variable is a reference to a builtin array. In that case the
527
// array is considered modified if the loop-variable is a non-const reference.
528
const auto DeclStmtToNonRefToArray = declStmt(hasSingleDecl(varDecl(hasType(
529
hasUnqualifiedDesugaredType(referenceType(pointee(arrayType())))))));
530
const auto RefToArrayRefToElements = match(
531
findFirst(stmt(cxxForRangeStmt(
532
hasLoopVariable(
533
varDecl(anyOf(hasType(nonConstReferenceType()),
534
hasType(nonConstPointerType())))
535
.bind(NodeID<Decl>::value)),
536
hasRangeStmt(DeclStmtToNonRefToArray),
537
hasRangeInit(canResolveToExpr(Exp))))
538
.bind("stmt")),
539
Stm, Context);
540
541
if (const auto *BadRangeInitFromArray =
542
selectFirst<Stmt>("stmt", RefToArrayRefToElements))
543
return BadRangeInitFromArray;
544
545
// Small helper to match special cases in range-for loops.
546
//
547
// It is possible that containers do not provide a const-overload for their
548
// iterator accessors. If this is the case, the variable is used non-const
549
// no matter what happens in the loop. This requires special detection as it
550
// is then faster to find all mutations of the loop variable.
551
// It aims at a different modification as well.
552
const auto HasAnyNonConstIterator =
553
anyOf(allOf(hasMethod(allOf(hasName("begin"), unless(isConst()))),
554
unless(hasMethod(allOf(hasName("begin"), isConst())))),
555
allOf(hasMethod(allOf(hasName("end"), unless(isConst()))),
556
unless(hasMethod(allOf(hasName("end"), isConst())))));
557
558
const auto DeclStmtToNonConstIteratorContainer = declStmt(
559
hasSingleDecl(varDecl(hasType(hasUnqualifiedDesugaredType(referenceType(
560
pointee(hasDeclaration(cxxRecordDecl(HasAnyNonConstIterator)))))))));
561
562
const auto RefToContainerBadIterators = match(
563
findFirst(stmt(cxxForRangeStmt(allOf(
564
hasRangeStmt(DeclStmtToNonConstIteratorContainer),
565
hasRangeInit(canResolveToExpr(Exp)))))
566
.bind("stmt")),
567
Stm, Context);
568
569
if (const auto *BadIteratorsContainer =
570
selectFirst<Stmt>("stmt", RefToContainerBadIterators))
571
return BadIteratorsContainer;
572
573
// If range for looping over 'Exp' with a non-const reference loop variable,
574
// check all declRefExpr of the loop variable.
575
const auto LoopVars =
576
match(findAll(cxxForRangeStmt(
577
hasLoopVariable(varDecl(hasType(nonConstReferenceType()))
578
.bind(NodeID<Decl>::value)),
579
hasRangeInit(canResolveToExpr(Exp)))),
580
Stm, Context);
581
return findDeclMutation(LoopVars);
582
}
583
584
const Stmt *
585
ExprMutationAnalyzer::Analyzer::findReferenceMutation(const Expr *Exp) {
586
// Follow non-const reference returned by `operator*()` of move-only classes.
587
// These are typically smart pointers with unique ownership so we treat
588
// mutation of pointee as mutation of the smart pointer itself.
589
const auto Ref = match(
590
findAll(cxxOperatorCallExpr(
591
hasOverloadedOperatorName("*"),
592
callee(cxxMethodDecl(ofClass(isMoveOnly()),
593
returns(nonConstReferenceType()))),
594
argumentCountIs(1), hasArgument(0, canResolveToExpr(Exp)))
595
.bind(NodeID<Expr>::value)),
596
Stm, Context);
597
if (const Stmt *S = findExprMutation(Ref))
598
return S;
599
600
// If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
601
const auto Refs = match(
602
stmt(forEachDescendant(
603
varDecl(hasType(nonConstReferenceType()),
604
hasInitializer(anyOf(
605
canResolveToExpr(Exp),
606
memberExpr(hasObjectExpression(canResolveToExpr(Exp))))),
607
hasParent(declStmt().bind("stmt")),
608
// Don't follow the reference in range statement, we've
609
// handled that separately.
610
unless(hasParent(declStmt(hasParent(cxxForRangeStmt(
611
hasRangeStmt(equalsBoundNode("stmt"))))))))
612
.bind(NodeID<Decl>::value))),
613
Stm, Context);
614
return findDeclMutation(Refs);
615
}
616
617
const Stmt *
618
ExprMutationAnalyzer::Analyzer::findFunctionArgMutation(const Expr *Exp) {
619
const auto NonConstRefParam = forEachArgumentWithParam(
620
canResolveToExpr(Exp),
621
parmVarDecl(hasType(nonConstReferenceType())).bind("parm"));
622
const auto IsInstantiated = hasDeclaration(isInstantiated());
623
const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));
624
const auto Matches = match(
625
traverse(
626
TK_AsIs,
627
findAll(
628
expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl,
629
unless(callee(namedDecl(hasAnyName(
630
"::std::move", "::std::forward"))))),
631
cxxConstructExpr(NonConstRefParam, IsInstantiated,
632
FuncDecl)))
633
.bind(NodeID<Expr>::value))),
634
Stm, Context);
635
for (const auto &Nodes : Matches) {
636
const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value);
637
const auto *Func = Nodes.getNodeAs<FunctionDecl>("func");
638
if (!Func->getBody() || !Func->getPrimaryTemplate())
639
return Exp;
640
641
const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm");
642
const ArrayRef<ParmVarDecl *> AllParams =
643
Func->getPrimaryTemplate()->getTemplatedDecl()->parameters();
644
QualType ParmType =
645
AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(),
646
AllParams.size() - 1)]
647
->getType();
648
if (const auto *T = ParmType->getAs<PackExpansionType>())
649
ParmType = T->getPattern();
650
651
// If param type is forwarding reference, follow into the function
652
// definition and see whether the param is mutated inside.
653
if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) {
654
if (!RefType->getPointeeType().getQualifiers() &&
655
RefType->getPointeeType()->getAs<TemplateTypeParmType>()) {
656
FunctionParmMutationAnalyzer *Analyzer =
657
FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(
658
*Func, Context, Memorized);
659
if (Analyzer->findMutation(Parm))
660
return Exp;
661
continue;
662
}
663
}
664
// Not forwarding reference.
665
return Exp;
666
}
667
return nullptr;
668
}
669
670
FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(
671
const FunctionDecl &Func, ASTContext &Context,
672
ExprMutationAnalyzer::Memoized &Memorized)
673
: BodyAnalyzer(*Func.getBody(), Context, Memorized) {
674
if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) {
675
// CXXCtorInitializer might also mutate Param but they're not part of
676
// function body, check them eagerly here since they're typically trivial.
677
for (const CXXCtorInitializer *Init : Ctor->inits()) {
678
ExprMutationAnalyzer::Analyzer InitAnalyzer(*Init->getInit(), Context,
679
Memorized);
680
for (const ParmVarDecl *Parm : Ctor->parameters()) {
681
if (Results.contains(Parm))
682
continue;
683
if (const Stmt *S = InitAnalyzer.findMutation(Parm))
684
Results[Parm] = S;
685
}
686
}
687
}
688
}
689
690
const Stmt *
691
FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) {
692
const auto Memoized = Results.find(Parm);
693
if (Memoized != Results.end())
694
return Memoized->second;
695
// To handle call A -> call B -> call A. Assume parameters of A is not mutated
696
// before analyzing parameters of A. Then when analyzing the second "call A",
697
// FunctionParmMutationAnalyzer can use this memoized value to avoid infinite
698
// recursion.
699
Results[Parm] = nullptr;
700
if (const Stmt *S = BodyAnalyzer.findMutation(Parm))
701
return Results[Parm] = S;
702
return Results[Parm];
703
}
704
705
} // namespace clang
706
707