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/ASTMatchers/ASTMatchFinder.h" 10 #include "llvm/ADT/STLExtras.h" 11 12 namespace clang { 13 using namespace ast_matchers; 14 15 namespace { 16 17 AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) { 18 return llvm::is_contained(Node.capture_inits(), E); 19 } 20 21 AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt, 22 ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) { 23 const DeclStmt *const Range = Node.getRangeStmt(); 24 return InnerMatcher.matches(*Range, Finder, Builder); 25 } 26 27 AST_MATCHER_P(Expr, maybeEvalCommaExpr, 28 ast_matchers::internal::Matcher<Expr>, InnerMatcher) { 29 const Expr* Result = &Node; 30 while (const auto *BOComma = 31 dyn_cast_or_null<BinaryOperator>(Result->IgnoreParens())) { 32 if (!BOComma->isCommaOp()) 33 break; 34 Result = BOComma->getRHS(); 35 } 36 return InnerMatcher.matches(*Result, Finder, Builder); 37 } 38 39 const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr> 40 cxxTypeidExpr; 41 42 AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) { 43 return Node.isPotentiallyEvaluated(); 44 } 45 46 const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, 47 GenericSelectionExpr> 48 genericSelectionExpr; 49 50 AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr, 51 ast_matchers::internal::Matcher<Expr>, InnerMatcher) { 52 return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder); 53 } 54 55 const auto nonConstReferenceType = [] { 56 return hasUnqualifiedDesugaredType( 57 referenceType(pointee(unless(isConstQualified())))); 58 }; 59 60 const auto nonConstPointerType = [] { 61 return hasUnqualifiedDesugaredType( 62 pointerType(pointee(unless(isConstQualified())))); 63 }; 64 65 const auto isMoveOnly = [] { 66 return cxxRecordDecl( 67 hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))), 68 hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))), 69 unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(), 70 unless(isDeleted()))), 71 hasMethod(cxxMethodDecl(isCopyAssignmentOperator(), 72 unless(isDeleted())))))); 73 }; 74 75 template <class T> struct NodeID; 76 template <> struct NodeID<Expr> { static constexpr StringRef value = "expr"; }; 77 template <> struct NodeID<Decl> { static constexpr StringRef value = "decl"; }; 78 constexpr StringRef NodeID<Expr>::value; 79 constexpr StringRef NodeID<Decl>::value; 80 81 template <class T, class F = const Stmt *(ExprMutationAnalyzer::*)(const T *)> 82 const Stmt *tryEachMatch(ArrayRef<ast_matchers::BoundNodes> Matches, 83 ExprMutationAnalyzer *Analyzer, F Finder) { 84 const StringRef ID = NodeID<T>::value; 85 for (const auto &Nodes : Matches) { 86 if (const Stmt *S = (Analyzer->*Finder)(Nodes.getNodeAs<T>(ID))) 87 return S; 88 } 89 return nullptr; 90 } 91 92 } // namespace 93 94 const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) { 95 return findMutationMemoized(Exp, 96 {&ExprMutationAnalyzer::findDirectMutation, 97 &ExprMutationAnalyzer::findMemberMutation, 98 &ExprMutationAnalyzer::findArrayElementMutation, 99 &ExprMutationAnalyzer::findCastMutation, 100 &ExprMutationAnalyzer::findRangeLoopMutation, 101 &ExprMutationAnalyzer::findReferenceMutation, 102 &ExprMutationAnalyzer::findFunctionArgMutation}, 103 Results); 104 } 105 106 const Stmt *ExprMutationAnalyzer::findMutation(const Decl *Dec) { 107 return tryEachDeclRef(Dec, &ExprMutationAnalyzer::findMutation); 108 } 109 110 const Stmt *ExprMutationAnalyzer::findPointeeMutation(const Expr *Exp) { 111 return findMutationMemoized(Exp, {/*TODO*/}, PointeeResults); 112 } 113 114 const Stmt *ExprMutationAnalyzer::findPointeeMutation(const Decl *Dec) { 115 return tryEachDeclRef(Dec, &ExprMutationAnalyzer::findPointeeMutation); 116 } 117 118 const Stmt *ExprMutationAnalyzer::findMutationMemoized( 119 const Expr *Exp, llvm::ArrayRef<MutationFinder> Finders, 120 ResultMap &MemoizedResults) { 121 const auto Memoized = MemoizedResults.find(Exp); 122 if (Memoized != MemoizedResults.end()) 123 return Memoized->second; 124 125 if (isUnevaluated(Exp)) 126 return MemoizedResults[Exp] = nullptr; 127 128 for (const auto &Finder : Finders) { 129 if (const Stmt *S = (this->*Finder)(Exp)) 130 return MemoizedResults[Exp] = S; 131 } 132 133 return MemoizedResults[Exp] = nullptr; 134 } 135 136 const Stmt *ExprMutationAnalyzer::tryEachDeclRef(const Decl *Dec, 137 MutationFinder Finder) { 138 const auto Refs = 139 match(findAll(declRefExpr(to(equalsNode(Dec))).bind(NodeID<Expr>::value)), 140 Stm, Context); 141 for (const auto &RefNodes : Refs) { 142 const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value); 143 if ((this->*Finder)(E)) 144 return E; 145 } 146 return nullptr; 147 } 148 149 bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { 150 return selectFirst<Expr>( 151 NodeID<Expr>::value, 152 match( 153 findAll( 154 expr(equalsNode(Exp), 155 anyOf( 156 // `Exp` is part of the underlying expression of 157 // decltype/typeof if it has an ancestor of 158 // typeLoc. 159 hasAncestor(typeLoc(unless( 160 hasAncestor(unaryExprOrTypeTraitExpr())))), 161 hasAncestor(expr(anyOf( 162 // `UnaryExprOrTypeTraitExpr` is unevaluated 163 // unless it's sizeof on VLA. 164 unaryExprOrTypeTraitExpr(unless(sizeOfExpr( 165 hasArgumentOfType(variableArrayType())))), 166 // `CXXTypeidExpr` is unevaluated unless it's 167 // applied to an expression of glvalue of 168 // polymorphic class type. 169 cxxTypeidExpr( 170 unless(isPotentiallyEvaluated())), 171 // The controlling expression of 172 // `GenericSelectionExpr` is unevaluated. 173 genericSelectionExpr(hasControllingExpr( 174 hasDescendant(equalsNode(Exp)))), 175 cxxNoexceptExpr()))))) 176 .bind(NodeID<Expr>::value)), 177 Stm, Context)) != nullptr; 178 } 179 180 const Stmt * 181 ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) { 182 return tryEachMatch<Expr>(Matches, this, &ExprMutationAnalyzer::findMutation); 183 } 184 185 const Stmt * 186 ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) { 187 return tryEachMatch<Decl>(Matches, this, &ExprMutationAnalyzer::findMutation); 188 } 189 190 const Stmt *ExprMutationAnalyzer::findExprPointeeMutation( 191 ArrayRef<ast_matchers::BoundNodes> Matches) { 192 return tryEachMatch<Expr>(Matches, this, 193 &ExprMutationAnalyzer::findPointeeMutation); 194 } 195 196 const Stmt *ExprMutationAnalyzer::findDeclPointeeMutation( 197 ArrayRef<ast_matchers::BoundNodes> Matches) { 198 return tryEachMatch<Decl>(Matches, this, 199 &ExprMutationAnalyzer::findPointeeMutation); 200 } 201 202 const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) { 203 // LHS of any assignment operators. 204 const auto AsAssignmentLhs = binaryOperator( 205 isAssignmentOperator(), 206 hasLHS(maybeEvalCommaExpr(ignoringParenImpCasts(equalsNode(Exp))))); 207 208 // Operand of increment/decrement operators. 209 const auto AsIncDecOperand = 210 unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), 211 hasUnaryOperand(maybeEvalCommaExpr( 212 ignoringParenImpCasts(equalsNode(Exp))))); 213 214 // Invoking non-const member function. 215 // A member function is assumed to be non-const when it is unresolved. 216 const auto NonConstMethod = cxxMethodDecl(unless(isConst())); 217 const auto AsNonConstThis = 218 expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), 219 on(maybeEvalCommaExpr(equalsNode(Exp)))), 220 cxxOperatorCallExpr(callee(NonConstMethod), 221 hasArgument(0, 222 maybeEvalCommaExpr(equalsNode(Exp)))), 223 callExpr(callee(expr(anyOf( 224 unresolvedMemberExpr( 225 hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp)))), 226 cxxDependentScopeMemberExpr( 227 hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp)))))))))); 228 229 // Taking address of 'Exp'. 230 // We're assuming 'Exp' is mutated as soon as its address is taken, though in 231 // theory we can follow the pointer and see whether it escaped `Stm` or is 232 // dereferenced and then mutated. This is left for future improvements. 233 const auto AsAmpersandOperand = 234 unaryOperator(hasOperatorName("&"), 235 // A NoOp implicit cast is adding const. 236 unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), 237 hasUnaryOperand(maybeEvalCommaExpr(equalsNode(Exp)))); 238 const auto AsPointerFromArrayDecay = 239 castExpr(hasCastKind(CK_ArrayToPointerDecay), 240 unless(hasParent(arraySubscriptExpr())), 241 has(maybeEvalCommaExpr(equalsNode(Exp)))); 242 // Treat calling `operator->()` of move-only classes as taking address. 243 // These are typically smart pointers with unique ownership so we treat 244 // mutation of pointee as mutation of the smart pointer itself. 245 const auto AsOperatorArrowThis = 246 cxxOperatorCallExpr(hasOverloadedOperatorName("->"), 247 callee(cxxMethodDecl(ofClass(isMoveOnly()), 248 returns(nonConstPointerType()))), 249 argumentCountIs(1), 250 hasArgument(0, maybeEvalCommaExpr(equalsNode(Exp)))); 251 252 // Used as non-const-ref argument when calling a function. 253 // An argument is assumed to be non-const-ref when the function is unresolved. 254 // Instantiated template functions are not handled here but in 255 // findFunctionArgMutation which has additional smarts for handling forwarding 256 // references. 257 const auto NonConstRefParam = forEachArgumentWithParam( 258 maybeEvalCommaExpr(equalsNode(Exp)), 259 parmVarDecl(hasType(nonConstReferenceType()))); 260 const auto NotInstantiated = unless(hasDeclaration(isInstantiated())); 261 const auto AsNonConstRefArg = anyOf( 262 callExpr(NonConstRefParam, NotInstantiated), 263 cxxConstructExpr(NonConstRefParam, NotInstantiated), 264 callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(), 265 cxxDependentScopeMemberExpr(), 266 hasType(templateTypeParmType())))), 267 hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp)))), 268 cxxUnresolvedConstructExpr(hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp))))); 269 270 // Captured by a lambda by reference. 271 // If we're initializing a capture with 'Exp' directly then we're initializing 272 // a reference capture. 273 // For value captures there will be an ImplicitCastExpr <LValueToRValue>. 274 const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp)); 275 276 // Returned as non-const-ref. 277 // If we're returning 'Exp' directly then it's returned as non-const-ref. 278 // For returning by value there will be an ImplicitCastExpr <LValueToRValue>. 279 // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for 280 // adding const.) 281 const auto AsNonConstRefReturn = returnStmt(hasReturnValue( 282 maybeEvalCommaExpr(equalsNode(Exp)))); 283 284 const auto Matches = match( 285 traverse( 286 ast_type_traits::TK_AsIs, 287 findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, 288 AsAmpersandOperand, AsPointerFromArrayDecay, 289 AsOperatorArrowThis, AsNonConstRefArg, 290 AsLambdaRefCaptureInit, AsNonConstRefReturn)) 291 .bind("stmt"))), 292 Stm, Context); 293 return selectFirst<Stmt>("stmt", Matches); 294 } 295 296 const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) { 297 // Check whether any member of 'Exp' is mutated. 298 const auto MemberExprs = 299 match(findAll(expr(anyOf(memberExpr(hasObjectExpression(equalsNode(Exp))), 300 cxxDependentScopeMemberExpr( 301 hasObjectExpression(equalsNode(Exp))))) 302 .bind(NodeID<Expr>::value)), 303 Stm, Context); 304 return findExprMutation(MemberExprs); 305 } 306 307 const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) { 308 // Check whether any element of an array is mutated. 309 const auto SubscriptExprs = match( 310 findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp)))) 311 .bind(NodeID<Expr>::value)), 312 Stm, Context); 313 return findExprMutation(SubscriptExprs); 314 } 315 316 const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) { 317 // If 'Exp' is casted to any non-const reference type, check the castExpr. 318 const auto Casts = 319 match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)), 320 anyOf(explicitCastExpr(hasDestinationType( 321 nonConstReferenceType())), 322 implicitCastExpr(hasImplicitDestinationType( 323 nonConstReferenceType())))) 324 .bind(NodeID<Expr>::value)), 325 Stm, Context); 326 if (const Stmt *S = findExprMutation(Casts)) 327 return S; 328 // Treat std::{move,forward} as cast. 329 const auto Calls = 330 match(findAll(callExpr(callee(namedDecl( 331 hasAnyName("::std::move", "::std::forward"))), 332 hasArgument(0, equalsNode(Exp))) 333 .bind("expr")), 334 Stm, Context); 335 return findExprMutation(Calls); 336 } 337 338 const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { 339 // If range for looping over 'Exp' with a non-const reference loop variable, 340 // check all declRefExpr of the loop variable. 341 const auto LoopVars = 342 match(findAll(cxxForRangeStmt( 343 hasLoopVariable(varDecl(hasType(nonConstReferenceType())) 344 .bind(NodeID<Decl>::value)), 345 hasRangeInit(equalsNode(Exp)))), 346 Stm, Context); 347 return findDeclMutation(LoopVars); 348 } 349 350 const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) { 351 // Follow non-const reference returned by `operator*()` of move-only classes. 352 // These are typically smart pointers with unique ownership so we treat 353 // mutation of pointee as mutation of the smart pointer itself. 354 const auto Ref = 355 match(findAll(cxxOperatorCallExpr( 356 hasOverloadedOperatorName("*"), 357 callee(cxxMethodDecl(ofClass(isMoveOnly()), 358 returns(nonConstReferenceType()))), 359 argumentCountIs(1), hasArgument(0, equalsNode(Exp))) 360 .bind(NodeID<Expr>::value)), 361 Stm, Context); 362 if (const Stmt *S = findExprMutation(Ref)) 363 return S; 364 365 // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. 366 const auto Refs = match( 367 stmt(forEachDescendant( 368 varDecl( 369 hasType(nonConstReferenceType()), 370 hasInitializer(anyOf(equalsNode(Exp), 371 conditionalOperator(anyOf( 372 hasTrueExpression(equalsNode(Exp)), 373 hasFalseExpression(equalsNode(Exp)))))), 374 hasParent(declStmt().bind("stmt")), 375 // Don't follow the reference in range statement, we've handled 376 // that separately. 377 unless(hasParent(declStmt(hasParent( 378 cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt")))))))) 379 .bind(NodeID<Decl>::value))), 380 Stm, Context); 381 return findDeclMutation(Refs); 382 } 383 384 const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) { 385 const auto NonConstRefParam = forEachArgumentWithParam( 386 equalsNode(Exp), 387 parmVarDecl(hasType(nonConstReferenceType())).bind("parm")); 388 const auto IsInstantiated = hasDeclaration(isInstantiated()); 389 const auto FuncDecl = hasDeclaration(functionDecl().bind("func")); 390 const auto Matches = match( 391 traverse( 392 ast_type_traits::TK_AsIs, 393 findAll( 394 expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl, 395 unless(callee(namedDecl(hasAnyName( 396 "::std::move", "::std::forward"))))), 397 cxxConstructExpr(NonConstRefParam, IsInstantiated, 398 FuncDecl))) 399 .bind(NodeID<Expr>::value))), 400 Stm, Context); 401 for (const auto &Nodes : Matches) { 402 const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value); 403 const auto *Func = Nodes.getNodeAs<FunctionDecl>("func"); 404 if (!Func->getBody() || !Func->getPrimaryTemplate()) 405 return Exp; 406 407 const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm"); 408 const ArrayRef<ParmVarDecl *> AllParams = 409 Func->getPrimaryTemplate()->getTemplatedDecl()->parameters(); 410 QualType ParmType = 411 AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(), 412 AllParams.size() - 1)] 413 ->getType(); 414 if (const auto *T = ParmType->getAs<PackExpansionType>()) 415 ParmType = T->getPattern(); 416 417 // If param type is forwarding reference, follow into the function 418 // definition and see whether the param is mutated inside. 419 if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) { 420 if (!RefType->getPointeeType().getQualifiers() && 421 RefType->getPointeeType()->getAs<TemplateTypeParmType>()) { 422 std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer = 423 FuncParmAnalyzer[Func]; 424 if (!Analyzer) 425 Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context)); 426 if (Analyzer->findMutation(Parm)) 427 return Exp; 428 continue; 429 } 430 } 431 // Not forwarding reference. 432 return Exp; 433 } 434 return nullptr; 435 } 436 437 FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer( 438 const FunctionDecl &Func, ASTContext &Context) 439 : BodyAnalyzer(*Func.getBody(), Context) { 440 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) { 441 // CXXCtorInitializer might also mutate Param but they're not part of 442 // function body, check them eagerly here since they're typically trivial. 443 for (const CXXCtorInitializer *Init : Ctor->inits()) { 444 ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context); 445 for (const ParmVarDecl *Parm : Ctor->parameters()) { 446 if (Results.find(Parm) != Results.end()) 447 continue; 448 if (const Stmt *S = InitAnalyzer.findMutation(Parm)) 449 Results[Parm] = S; 450 } 451 } 452 } 453 } 454 455 const Stmt * 456 FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) { 457 const auto Memoized = Results.find(Parm); 458 if (Memoized != Results.end()) 459 return Memoized->second; 460 461 if (const Stmt *S = BodyAnalyzer.findMutation(Parm)) 462 return Results[Parm] = S; 463 464 return Results[Parm] = nullptr; 465 } 466 467 } // namespace clang 468