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