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