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