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