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