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