Path: blob/main/contrib/llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp
35234 views
//===---------- ExprMutationAnalyzer.cpp ----------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"8#include "clang/AST/Expr.h"9#include "clang/AST/OperationKinds.h"10#include "clang/ASTMatchers/ASTMatchFinder.h"11#include "clang/ASTMatchers/ASTMatchers.h"12#include "llvm/ADT/STLExtras.h"1314namespace clang {15using namespace ast_matchers;1617// Check if result of Source expression could be a Target expression.18// Checks:19// - Implicit Casts20// - Binary Operators21// - ConditionalOperator22// - BinaryConditionalOperator23static bool canExprResolveTo(const Expr *Source, const Expr *Target) {2425const auto IgnoreDerivedToBase = [](const Expr *E, auto Matcher) {26if (Matcher(E))27return true;28if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {29if ((Cast->getCastKind() == CK_DerivedToBase ||30Cast->getCastKind() == CK_UncheckedDerivedToBase) &&31Matcher(Cast->getSubExpr()))32return true;33}34return false;35};3637const auto EvalCommaExpr = [](const Expr *E, auto Matcher) {38const Expr *Result = E;39while (const auto *BOComma =40dyn_cast_or_null<BinaryOperator>(Result->IgnoreParens())) {41if (!BOComma->isCommaOp())42break;43Result = BOComma->getRHS();44}4546return Result != E && Matcher(Result);47};4849// The 'ConditionalOperatorM' matches on `<anything> ? <expr> : <expr>`.50// This matching must be recursive because `<expr>` can be anything resolving51// to the `InnerMatcher`, for example another conditional operator.52// The edge-case `BaseClass &b = <cond> ? DerivedVar1 : DerivedVar2;`53// is handled, too. The implicit cast happens outside of the conditional.54// This is matched by `IgnoreDerivedToBase(canResolveToExpr(InnerMatcher))`55// below.56const auto ConditionalOperatorM = [Target](const Expr *E) {57if (const auto *OP = dyn_cast<ConditionalOperator>(E)) {58if (const auto *TE = OP->getTrueExpr()->IgnoreParens())59if (canExprResolveTo(TE, Target))60return true;61if (const auto *FE = OP->getFalseExpr()->IgnoreParens())62if (canExprResolveTo(FE, Target))63return true;64}65return false;66};6768const auto ElvisOperator = [Target](const Expr *E) {69if (const auto *OP = dyn_cast<BinaryConditionalOperator>(E)) {70if (const auto *TE = OP->getTrueExpr()->IgnoreParens())71if (canExprResolveTo(TE, Target))72return true;73if (const auto *FE = OP->getFalseExpr()->IgnoreParens())74if (canExprResolveTo(FE, Target))75return true;76}77return false;78};7980const Expr *SourceExprP = Source->IgnoreParens();81return IgnoreDerivedToBase(SourceExprP,82[&](const Expr *E) {83return E == Target || ConditionalOperatorM(E) ||84ElvisOperator(E);85}) ||86EvalCommaExpr(SourceExprP, [&](const Expr *E) {87return IgnoreDerivedToBase(88E->IgnoreParens(), [&](const Expr *EE) { return EE == Target; });89});90}9192namespace {9394AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {95return llvm::is_contained(Node.capture_inits(), E);96}9798AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,99ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {100const DeclStmt *const Range = Node.getRangeStmt();101return InnerMatcher.matches(*Range, Finder, Builder);102}103104AST_MATCHER_P(Stmt, canResolveToExpr, const Stmt *, Inner) {105auto *Exp = dyn_cast<Expr>(&Node);106if (!Exp)107return true;108auto *Target = dyn_cast<Expr>(Inner);109if (!Target)110return false;111return canExprResolveTo(Exp, Target);112}113114// Similar to 'hasAnyArgument', but does not work because 'InitListExpr' does115// not have the 'arguments()' method.116AST_MATCHER_P(InitListExpr, hasAnyInit, ast_matchers::internal::Matcher<Expr>,117InnerMatcher) {118for (const Expr *Arg : Node.inits()) {119ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);120if (InnerMatcher.matches(*Arg, Finder, &Result)) {121*Builder = std::move(Result);122return true;123}124}125return false;126}127128const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>129cxxTypeidExpr;130131AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) {132return Node.isPotentiallyEvaluated();133}134135AST_MATCHER(CXXMemberCallExpr, isConstCallee) {136const Decl *CalleeDecl = Node.getCalleeDecl();137const auto *VD = dyn_cast_or_null<ValueDecl>(CalleeDecl);138if (!VD)139return false;140const QualType T = VD->getType().getCanonicalType();141const auto *MPT = dyn_cast<MemberPointerType>(T);142const auto *FPT = MPT ? cast<FunctionProtoType>(MPT->getPointeeType())143: dyn_cast<FunctionProtoType>(T);144if (!FPT)145return false;146return FPT->isConst();147}148149AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr,150ast_matchers::internal::Matcher<Expr>, InnerMatcher) {151if (Node.isTypePredicate())152return false;153return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder);154}155156template <typename T>157ast_matchers::internal::Matcher<T>158findFirst(const ast_matchers::internal::Matcher<T> &Matcher) {159return anyOf(Matcher, hasDescendant(Matcher));160}161162const auto nonConstReferenceType = [] {163return hasUnqualifiedDesugaredType(164referenceType(pointee(unless(isConstQualified()))));165};166167const auto nonConstPointerType = [] {168return hasUnqualifiedDesugaredType(169pointerType(pointee(unless(isConstQualified()))));170};171172const auto isMoveOnly = [] {173return cxxRecordDecl(174hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))),175hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))),176unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(),177unless(isDeleted()))),178hasMethod(cxxMethodDecl(isCopyAssignmentOperator(),179unless(isDeleted()))))));180};181182template <class T> struct NodeID;183template <> struct NodeID<Expr> { static constexpr StringRef value = "expr"; };184template <> struct NodeID<Decl> { static constexpr StringRef value = "decl"; };185constexpr StringRef NodeID<Expr>::value;186constexpr StringRef NodeID<Decl>::value;187188template <class T,189class F = const Stmt *(ExprMutationAnalyzer::Analyzer::*)(const T *)>190const Stmt *tryEachMatch(ArrayRef<ast_matchers::BoundNodes> Matches,191ExprMutationAnalyzer::Analyzer *Analyzer, F Finder) {192const StringRef ID = NodeID<T>::value;193for (const auto &Nodes : Matches) {194if (const Stmt *S = (Analyzer->*Finder)(Nodes.getNodeAs<T>(ID)))195return S;196}197return nullptr;198}199200} // namespace201202const Stmt *ExprMutationAnalyzer::Analyzer::findMutation(const Expr *Exp) {203return findMutationMemoized(204Exp,205{&ExprMutationAnalyzer::Analyzer::findDirectMutation,206&ExprMutationAnalyzer::Analyzer::findMemberMutation,207&ExprMutationAnalyzer::Analyzer::findArrayElementMutation,208&ExprMutationAnalyzer::Analyzer::findCastMutation,209&ExprMutationAnalyzer::Analyzer::findRangeLoopMutation,210&ExprMutationAnalyzer::Analyzer::findReferenceMutation,211&ExprMutationAnalyzer::Analyzer::findFunctionArgMutation},212Memorized.Results);213}214215const Stmt *ExprMutationAnalyzer::Analyzer::findMutation(const Decl *Dec) {216return tryEachDeclRef(Dec, &ExprMutationAnalyzer::Analyzer::findMutation);217}218219const Stmt *220ExprMutationAnalyzer::Analyzer::findPointeeMutation(const Expr *Exp) {221return findMutationMemoized(Exp, {/*TODO*/}, Memorized.PointeeResults);222}223224const Stmt *225ExprMutationAnalyzer::Analyzer::findPointeeMutation(const Decl *Dec) {226return tryEachDeclRef(Dec,227&ExprMutationAnalyzer::Analyzer::findPointeeMutation);228}229230const Stmt *ExprMutationAnalyzer::Analyzer::findMutationMemoized(231const Expr *Exp, llvm::ArrayRef<MutationFinder> Finders,232Memoized::ResultMap &MemoizedResults) {233const auto Memoized = MemoizedResults.find(Exp);234if (Memoized != MemoizedResults.end())235return Memoized->second;236237// Assume Exp is not mutated before analyzing Exp.238MemoizedResults[Exp] = nullptr;239if (isUnevaluated(Exp))240return nullptr;241242for (const auto &Finder : Finders) {243if (const Stmt *S = (this->*Finder)(Exp))244return MemoizedResults[Exp] = S;245}246247return nullptr;248}249250const Stmt *251ExprMutationAnalyzer::Analyzer::tryEachDeclRef(const Decl *Dec,252MutationFinder Finder) {253const auto Refs = match(254findAll(255declRefExpr(to(256// `Dec` or a binding if `Dec` is a decomposition.257anyOf(equalsNode(Dec),258bindingDecl(forDecomposition(equalsNode(Dec))))259//260))261.bind(NodeID<Expr>::value)),262Stm, Context);263for (const auto &RefNodes : Refs) {264const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);265if ((this->*Finder)(E))266return E;267}268return nullptr;269}270271bool ExprMutationAnalyzer::Analyzer::isUnevaluated(const Stmt *Exp,272const Stmt &Stm,273ASTContext &Context) {274return selectFirst<Stmt>(275NodeID<Expr>::value,276match(277findFirst(278stmt(canResolveToExpr(Exp),279anyOf(280// `Exp` is part of the underlying expression of281// decltype/typeof if it has an ancestor of282// typeLoc.283hasAncestor(typeLoc(unless(284hasAncestor(unaryExprOrTypeTraitExpr())))),285hasAncestor(expr(anyOf(286// `UnaryExprOrTypeTraitExpr` is unevaluated287// unless it's sizeof on VLA.288unaryExprOrTypeTraitExpr(unless(sizeOfExpr(289hasArgumentOfType(variableArrayType())))),290// `CXXTypeidExpr` is unevaluated unless it's291// applied to an expression of glvalue of292// polymorphic class type.293cxxTypeidExpr(294unless(isPotentiallyEvaluated())),295// The controlling expression of296// `GenericSelectionExpr` is unevaluated.297genericSelectionExpr(hasControllingExpr(298hasDescendant(equalsNode(Exp)))),299cxxNoexceptExpr())))))300.bind(NodeID<Expr>::value)),301Stm, Context)) != nullptr;302}303304bool ExprMutationAnalyzer::Analyzer::isUnevaluated(const Expr *Exp) {305return isUnevaluated(Exp, Stm, Context);306}307308const Stmt *309ExprMutationAnalyzer::Analyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {310return tryEachMatch<Expr>(Matches, this,311&ExprMutationAnalyzer::Analyzer::findMutation);312}313314const Stmt *315ExprMutationAnalyzer::Analyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {316return tryEachMatch<Decl>(Matches, this,317&ExprMutationAnalyzer::Analyzer::findMutation);318}319320const Stmt *ExprMutationAnalyzer::Analyzer::findExprPointeeMutation(321ArrayRef<ast_matchers::BoundNodes> Matches) {322return tryEachMatch<Expr>(323Matches, this, &ExprMutationAnalyzer::Analyzer::findPointeeMutation);324}325326const Stmt *ExprMutationAnalyzer::Analyzer::findDeclPointeeMutation(327ArrayRef<ast_matchers::BoundNodes> Matches) {328return tryEachMatch<Decl>(329Matches, this, &ExprMutationAnalyzer::Analyzer::findPointeeMutation);330}331332const Stmt *333ExprMutationAnalyzer::Analyzer::findDirectMutation(const Expr *Exp) {334// LHS of any assignment operators.335const auto AsAssignmentLhs =336binaryOperator(isAssignmentOperator(), hasLHS(canResolveToExpr(Exp)));337338// Operand of increment/decrement operators.339const auto AsIncDecOperand =340unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),341hasUnaryOperand(canResolveToExpr(Exp)));342343// Invoking non-const member function.344// A member function is assumed to be non-const when it is unresolved.345const auto NonConstMethod = cxxMethodDecl(unless(isConst()));346347const auto AsNonConstThis = expr(anyOf(348cxxMemberCallExpr(on(canResolveToExpr(Exp)), unless(isConstCallee())),349cxxOperatorCallExpr(callee(NonConstMethod),350hasArgument(0, canResolveToExpr(Exp))),351// In case of a templated type, calling overloaded operators is not352// resolved and modelled as `binaryOperator` on a dependent type.353// Such instances are considered a modification, because they can modify354// in different instantiations of the template.355binaryOperator(isTypeDependent(),356hasEitherOperand(ignoringImpCasts(canResolveToExpr(Exp)))),357// A fold expression may contain `Exp` as it's initializer.358// We don't know if the operator modifies `Exp` because the359// operator is type dependent due to the parameter pack.360cxxFoldExpr(hasFoldInit(ignoringImpCasts(canResolveToExpr(Exp)))),361// Within class templates and member functions the member expression might362// not be resolved. In that case, the `callExpr` is considered to be a363// modification.364callExpr(callee(expr(anyOf(365unresolvedMemberExpr(hasObjectExpression(canResolveToExpr(Exp))),366cxxDependentScopeMemberExpr(367hasObjectExpression(canResolveToExpr(Exp))))))),368// Match on a call to a known method, but the call itself is type369// dependent (e.g. `vector<T> v; v.push(T{});` in a templated function).370callExpr(allOf(371isTypeDependent(),372callee(memberExpr(hasDeclaration(NonConstMethod),373hasObjectExpression(canResolveToExpr(Exp))))))));374375// Taking address of 'Exp'.376// We're assuming 'Exp' is mutated as soon as its address is taken, though in377// theory we can follow the pointer and see whether it escaped `Stm` or is378// dereferenced and then mutated. This is left for future improvements.379const auto AsAmpersandOperand =380unaryOperator(hasOperatorName("&"),381// A NoOp implicit cast is adding const.382unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),383hasUnaryOperand(canResolveToExpr(Exp)));384const auto AsPointerFromArrayDecay = castExpr(385hasCastKind(CK_ArrayToPointerDecay),386unless(hasParent(arraySubscriptExpr())), has(canResolveToExpr(Exp)));387// Treat calling `operator->()` of move-only classes as taking address.388// These are typically smart pointers with unique ownership so we treat389// mutation of pointee as mutation of the smart pointer itself.390const auto AsOperatorArrowThis = cxxOperatorCallExpr(391hasOverloadedOperatorName("->"),392callee(393cxxMethodDecl(ofClass(isMoveOnly()), returns(nonConstPointerType()))),394argumentCountIs(1), hasArgument(0, canResolveToExpr(Exp)));395396// Used as non-const-ref argument when calling a function.397// An argument is assumed to be non-const-ref when the function is unresolved.398// Instantiated template functions are not handled here but in399// findFunctionArgMutation which has additional smarts for handling forwarding400// references.401const auto NonConstRefParam = forEachArgumentWithParamType(402anyOf(canResolveToExpr(Exp),403memberExpr(hasObjectExpression(canResolveToExpr(Exp)))),404nonConstReferenceType());405const auto NotInstantiated = unless(hasDeclaration(isInstantiated()));406407const auto AsNonConstRefArg =408anyOf(callExpr(NonConstRefParam, NotInstantiated),409cxxConstructExpr(NonConstRefParam, NotInstantiated),410// If the call is type-dependent, we can't properly process any411// argument because required type conversions and implicit casts412// will be inserted only after specialization.413callExpr(isTypeDependent(), hasAnyArgument(canResolveToExpr(Exp))),414cxxUnresolvedConstructExpr(hasAnyArgument(canResolveToExpr(Exp))),415// Previous False Positive in the following Code:416// `template <typename T> void f() { int i = 42; new Type<T>(i); }`417// Where the constructor of `Type` takes its argument as reference.418// The AST does not resolve in a `cxxConstructExpr` because it is419// type-dependent.420parenListExpr(hasDescendant(expr(canResolveToExpr(Exp)))),421// If the initializer is for a reference type, there is no cast for422// the variable. Values are cast to RValue first.423initListExpr(hasAnyInit(expr(canResolveToExpr(Exp)))));424425// Captured by a lambda by reference.426// If we're initializing a capture with 'Exp' directly then we're initializing427// a reference capture.428// For value captures there will be an ImplicitCastExpr <LValueToRValue>.429const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));430431// Returned as non-const-ref.432// If we're returning 'Exp' directly then it's returned as non-const-ref.433// For returning by value there will be an ImplicitCastExpr <LValueToRValue>.434// For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for435// adding const.)436const auto AsNonConstRefReturn =437returnStmt(hasReturnValue(canResolveToExpr(Exp)));438439// It is used as a non-const-reference for initializing a range-for loop.440const auto AsNonConstRefRangeInit = cxxForRangeStmt(hasRangeInit(declRefExpr(441allOf(canResolveToExpr(Exp), hasType(nonConstReferenceType())))));442443const auto Matches = match(444traverse(445TK_AsIs,446findFirst(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,447AsAmpersandOperand, AsPointerFromArrayDecay,448AsOperatorArrowThis, AsNonConstRefArg,449AsLambdaRefCaptureInit, AsNonConstRefReturn,450AsNonConstRefRangeInit))451.bind("stmt"))),452Stm, Context);453return selectFirst<Stmt>("stmt", Matches);454}455456const Stmt *457ExprMutationAnalyzer::Analyzer::findMemberMutation(const Expr *Exp) {458// Check whether any member of 'Exp' is mutated.459const auto MemberExprs = match(460findAll(expr(anyOf(memberExpr(hasObjectExpression(canResolveToExpr(Exp))),461cxxDependentScopeMemberExpr(462hasObjectExpression(canResolveToExpr(Exp))),463binaryOperator(hasOperatorName(".*"),464hasLHS(equalsNode(Exp)))))465.bind(NodeID<Expr>::value)),466Stm, Context);467return findExprMutation(MemberExprs);468}469470const Stmt *471ExprMutationAnalyzer::Analyzer::findArrayElementMutation(const Expr *Exp) {472// Check whether any element of an array is mutated.473const auto SubscriptExprs = match(474findAll(arraySubscriptExpr(475anyOf(hasBase(canResolveToExpr(Exp)),476hasBase(implicitCastExpr(allOf(477hasCastKind(CK_ArrayToPointerDecay),478hasSourceExpression(canResolveToExpr(Exp)))))))479.bind(NodeID<Expr>::value)),480Stm, Context);481return findExprMutation(SubscriptExprs);482}483484const Stmt *ExprMutationAnalyzer::Analyzer::findCastMutation(const Expr *Exp) {485// If the 'Exp' is explicitly casted to a non-const reference type the486// 'Exp' is considered to be modified.487const auto ExplicitCast =488match(findFirst(stmt(castExpr(hasSourceExpression(canResolveToExpr(Exp)),489explicitCastExpr(hasDestinationType(490nonConstReferenceType()))))491.bind("stmt")),492Stm, Context);493494if (const auto *CastStmt = selectFirst<Stmt>("stmt", ExplicitCast))495return CastStmt;496497// If 'Exp' is casted to any non-const reference type, check the castExpr.498const auto Casts = match(499findAll(expr(castExpr(hasSourceExpression(canResolveToExpr(Exp)),500anyOf(explicitCastExpr(hasDestinationType(501nonConstReferenceType())),502implicitCastExpr(hasImplicitDestinationType(503nonConstReferenceType())))))504.bind(NodeID<Expr>::value)),505Stm, Context);506507if (const Stmt *S = findExprMutation(Casts))508return S;509// Treat std::{move,forward} as cast.510const auto Calls =511match(findAll(callExpr(callee(namedDecl(512hasAnyName("::std::move", "::std::forward"))),513hasArgument(0, canResolveToExpr(Exp)))514.bind("expr")),515Stm, Context);516return findExprMutation(Calls);517}518519const Stmt *520ExprMutationAnalyzer::Analyzer::findRangeLoopMutation(const Expr *Exp) {521// Keep the ordering for the specific initialization matches to happen first,522// because it is cheaper to match all potential modifications of the loop523// variable.524525// The range variable is a reference to a builtin array. In that case the526// array is considered modified if the loop-variable is a non-const reference.527const auto DeclStmtToNonRefToArray = declStmt(hasSingleDecl(varDecl(hasType(528hasUnqualifiedDesugaredType(referenceType(pointee(arrayType())))))));529const auto RefToArrayRefToElements = match(530findFirst(stmt(cxxForRangeStmt(531hasLoopVariable(532varDecl(anyOf(hasType(nonConstReferenceType()),533hasType(nonConstPointerType())))534.bind(NodeID<Decl>::value)),535hasRangeStmt(DeclStmtToNonRefToArray),536hasRangeInit(canResolveToExpr(Exp))))537.bind("stmt")),538Stm, Context);539540if (const auto *BadRangeInitFromArray =541selectFirst<Stmt>("stmt", RefToArrayRefToElements))542return BadRangeInitFromArray;543544// Small helper to match special cases in range-for loops.545//546// It is possible that containers do not provide a const-overload for their547// iterator accessors. If this is the case, the variable is used non-const548// no matter what happens in the loop. This requires special detection as it549// is then faster to find all mutations of the loop variable.550// It aims at a different modification as well.551const auto HasAnyNonConstIterator =552anyOf(allOf(hasMethod(allOf(hasName("begin"), unless(isConst()))),553unless(hasMethod(allOf(hasName("begin"), isConst())))),554allOf(hasMethod(allOf(hasName("end"), unless(isConst()))),555unless(hasMethod(allOf(hasName("end"), isConst())))));556557const auto DeclStmtToNonConstIteratorContainer = declStmt(558hasSingleDecl(varDecl(hasType(hasUnqualifiedDesugaredType(referenceType(559pointee(hasDeclaration(cxxRecordDecl(HasAnyNonConstIterator)))))))));560561const auto RefToContainerBadIterators = match(562findFirst(stmt(cxxForRangeStmt(allOf(563hasRangeStmt(DeclStmtToNonConstIteratorContainer),564hasRangeInit(canResolveToExpr(Exp)))))565.bind("stmt")),566Stm, Context);567568if (const auto *BadIteratorsContainer =569selectFirst<Stmt>("stmt", RefToContainerBadIterators))570return BadIteratorsContainer;571572// If range for looping over 'Exp' with a non-const reference loop variable,573// check all declRefExpr of the loop variable.574const auto LoopVars =575match(findAll(cxxForRangeStmt(576hasLoopVariable(varDecl(hasType(nonConstReferenceType()))577.bind(NodeID<Decl>::value)),578hasRangeInit(canResolveToExpr(Exp)))),579Stm, Context);580return findDeclMutation(LoopVars);581}582583const Stmt *584ExprMutationAnalyzer::Analyzer::findReferenceMutation(const Expr *Exp) {585// Follow non-const reference returned by `operator*()` of move-only classes.586// These are typically smart pointers with unique ownership so we treat587// mutation of pointee as mutation of the smart pointer itself.588const auto Ref = match(589findAll(cxxOperatorCallExpr(590hasOverloadedOperatorName("*"),591callee(cxxMethodDecl(ofClass(isMoveOnly()),592returns(nonConstReferenceType()))),593argumentCountIs(1), hasArgument(0, canResolveToExpr(Exp)))594.bind(NodeID<Expr>::value)),595Stm, Context);596if (const Stmt *S = findExprMutation(Ref))597return S;598599// If 'Exp' is bound to a non-const reference, check all declRefExpr to that.600const auto Refs = match(601stmt(forEachDescendant(602varDecl(hasType(nonConstReferenceType()),603hasInitializer(anyOf(604canResolveToExpr(Exp),605memberExpr(hasObjectExpression(canResolveToExpr(Exp))))),606hasParent(declStmt().bind("stmt")),607// Don't follow the reference in range statement, we've608// handled that separately.609unless(hasParent(declStmt(hasParent(cxxForRangeStmt(610hasRangeStmt(equalsBoundNode("stmt"))))))))611.bind(NodeID<Decl>::value))),612Stm, Context);613return findDeclMutation(Refs);614}615616const Stmt *617ExprMutationAnalyzer::Analyzer::findFunctionArgMutation(const Expr *Exp) {618const auto NonConstRefParam = forEachArgumentWithParam(619canResolveToExpr(Exp),620parmVarDecl(hasType(nonConstReferenceType())).bind("parm"));621const auto IsInstantiated = hasDeclaration(isInstantiated());622const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));623const auto Matches = match(624traverse(625TK_AsIs,626findAll(627expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl,628unless(callee(namedDecl(hasAnyName(629"::std::move", "::std::forward"))))),630cxxConstructExpr(NonConstRefParam, IsInstantiated,631FuncDecl)))632.bind(NodeID<Expr>::value))),633Stm, Context);634for (const auto &Nodes : Matches) {635const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value);636const auto *Func = Nodes.getNodeAs<FunctionDecl>("func");637if (!Func->getBody() || !Func->getPrimaryTemplate())638return Exp;639640const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm");641const ArrayRef<ParmVarDecl *> AllParams =642Func->getPrimaryTemplate()->getTemplatedDecl()->parameters();643QualType ParmType =644AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(),645AllParams.size() - 1)]646->getType();647if (const auto *T = ParmType->getAs<PackExpansionType>())648ParmType = T->getPattern();649650// If param type is forwarding reference, follow into the function651// definition and see whether the param is mutated inside.652if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) {653if (!RefType->getPointeeType().getQualifiers() &&654RefType->getPointeeType()->getAs<TemplateTypeParmType>()) {655FunctionParmMutationAnalyzer *Analyzer =656FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(657*Func, Context, Memorized);658if (Analyzer->findMutation(Parm))659return Exp;660continue;661}662}663// Not forwarding reference.664return Exp;665}666return nullptr;667}668669FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(670const FunctionDecl &Func, ASTContext &Context,671ExprMutationAnalyzer::Memoized &Memorized)672: BodyAnalyzer(*Func.getBody(), Context, Memorized) {673if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) {674// CXXCtorInitializer might also mutate Param but they're not part of675// function body, check them eagerly here since they're typically trivial.676for (const CXXCtorInitializer *Init : Ctor->inits()) {677ExprMutationAnalyzer::Analyzer InitAnalyzer(*Init->getInit(), Context,678Memorized);679for (const ParmVarDecl *Parm : Ctor->parameters()) {680if (Results.contains(Parm))681continue;682if (const Stmt *S = InitAnalyzer.findMutation(Parm))683Results[Parm] = S;684}685}686}687}688689const Stmt *690FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) {691const auto Memoized = Results.find(Parm);692if (Memoized != Results.end())693return Memoized->second;694// To handle call A -> call B -> call A. Assume parameters of A is not mutated695// before analyzing parameters of A. Then when analyzing the second "call A",696// FunctionParmMutationAnalyzer can use this memoized value to avoid infinite697// recursion.698Results[Parm] = nullptr;699if (const Stmt *S = BodyAnalyzer.findMutation(Parm))700return Results[Parm] = S;701return Results[Parm];702}703704} // namespace clang705706707