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 const std::string value; }; 77 template <> struct NodeID<Decl> { static const std::string value; }; 78 const std::string NodeID<Expr>::value = "expr"; 79 const std::string NodeID<Decl>::value = "decl"; 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 = 205 binaryOperator(isAssignmentOperator(), 206 hasLHS(maybeEvalCommaExpr(equalsNode(Exp)))); 207 208 // Operand of increment/decrement operators. 209 const auto AsIncDecOperand = 210 unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), 211 hasUnaryOperand(maybeEvalCommaExpr(equalsNode(Exp)))); 212 213 // Invoking non-const member function. 214 // A member function is assumed to be non-const when it is unresolved. 215 const auto NonConstMethod = cxxMethodDecl(unless(isConst())); 216 const auto AsNonConstThis = 217 expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), 218 on(maybeEvalCommaExpr(equalsNode(Exp)))), 219 cxxOperatorCallExpr(callee(NonConstMethod), 220 hasArgument(0, 221 maybeEvalCommaExpr(equalsNode(Exp)))), 222 callExpr(callee(expr(anyOf( 223 unresolvedMemberExpr( 224 hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp)))), 225 cxxDependentScopeMemberExpr( 226 hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp)))))))))); 227 228 // Taking address of 'Exp'. 229 // We're assuming 'Exp' is mutated as soon as its address is taken, though in 230 // theory we can follow the pointer and see whether it escaped `Stm` or is 231 // dereferenced and then mutated. This is left for future improvements. 232 const auto AsAmpersandOperand = 233 unaryOperator(hasOperatorName("&"), 234 // A NoOp implicit cast is adding const. 235 unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), 236 hasUnaryOperand(maybeEvalCommaExpr(equalsNode(Exp)))); 237 const auto AsPointerFromArrayDecay = 238 castExpr(hasCastKind(CK_ArrayToPointerDecay), 239 unless(hasParent(arraySubscriptExpr())), 240 has(maybeEvalCommaExpr(equalsNode(Exp)))); 241 // Treat calling `operator->()` of move-only classes as taking address. 242 // These are typically smart pointers with unique ownership so we treat 243 // mutation of pointee as mutation of the smart pointer itself. 244 const auto AsOperatorArrowThis = 245 cxxOperatorCallExpr(hasOverloadedOperatorName("->"), 246 callee(cxxMethodDecl(ofClass(isMoveOnly()), 247 returns(nonConstPointerType()))), 248 argumentCountIs(1), 249 hasArgument(0, maybeEvalCommaExpr(equalsNode(Exp)))); 250 251 // Used as non-const-ref argument when calling a function. 252 // An argument is assumed to be non-const-ref when the function is unresolved. 253 // Instantiated template functions are not handled here but in 254 // findFunctionArgMutation which has additional smarts for handling forwarding 255 // references. 256 const auto NonConstRefParam = forEachArgumentWithParam( 257 maybeEvalCommaExpr(equalsNode(Exp)), 258 parmVarDecl(hasType(nonConstReferenceType()))); 259 const auto NotInstantiated = unless(hasDeclaration(isInstantiated())); 260 const auto AsNonConstRefArg = anyOf( 261 callExpr(NonConstRefParam, NotInstantiated), 262 cxxConstructExpr(NonConstRefParam, NotInstantiated), 263 callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(), 264 cxxDependentScopeMemberExpr(), 265 hasType(templateTypeParmType())))), 266 hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp)))), 267 cxxUnresolvedConstructExpr(hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp))))); 268 269 // Captured by a lambda by reference. 270 // If we're initializing a capture with 'Exp' directly then we're initializing 271 // a reference capture. 272 // For value captures there will be an ImplicitCastExpr <LValueToRValue>. 273 const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp)); 274 275 // Returned as non-const-ref. 276 // If we're returning 'Exp' directly then it's returned as non-const-ref. 277 // For returning by value there will be an ImplicitCastExpr <LValueToRValue>. 278 // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for 279 // adding const.) 280 const auto AsNonConstRefReturn = returnStmt(hasReturnValue( 281 maybeEvalCommaExpr(equalsNode(Exp)))); 282 283 const auto Matches = match( 284 traverse( 285 ast_type_traits::TK_AsIs, 286 findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, 287 AsAmpersandOperand, AsPointerFromArrayDecay, 288 AsOperatorArrowThis, AsNonConstRefArg, 289 AsLambdaRefCaptureInit, AsNonConstRefReturn)) 290 .bind("stmt"))), 291 Stm, Context); 292 return selectFirst<Stmt>("stmt", Matches); 293 } 294 295 const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) { 296 // Check whether any member of 'Exp' is mutated. 297 const auto MemberExprs = 298 match(findAll(expr(anyOf(memberExpr(hasObjectExpression(equalsNode(Exp))), 299 cxxDependentScopeMemberExpr( 300 hasObjectExpression(equalsNode(Exp))))) 301 .bind(NodeID<Expr>::value)), 302 Stm, Context); 303 return findExprMutation(MemberExprs); 304 } 305 306 const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) { 307 // Check whether any element of an array is mutated. 308 const auto SubscriptExprs = match( 309 findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp)))) 310 .bind(NodeID<Expr>::value)), 311 Stm, Context); 312 return findExprMutation(SubscriptExprs); 313 } 314 315 const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) { 316 // If 'Exp' is casted to any non-const reference type, check the castExpr. 317 const auto Casts = 318 match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)), 319 anyOf(explicitCastExpr(hasDestinationType( 320 nonConstReferenceType())), 321 implicitCastExpr(hasImplicitDestinationType( 322 nonConstReferenceType())))) 323 .bind(NodeID<Expr>::value)), 324 Stm, Context); 325 if (const Stmt *S = findExprMutation(Casts)) 326 return S; 327 // Treat std::{move,forward} as cast. 328 const auto Calls = 329 match(findAll(callExpr(callee(namedDecl( 330 hasAnyName("::std::move", "::std::forward"))), 331 hasArgument(0, equalsNode(Exp))) 332 .bind("expr")), 333 Stm, Context); 334 return findExprMutation(Calls); 335 } 336 337 const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { 338 // If range for looping over 'Exp' with a non-const reference loop variable, 339 // check all declRefExpr of the loop variable. 340 const auto LoopVars = 341 match(findAll(cxxForRangeStmt( 342 hasLoopVariable(varDecl(hasType(nonConstReferenceType())) 343 .bind(NodeID<Decl>::value)), 344 hasRangeInit(equalsNode(Exp)))), 345 Stm, Context); 346 return findDeclMutation(LoopVars); 347 } 348 349 const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) { 350 // Follow non-const reference returned by `operator*()` of move-only classes. 351 // These are typically smart pointers with unique ownership so we treat 352 // mutation of pointee as mutation of the smart pointer itself. 353 const auto Ref = 354 match(findAll(cxxOperatorCallExpr( 355 hasOverloadedOperatorName("*"), 356 callee(cxxMethodDecl(ofClass(isMoveOnly()), 357 returns(nonConstReferenceType()))), 358 argumentCountIs(1), hasArgument(0, equalsNode(Exp))) 359 .bind(NodeID<Expr>::value)), 360 Stm, Context); 361 if (const Stmt *S = findExprMutation(Ref)) 362 return S; 363 364 // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. 365 const auto Refs = match( 366 stmt(forEachDescendant( 367 varDecl( 368 hasType(nonConstReferenceType()), 369 hasInitializer(anyOf(equalsNode(Exp), 370 conditionalOperator(anyOf( 371 hasTrueExpression(equalsNode(Exp)), 372 hasFalseExpression(equalsNode(Exp)))))), 373 hasParent(declStmt().bind("stmt")), 374 // Don't follow the reference in range statement, we've handled 375 // that separately. 376 unless(hasParent(declStmt(hasParent( 377 cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt")))))))) 378 .bind(NodeID<Decl>::value))), 379 Stm, Context); 380 return findDeclMutation(Refs); 381 } 382 383 const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) { 384 const auto NonConstRefParam = forEachArgumentWithParam( 385 equalsNode(Exp), 386 parmVarDecl(hasType(nonConstReferenceType())).bind("parm")); 387 const auto IsInstantiated = hasDeclaration(isInstantiated()); 388 const auto FuncDecl = hasDeclaration(functionDecl().bind("func")); 389 const auto Matches = match( 390 traverse( 391 ast_type_traits::TK_AsIs, 392 findAll( 393 expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl, 394 unless(callee(namedDecl(hasAnyName( 395 "::std::move", "::std::forward"))))), 396 cxxConstructExpr(NonConstRefParam, IsInstantiated, 397 FuncDecl))) 398 .bind(NodeID<Expr>::value))), 399 Stm, Context); 400 for (const auto &Nodes : Matches) { 401 const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value); 402 const auto *Func = Nodes.getNodeAs<FunctionDecl>("func"); 403 if (!Func->getBody() || !Func->getPrimaryTemplate()) 404 return Exp; 405 406 const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm"); 407 const ArrayRef<ParmVarDecl *> AllParams = 408 Func->getPrimaryTemplate()->getTemplatedDecl()->parameters(); 409 QualType ParmType = 410 AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(), 411 AllParams.size() - 1)] 412 ->getType(); 413 if (const auto *T = ParmType->getAs<PackExpansionType>()) 414 ParmType = T->getPattern(); 415 416 // If param type is forwarding reference, follow into the function 417 // definition and see whether the param is mutated inside. 418 if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) { 419 if (!RefType->getPointeeType().getQualifiers() && 420 RefType->getPointeeType()->getAs<TemplateTypeParmType>()) { 421 std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer = 422 FuncParmAnalyzer[Func]; 423 if (!Analyzer) 424 Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context)); 425 if (Analyzer->findMutation(Parm)) 426 return Exp; 427 continue; 428 } 429 } 430 // Not forwarding reference. 431 return Exp; 432 } 433 return nullptr; 434 } 435 436 FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer( 437 const FunctionDecl &Func, ASTContext &Context) 438 : BodyAnalyzer(*Func.getBody(), Context) { 439 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) { 440 // CXXCtorInitializer might also mutate Param but they're not part of 441 // function body, check them eagerly here since they're typically trivial. 442 for (const CXXCtorInitializer *Init : Ctor->inits()) { 443 ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context); 444 for (const ParmVarDecl *Parm : Ctor->parameters()) { 445 if (Results.find(Parm) != Results.end()) 446 continue; 447 if (const Stmt *S = InitAnalyzer.findMutation(Parm)) 448 Results[Parm] = S; 449 } 450 } 451 } 452 } 453 454 const Stmt * 455 FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) { 456 const auto Memoized = Results.find(Parm); 457 if (Memoized != Results.end()) 458 return Memoized->second; 459 460 if (const Stmt *S = BodyAnalyzer.findMutation(Parm)) 461 return Results[Parm] = S; 462 463 return Results[Parm] = nullptr; 464 } 465 466 } // namespace clang 467