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