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 = 284 match(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 findAll(expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl, 389 unless(callee(namedDecl(hasAnyName( 390 "::std::move", "::std::forward"))))), 391 cxxConstructExpr(NonConstRefParam, IsInstantiated, 392 FuncDecl))) 393 .bind(NodeID<Expr>::value)), 394 Stm, Context); 395 for (const auto &Nodes : Matches) { 396 const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value); 397 const auto *Func = Nodes.getNodeAs<FunctionDecl>("func"); 398 if (!Func->getBody() || !Func->getPrimaryTemplate()) 399 return Exp; 400 401 const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm"); 402 const ArrayRef<ParmVarDecl *> AllParams = 403 Func->getPrimaryTemplate()->getTemplatedDecl()->parameters(); 404 QualType ParmType = 405 AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(), 406 AllParams.size() - 1)] 407 ->getType(); 408 if (const auto *T = ParmType->getAs<PackExpansionType>()) 409 ParmType = T->getPattern(); 410 411 // If param type is forwarding reference, follow into the function 412 // definition and see whether the param is mutated inside. 413 if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) { 414 if (!RefType->getPointeeType().getQualifiers() && 415 RefType->getPointeeType()->getAs<TemplateTypeParmType>()) { 416 std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer = 417 FuncParmAnalyzer[Func]; 418 if (!Analyzer) 419 Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context)); 420 if (Analyzer->findMutation(Parm)) 421 return Exp; 422 continue; 423 } 424 } 425 // Not forwarding reference. 426 return Exp; 427 } 428 return nullptr; 429 } 430 431 FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer( 432 const FunctionDecl &Func, ASTContext &Context) 433 : BodyAnalyzer(*Func.getBody(), Context) { 434 if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) { 435 // CXXCtorInitializer might also mutate Param but they're not part of 436 // function body, check them eagerly here since they're typically trivial. 437 for (const CXXCtorInitializer *Init : Ctor->inits()) { 438 ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context); 439 for (const ParmVarDecl *Parm : Ctor->parameters()) { 440 if (Results.find(Parm) != Results.end()) 441 continue; 442 if (const Stmt *S = InitAnalyzer.findMutation(Parm)) 443 Results[Parm] = S; 444 } 445 } 446 } 447 } 448 449 const Stmt * 450 FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) { 451 const auto Memoized = Results.find(Parm); 452 if (Memoized != Results.end()) 453 return Memoized->second; 454 455 if (const Stmt *S = BodyAnalyzer.findMutation(Parm)) 456 return Results[Parm] = S; 457 458 return Results[Parm] = nullptr; 459 } 460 461 } // namespace clang 462