1 //===---------- ExprMutationAnalyzer.cpp ----------------------------------===//
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
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"
16 using namespace ast_matchers
;
20 AST_MATCHER_P(LambdaExpr
, hasCaptureInit
, const Expr
*, E
) {
21 return llvm::is_contained(Node
.capture_inits(), E
);
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
);
30 AST_MATCHER_P(Expr
, maybeEvalCommaExpr
, ast_matchers::internal::Matcher
<Expr
>,
32 const Expr
*Result
= &Node
;
33 while (const auto *BOComma
=
34 dyn_cast_or_null
<BinaryOperator
>(Result
->IgnoreParens())) {
35 if (!BOComma
->isCommaOp())
37 Result
= BOComma
->getRHS();
39 return InnerMatcher
.matches(*Result
, Finder
, Builder
);
42 AST_MATCHER_P(Stmt
, canResolveToExpr
, ast_matchers::internal::Matcher
<Stmt
>,
44 auto *Exp
= dyn_cast
<Expr
>(&Node
);
46 return stmt().matches(Node
, Finder
, Builder
);
49 auto DerivedToBase
= [](const ast_matchers::internal::Matcher
<Expr
> &Inner
) {
50 return implicitCastExpr(anyOf(hasCastKind(CK_DerivedToBase
),
51 hasCastKind(CK_UncheckedDerivedToBase
)),
52 hasSourceExpression(Inner
));
54 auto IgnoreDerivedToBase
=
55 [&DerivedToBase
](const ast_matchers::internal::Matcher
<Expr
> &Inner
) {
56 return ignoringParens(expr(anyOf(Inner
, DerivedToBase(Inner
))));
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))`
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
)))));
73 auto const ComplexMatcher
= ignoringParens(
74 expr(anyOf(IgnoreDerivedToBase(InnerMatcher
),
75 maybeEvalCommaExpr(IgnoreDerivedToBase(InnerMatcher
)),
76 IgnoreDerivedToBase(ConditionalOperator
),
77 IgnoreDerivedToBase(ElvisOperator
))));
79 return ComplexMatcher
.matches(*Exp
, Finder
, Builder
);
82 // Similar to 'hasAnyArgument', but does not work because 'InitListExpr' does
83 // not have the 'arguments()' method.
84 AST_MATCHER_P(InitListExpr
, hasAnyInit
, ast_matchers::internal::Matcher
<Expr
>,
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
);
96 const ast_matchers::internal::VariadicDynCastAllOfMatcher
<Stmt
, CXXTypeidExpr
>
99 AST_MATCHER(CXXTypeidExpr
, isPotentiallyEvaluated
) {
100 return Node
.isPotentiallyEvaluated();
103 AST_MATCHER(CXXMemberCallExpr
, isConstCallee
) {
104 const Decl
*CalleeDecl
= Node
.getCalleeDecl();
105 const auto *VD
= dyn_cast_or_null
<ValueDecl
>(CalleeDecl
);
108 const QualType T
= VD
->getType().getCanonicalType();
109 const auto *MPT
= dyn_cast
<MemberPointerType
>(T
);
110 const auto *FPT
= MPT
? cast
<FunctionProtoType
>(MPT
->getPointeeType())
111 : dyn_cast
<FunctionProtoType
>(T
);
114 return FPT
->isConst();
117 AST_MATCHER_P(GenericSelectionExpr
, hasControllingExpr
,
118 ast_matchers::internal::Matcher
<Expr
>, InnerMatcher
) {
119 if (Node
.isTypePredicate())
121 return InnerMatcher
.matches(*Node
.getControllingExpr(), Finder
, Builder
);
124 const auto nonConstReferenceType
= [] {
125 return hasUnqualifiedDesugaredType(
126 referenceType(pointee(unless(isConstQualified()))));
129 const auto nonConstPointerType
= [] {
130 return hasUnqualifiedDesugaredType(
131 pointerType(pointee(unless(isConstQualified()))));
134 const auto isMoveOnly
= [] {
135 return cxxRecordDecl(
136 hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))),
137 hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))),
138 unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(),
139 unless(isDeleted()))),
140 hasMethod(cxxMethodDecl(isCopyAssignmentOperator(),
141 unless(isDeleted()))))));
144 template <class T
> struct NodeID
;
145 template <> struct NodeID
<Expr
> { static constexpr StringRef value
= "expr"; };
146 template <> struct NodeID
<Decl
> { static constexpr StringRef value
= "decl"; };
147 constexpr StringRef NodeID
<Expr
>::value
;
148 constexpr StringRef NodeID
<Decl
>::value
;
150 template <class T
, class F
= const Stmt
*(ExprMutationAnalyzer::*)(const T
*)>
151 const Stmt
*tryEachMatch(ArrayRef
<ast_matchers::BoundNodes
> Matches
,
152 ExprMutationAnalyzer
*Analyzer
, F Finder
) {
153 const StringRef ID
= NodeID
<T
>::value
;
154 for (const auto &Nodes
: Matches
) {
155 if (const Stmt
*S
= (Analyzer
->*Finder
)(Nodes
.getNodeAs
<T
>(ID
)))
163 const Stmt
*ExprMutationAnalyzer::findMutation(const Expr
*Exp
) {
164 return findMutationMemoized(Exp
,
165 {&ExprMutationAnalyzer::findDirectMutation
,
166 &ExprMutationAnalyzer::findMemberMutation
,
167 &ExprMutationAnalyzer::findArrayElementMutation
,
168 &ExprMutationAnalyzer::findCastMutation
,
169 &ExprMutationAnalyzer::findRangeLoopMutation
,
170 &ExprMutationAnalyzer::findReferenceMutation
,
171 &ExprMutationAnalyzer::findFunctionArgMutation
},
175 const Stmt
*ExprMutationAnalyzer::findMutation(const Decl
*Dec
) {
176 return tryEachDeclRef(Dec
, &ExprMutationAnalyzer::findMutation
);
179 const Stmt
*ExprMutationAnalyzer::findPointeeMutation(const Expr
*Exp
) {
180 return findMutationMemoized(Exp
, {/*TODO*/}, PointeeResults
);
183 const Stmt
*ExprMutationAnalyzer::findPointeeMutation(const Decl
*Dec
) {
184 return tryEachDeclRef(Dec
, &ExprMutationAnalyzer::findPointeeMutation
);
187 const Stmt
*ExprMutationAnalyzer::findMutationMemoized(
188 const Expr
*Exp
, llvm::ArrayRef
<MutationFinder
> Finders
,
189 ResultMap
&MemoizedResults
) {
190 const auto Memoized
= MemoizedResults
.find(Exp
);
191 if (Memoized
!= MemoizedResults
.end())
192 return Memoized
->second
;
194 if (isUnevaluated(Exp
))
195 return MemoizedResults
[Exp
] = nullptr;
197 for (const auto &Finder
: Finders
) {
198 if (const Stmt
*S
= (this->*Finder
)(Exp
))
199 return MemoizedResults
[Exp
] = S
;
202 return MemoizedResults
[Exp
] = nullptr;
205 const Stmt
*ExprMutationAnalyzer::tryEachDeclRef(const Decl
*Dec
,
206 MutationFinder Finder
) {
208 match(findAll(declRefExpr(to(equalsNode(Dec
))).bind(NodeID
<Expr
>::value
)),
210 for (const auto &RefNodes
: Refs
) {
211 const auto *E
= RefNodes
.getNodeAs
<Expr
>(NodeID
<Expr
>::value
);
212 if ((this->*Finder
)(E
))
218 bool ExprMutationAnalyzer::isUnevaluated(const Stmt
*Exp
, const Stmt
&Stm
,
219 ASTContext
&Context
) {
220 return selectFirst
<Stmt
>(
224 stmt(canResolveToExpr(equalsNode(Exp
)),
226 // `Exp` is part of the underlying expression of
227 // decltype/typeof if it has an ancestor of
229 hasAncestor(typeLoc(unless(
230 hasAncestor(unaryExprOrTypeTraitExpr())))),
231 hasAncestor(expr(anyOf(
232 // `UnaryExprOrTypeTraitExpr` is unevaluated
233 // unless it's sizeof on VLA.
234 unaryExprOrTypeTraitExpr(unless(sizeOfExpr(
235 hasArgumentOfType(variableArrayType())))),
236 // `CXXTypeidExpr` is unevaluated unless it's
237 // applied to an expression of glvalue of
238 // polymorphic class type.
240 unless(isPotentiallyEvaluated())),
241 // The controlling expression of
242 // `GenericSelectionExpr` is unevaluated.
243 genericSelectionExpr(hasControllingExpr(
244 hasDescendant(equalsNode(Exp
)))),
245 cxxNoexceptExpr())))))
246 .bind(NodeID
<Expr
>::value
)),
247 Stm
, Context
)) != nullptr;
250 bool ExprMutationAnalyzer::isUnevaluated(const Expr
*Exp
) {
251 return isUnevaluated(Exp
, Stm
, Context
);
255 ExprMutationAnalyzer::findExprMutation(ArrayRef
<BoundNodes
> Matches
) {
256 return tryEachMatch
<Expr
>(Matches
, this, &ExprMutationAnalyzer::findMutation
);
260 ExprMutationAnalyzer::findDeclMutation(ArrayRef
<BoundNodes
> Matches
) {
261 return tryEachMatch
<Decl
>(Matches
, this, &ExprMutationAnalyzer::findMutation
);
264 const Stmt
*ExprMutationAnalyzer::findExprPointeeMutation(
265 ArrayRef
<ast_matchers::BoundNodes
> Matches
) {
266 return tryEachMatch
<Expr
>(Matches
, this,
267 &ExprMutationAnalyzer::findPointeeMutation
);
270 const Stmt
*ExprMutationAnalyzer::findDeclPointeeMutation(
271 ArrayRef
<ast_matchers::BoundNodes
> Matches
) {
272 return tryEachMatch
<Decl
>(Matches
, this,
273 &ExprMutationAnalyzer::findPointeeMutation
);
276 const Stmt
*ExprMutationAnalyzer::findDirectMutation(const Expr
*Exp
) {
277 // LHS of any assignment operators.
278 const auto AsAssignmentLhs
= binaryOperator(
279 isAssignmentOperator(), hasLHS(canResolveToExpr(equalsNode(Exp
))));
281 // Operand of increment/decrement operators.
282 const auto AsIncDecOperand
=
283 unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
284 hasUnaryOperand(canResolveToExpr(equalsNode(Exp
))));
286 // Invoking non-const member function.
287 // A member function is assumed to be non-const when it is unresolved.
288 const auto NonConstMethod
= cxxMethodDecl(unless(isConst()));
290 const auto AsNonConstThis
= expr(anyOf(
291 cxxMemberCallExpr(on(canResolveToExpr(equalsNode(Exp
))),
292 unless(isConstCallee())),
293 cxxOperatorCallExpr(callee(NonConstMethod
),
294 hasArgument(0, canResolveToExpr(equalsNode(Exp
)))),
295 // In case of a templated type, calling overloaded operators is not
296 // resolved and modelled as `binaryOperator` on a dependent type.
297 // Such instances are considered a modification, because they can modify
298 // in different instantiations of the template.
299 binaryOperator(hasEitherOperand(
300 allOf(ignoringImpCasts(canResolveToExpr(equalsNode(Exp
))),
301 isTypeDependent()))),
302 // Within class templates and member functions the member expression might
303 // not be resolved. In that case, the `callExpr` is considered to be a
306 callee(expr(anyOf(unresolvedMemberExpr(hasObjectExpression(
307 canResolveToExpr(equalsNode(Exp
)))),
308 cxxDependentScopeMemberExpr(hasObjectExpression(
309 canResolveToExpr(equalsNode(Exp
)))))))),
310 // Match on a call to a known method, but the call itself is type
311 // dependent (e.g. `vector<T> v; v.push(T{});` in a templated function).
312 callExpr(allOf(isTypeDependent(),
313 callee(memberExpr(hasDeclaration(NonConstMethod
),
314 hasObjectExpression(canResolveToExpr(
315 equalsNode(Exp
)))))))));
317 // Taking address of 'Exp'.
318 // We're assuming 'Exp' is mutated as soon as its address is taken, though in
319 // theory we can follow the pointer and see whether it escaped `Stm` or is
320 // dereferenced and then mutated. This is left for future improvements.
321 const auto AsAmpersandOperand
=
322 unaryOperator(hasOperatorName("&"),
323 // A NoOp implicit cast is adding const.
324 unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp
)))),
325 hasUnaryOperand(canResolveToExpr(equalsNode(Exp
))));
326 const auto AsPointerFromArrayDecay
=
327 castExpr(hasCastKind(CK_ArrayToPointerDecay
),
328 unless(hasParent(arraySubscriptExpr())),
329 has(canResolveToExpr(equalsNode(Exp
))));
330 // Treat calling `operator->()` of move-only classes as taking address.
331 // These are typically smart pointers with unique ownership so we treat
332 // mutation of pointee as mutation of the smart pointer itself.
333 const auto AsOperatorArrowThis
= cxxOperatorCallExpr(
334 hasOverloadedOperatorName("->"),
336 cxxMethodDecl(ofClass(isMoveOnly()), returns(nonConstPointerType()))),
337 argumentCountIs(1), hasArgument(0, canResolveToExpr(equalsNode(Exp
))));
339 // Used as non-const-ref argument when calling a function.
340 // An argument is assumed to be non-const-ref when the function is unresolved.
341 // Instantiated template functions are not handled here but in
342 // findFunctionArgMutation which has additional smarts for handling forwarding
344 const auto NonConstRefParam
= forEachArgumentWithParamType(
345 anyOf(canResolveToExpr(equalsNode(Exp
)),
346 memberExpr(hasObjectExpression(canResolveToExpr(equalsNode(Exp
))))),
347 nonConstReferenceType());
348 const auto NotInstantiated
= unless(hasDeclaration(isInstantiated()));
349 const auto TypeDependentCallee
=
350 callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(),
351 cxxDependentScopeMemberExpr(),
352 hasType(templateTypeParmType()), isTypeDependent())));
354 const auto AsNonConstRefArg
= anyOf(
355 callExpr(NonConstRefParam
, NotInstantiated
),
356 cxxConstructExpr(NonConstRefParam
, NotInstantiated
),
357 callExpr(TypeDependentCallee
,
358 hasAnyArgument(canResolveToExpr(equalsNode(Exp
)))),
359 cxxUnresolvedConstructExpr(
360 hasAnyArgument(canResolveToExpr(equalsNode(Exp
)))),
361 // Previous False Positive in the following Code:
362 // `template <typename T> void f() { int i = 42; new Type<T>(i); }`
363 // Where the constructor of `Type` takes its argument as reference.
364 // The AST does not resolve in a `cxxConstructExpr` because it is
366 parenListExpr(hasDescendant(expr(canResolveToExpr(equalsNode(Exp
))))),
367 // If the initializer is for a reference type, there is no cast for
368 // the variable. Values are cast to RValue first.
369 initListExpr(hasAnyInit(expr(canResolveToExpr(equalsNode(Exp
))))));
371 // Captured by a lambda by reference.
372 // If we're initializing a capture with 'Exp' directly then we're initializing
373 // a reference capture.
374 // For value captures there will be an ImplicitCastExpr <LValueToRValue>.
375 const auto AsLambdaRefCaptureInit
= lambdaExpr(hasCaptureInit(Exp
));
377 // Returned as non-const-ref.
378 // If we're returning 'Exp' directly then it's returned as non-const-ref.
379 // For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
380 // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
382 const auto AsNonConstRefReturn
=
383 returnStmt(hasReturnValue(canResolveToExpr(equalsNode(Exp
))));
385 // It is used as a non-const-reference for initalizing a range-for loop.
386 const auto AsNonConstRefRangeInit
= cxxForRangeStmt(
387 hasRangeInit(declRefExpr(allOf(canResolveToExpr(equalsNode(Exp
)),
388 hasType(nonConstReferenceType())))));
390 const auto Matches
= match(
392 findAll(stmt(anyOf(AsAssignmentLhs
, AsIncDecOperand
,
393 AsNonConstThis
, AsAmpersandOperand
,
394 AsPointerFromArrayDecay
, AsOperatorArrowThis
,
395 AsNonConstRefArg
, AsLambdaRefCaptureInit
,
396 AsNonConstRefReturn
, AsNonConstRefRangeInit
))
399 return selectFirst
<Stmt
>("stmt", Matches
);
402 const Stmt
*ExprMutationAnalyzer::findMemberMutation(const Expr
*Exp
) {
403 // Check whether any member of 'Exp' is mutated.
404 const auto MemberExprs
=
405 match(findAll(expr(anyOf(memberExpr(hasObjectExpression(
406 canResolveToExpr(equalsNode(Exp
)))),
407 cxxDependentScopeMemberExpr(hasObjectExpression(
408 canResolveToExpr(equalsNode(Exp
)))),
409 binaryOperator(hasOperatorName(".*"),
410 hasLHS(equalsNode(Exp
)))))
411 .bind(NodeID
<Expr
>::value
)),
413 return findExprMutation(MemberExprs
);
416 const Stmt
*ExprMutationAnalyzer::findArrayElementMutation(const Expr
*Exp
) {
417 // Check whether any element of an array is mutated.
418 const auto SubscriptExprs
=
419 match(findAll(arraySubscriptExpr(
420 anyOf(hasBase(canResolveToExpr(equalsNode(Exp
))),
421 hasBase(implicitCastExpr(
422 allOf(hasCastKind(CK_ArrayToPointerDecay
),
423 hasSourceExpression(canResolveToExpr(
424 equalsNode(Exp
))))))))
425 .bind(NodeID
<Expr
>::value
)),
427 return findExprMutation(SubscriptExprs
);
430 const Stmt
*ExprMutationAnalyzer::findCastMutation(const Expr
*Exp
) {
431 // If the 'Exp' is explicitly casted to a non-const reference type the
432 // 'Exp' is considered to be modified.
433 const auto ExplicitCast
= match(
435 stmt(castExpr(hasSourceExpression(canResolveToExpr(equalsNode(Exp
))),
437 hasDestinationType(nonConstReferenceType()))))
441 if (const auto *CastStmt
= selectFirst
<Stmt
>("stmt", ExplicitCast
))
444 // If 'Exp' is casted to any non-const reference type, check the castExpr.
445 const auto Casts
= match(
447 expr(castExpr(hasSourceExpression(canResolveToExpr(equalsNode(Exp
))),
448 anyOf(explicitCastExpr(
449 hasDestinationType(nonConstReferenceType())),
450 implicitCastExpr(hasImplicitDestinationType(
451 nonConstReferenceType())))))
452 .bind(NodeID
<Expr
>::value
)),
455 if (const Stmt
*S
= findExprMutation(Casts
))
457 // Treat std::{move,forward} as cast.
459 match(findAll(callExpr(callee(namedDecl(
460 hasAnyName("::std::move", "::std::forward"))),
461 hasArgument(0, canResolveToExpr(equalsNode(Exp
))))
464 return findExprMutation(Calls
);
467 const Stmt
*ExprMutationAnalyzer::findRangeLoopMutation(const Expr
*Exp
) {
468 // Keep the ordering for the specific initialization matches to happen first,
469 // because it is cheaper to match all potential modifications of the loop
472 // The range variable is a reference to a builtin array. In that case the
473 // array is considered modified if the loop-variable is a non-const reference.
474 const auto DeclStmtToNonRefToArray
= declStmt(hasSingleDecl(varDecl(hasType(
475 hasUnqualifiedDesugaredType(referenceType(pointee(arrayType())))))));
476 const auto RefToArrayRefToElements
=
477 match(findAll(stmt(cxxForRangeStmt(
479 varDecl(anyOf(hasType(nonConstReferenceType()),
480 hasType(nonConstPointerType())))
481 .bind(NodeID
<Decl
>::value
)),
482 hasRangeStmt(DeclStmtToNonRefToArray
),
483 hasRangeInit(canResolveToExpr(equalsNode(Exp
)))))
487 if (const auto *BadRangeInitFromArray
=
488 selectFirst
<Stmt
>("stmt", RefToArrayRefToElements
))
489 return BadRangeInitFromArray
;
491 // Small helper to match special cases in range-for loops.
493 // It is possible that containers do not provide a const-overload for their
494 // iterator accessors. If this is the case, the variable is used non-const
495 // no matter what happens in the loop. This requires special detection as it
496 // is then faster to find all mutations of the loop variable.
497 // It aims at a different modification as well.
498 const auto HasAnyNonConstIterator
=
499 anyOf(allOf(hasMethod(allOf(hasName("begin"), unless(isConst()))),
500 unless(hasMethod(allOf(hasName("begin"), isConst())))),
501 allOf(hasMethod(allOf(hasName("end"), unless(isConst()))),
502 unless(hasMethod(allOf(hasName("end"), isConst())))));
504 const auto DeclStmtToNonConstIteratorContainer
= declStmt(
505 hasSingleDecl(varDecl(hasType(hasUnqualifiedDesugaredType(referenceType(
506 pointee(hasDeclaration(cxxRecordDecl(HasAnyNonConstIterator
)))))))));
508 const auto RefToContainerBadIterators
=
509 match(findAll(stmt(cxxForRangeStmt(allOf(
510 hasRangeStmt(DeclStmtToNonConstIteratorContainer
),
511 hasRangeInit(canResolveToExpr(equalsNode(Exp
))))))
515 if (const auto *BadIteratorsContainer
=
516 selectFirst
<Stmt
>("stmt", RefToContainerBadIterators
))
517 return BadIteratorsContainer
;
519 // If range for looping over 'Exp' with a non-const reference loop variable,
520 // check all declRefExpr of the loop variable.
521 const auto LoopVars
=
522 match(findAll(cxxForRangeStmt(
523 hasLoopVariable(varDecl(hasType(nonConstReferenceType()))
524 .bind(NodeID
<Decl
>::value
)),
525 hasRangeInit(canResolveToExpr(equalsNode(Exp
))))),
527 return findDeclMutation(LoopVars
);
530 const Stmt
*ExprMutationAnalyzer::findReferenceMutation(const Expr
*Exp
) {
531 // Follow non-const reference returned by `operator*()` of move-only classes.
532 // These are typically smart pointers with unique ownership so we treat
533 // mutation of pointee as mutation of the smart pointer itself.
535 match(findAll(cxxOperatorCallExpr(
536 hasOverloadedOperatorName("*"),
537 callee(cxxMethodDecl(ofClass(isMoveOnly()),
538 returns(nonConstReferenceType()))),
540 hasArgument(0, canResolveToExpr(equalsNode(Exp
))))
541 .bind(NodeID
<Expr
>::value
)),
543 if (const Stmt
*S
= findExprMutation(Ref
))
546 // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
547 const auto Refs
= match(
548 stmt(forEachDescendant(
550 hasType(nonConstReferenceType()),
551 hasInitializer(anyOf(canResolveToExpr(equalsNode(Exp
)),
552 memberExpr(hasObjectExpression(
553 canResolveToExpr(equalsNode(Exp
)))))),
554 hasParent(declStmt().bind("stmt")),
555 // Don't follow the reference in range statement, we've
556 // handled that separately.
557 unless(hasParent(declStmt(hasParent(
558 cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt"))))))))
559 .bind(NodeID
<Decl
>::value
))),
561 return findDeclMutation(Refs
);
564 const Stmt
*ExprMutationAnalyzer::findFunctionArgMutation(const Expr
*Exp
) {
565 const auto NonConstRefParam
= forEachArgumentWithParam(
566 canResolveToExpr(equalsNode(Exp
)),
567 parmVarDecl(hasType(nonConstReferenceType())).bind("parm"));
568 const auto IsInstantiated
= hasDeclaration(isInstantiated());
569 const auto FuncDecl
= hasDeclaration(functionDecl().bind("func"));
570 const auto Matches
= match(
574 expr(anyOf(callExpr(NonConstRefParam
, IsInstantiated
, FuncDecl
,
575 unless(callee(namedDecl(hasAnyName(
576 "::std::move", "::std::forward"))))),
577 cxxConstructExpr(NonConstRefParam
, IsInstantiated
,
579 .bind(NodeID
<Expr
>::value
))),
581 for (const auto &Nodes
: Matches
) {
582 const auto *Exp
= Nodes
.getNodeAs
<Expr
>(NodeID
<Expr
>::value
);
583 const auto *Func
= Nodes
.getNodeAs
<FunctionDecl
>("func");
584 if (!Func
->getBody() || !Func
->getPrimaryTemplate())
587 const auto *Parm
= Nodes
.getNodeAs
<ParmVarDecl
>("parm");
588 const ArrayRef
<ParmVarDecl
*> AllParams
=
589 Func
->getPrimaryTemplate()->getTemplatedDecl()->parameters();
591 AllParams
[std::min
<size_t>(Parm
->getFunctionScopeIndex(),
592 AllParams
.size() - 1)]
594 if (const auto *T
= ParmType
->getAs
<PackExpansionType
>())
595 ParmType
= T
->getPattern();
597 // If param type is forwarding reference, follow into the function
598 // definition and see whether the param is mutated inside.
599 if (const auto *RefType
= ParmType
->getAs
<RValueReferenceType
>()) {
600 if (!RefType
->getPointeeType().getQualifiers() &&
601 RefType
->getPointeeType()->getAs
<TemplateTypeParmType
>()) {
602 std::unique_ptr
<FunctionParmMutationAnalyzer
> &Analyzer
=
603 FuncParmAnalyzer
[Func
];
605 Analyzer
.reset(new FunctionParmMutationAnalyzer(*Func
, Context
));
606 if (Analyzer
->findMutation(Parm
))
611 // Not forwarding reference.
617 FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(
618 const FunctionDecl
&Func
, ASTContext
&Context
)
619 : BodyAnalyzer(*Func
.getBody(), Context
) {
620 if (const auto *Ctor
= dyn_cast
<CXXConstructorDecl
>(&Func
)) {
621 // CXXCtorInitializer might also mutate Param but they're not part of
622 // function body, check them eagerly here since they're typically trivial.
623 for (const CXXCtorInitializer
*Init
: Ctor
->inits()) {
624 ExprMutationAnalyzer
InitAnalyzer(*Init
->getInit(), Context
);
625 for (const ParmVarDecl
*Parm
: Ctor
->parameters()) {
626 if (Results
.contains(Parm
))
628 if (const Stmt
*S
= InitAnalyzer
.findMutation(Parm
))
636 FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl
*Parm
) {
637 const auto Memoized
= Results
.find(Parm
);
638 if (Memoized
!= Results
.end())
639 return Memoized
->second
;
641 if (const Stmt
*S
= BodyAnalyzer
.findMutation(Parm
))
642 return Results
[Parm
] = S
;
644 return Results
[Parm
] = nullptr;