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