1 //===---------- ExprMutationAnalyzer.cpp ----------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" 10 #include "clang/ASTMatchers/ASTMatchFinder.h" 11 #include "llvm/ADT/STLExtras.h" 12 13 namespace clang { 14 using namespace ast_matchers; 15 16 namespace { 17 18 AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) { 19 return llvm::is_contained(Node.capture_inits(), E); 20 } 21 22 AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt, 23 ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) { 24 const DeclStmt *const Range = Node.getRangeStmt(); 25 return InnerMatcher.matches(*Range, Finder, Builder); 26 } 27 28 const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr> 29 cxxTypeidExpr; 30 31 AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) { 32 return Node.isPotentiallyEvaluated(); 33 } 34 35 const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXNoexceptExpr> 36 cxxNoexceptExpr; 37 38 const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, 39 GenericSelectionExpr> 40 genericSelectionExpr; 41 42 AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr, 43 ast_matchers::internal::Matcher<Expr>, InnerMatcher) { 44 return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder); 45 } 46 47 const auto nonConstReferenceType = [] { 48 return hasUnqualifiedDesugaredType( 49 referenceType(pointee(unless(isConstQualified())))); 50 }; 51 52 const auto nonConstPointerType = [] { 53 return hasUnqualifiedDesugaredType( 54 pointerType(pointee(unless(isConstQualified())))); 55 }; 56 57 const auto isMoveOnly = [] { 58 return cxxRecordDecl( 59 hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))), 60 hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))), 61 unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(), 62 unless(isDeleted()))), 63 hasMethod(cxxMethodDecl(isCopyAssignmentOperator(), 64 unless(isDeleted())))))); 65 }; 66 67 } // namespace 68 69 const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) { 70 const auto Memoized = Results.find(Exp); 71 if (Memoized != Results.end()) 72 return Memoized->second; 73 74 if (isUnevaluated(Exp)) 75 return Results[Exp] = nullptr; 76 77 for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation, 78 &ExprMutationAnalyzer::findMemberMutation, 79 &ExprMutationAnalyzer::findArrayElementMutation, 80 &ExprMutationAnalyzer::findCastMutation, 81 &ExprMutationAnalyzer::findRangeLoopMutation, 82 &ExprMutationAnalyzer::findReferenceMutation}) { 83 if (const Stmt *S = (this->*Finder)(Exp)) 84 return Results[Exp] = S; 85 } 86 87 return Results[Exp] = nullptr; 88 } 89 90 bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { 91 return selectFirst<Expr>( 92 "expr", 93 match( 94 findAll( 95 expr(equalsNode(Exp), 96 anyOf( 97 // `Exp` is part of the underlying expression of 98 // decltype/typeof if it has an ancestor of 99 // typeLoc. 100 hasAncestor(typeLoc(unless( 101 hasAncestor(unaryExprOrTypeTraitExpr())))), 102 hasAncestor(expr(anyOf( 103 // `UnaryExprOrTypeTraitExpr` is unevaluated 104 // unless it's sizeof on VLA. 105 unaryExprOrTypeTraitExpr(unless(sizeOfExpr( 106 hasArgumentOfType(variableArrayType())))), 107 // `CXXTypeidExpr` is unevaluated unless it's 108 // applied to an expression of glvalue of 109 // polymorphic class type. 110 cxxTypeidExpr( 111 unless(isPotentiallyEvaluated())), 112 // The controlling expression of 113 // `GenericSelectionExpr` is unevaluated. 114 genericSelectionExpr(hasControllingExpr( 115 hasDescendant(equalsNode(Exp)))), 116 cxxNoexceptExpr()))))) 117 .bind("expr")), 118 Stm, Context)) != nullptr; 119 } 120 121 const Stmt * 122 ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) { 123 for (const auto &Nodes : Matches) { 124 if (const Stmt *S = findMutation(Nodes.getNodeAs<Expr>("expr"))) 125 return S; 126 } 127 return nullptr; 128 } 129 130 const Stmt * 131 ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) { 132 for (const auto &DeclNodes : Matches) { 133 if (const Stmt *S = findDeclMutation(DeclNodes.getNodeAs<Decl>("decl"))) 134 return S; 135 } 136 return nullptr; 137 } 138 139 const Stmt *ExprMutationAnalyzer::findDeclMutation(const Decl *Dec) { 140 const auto Refs = match( 141 findAll(declRefExpr(to(equalsNode(Dec))).bind("expr")), Stm, Context); 142 for (const auto &RefNodes : Refs) { 143 const auto *E = RefNodes.getNodeAs<Expr>("expr"); 144 if (findMutation(E)) 145 return E; 146 } 147 return nullptr; 148 } 149 150 const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) { 151 // LHS of any assignment operators. 152 const auto AsAssignmentLhs = 153 binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp))); 154 155 // Operand of increment/decrement operators. 156 const auto AsIncDecOperand = 157 unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), 158 hasUnaryOperand(equalsNode(Exp))); 159 160 // Invoking non-const member function. 161 // A member function is assumed to be non-const when it is unresolved. 162 const auto NonConstMethod = cxxMethodDecl(unless(isConst())); 163 const auto AsNonConstThis = 164 expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))), 165 cxxOperatorCallExpr(callee(NonConstMethod), 166 hasArgument(0, equalsNode(Exp))), 167 callExpr(callee(expr(anyOf( 168 unresolvedMemberExpr(hasObjectExpression(equalsNode(Exp))), 169 cxxDependentScopeMemberExpr( 170 hasObjectExpression(equalsNode(Exp))))))))); 171 172 // Taking address of 'Exp'. 173 // We're assuming 'Exp' is mutated as soon as its address is taken, though in 174 // theory we can follow the pointer and see whether it escaped `Stm` or is 175 // dereferenced and then mutated. This is left for future improvements. 176 const auto AsAmpersandOperand = 177 unaryOperator(hasOperatorName("&"), 178 // A NoOp implicit cast is adding const. 179 unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), 180 hasUnaryOperand(equalsNode(Exp))); 181 const auto AsPointerFromArrayDecay = 182 castExpr(hasCastKind(CK_ArrayToPointerDecay), 183 unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp))); 184 // Treat calling `operator->()` of move-only classes as taking address. 185 // These are typically smart pointers with unique ownership so we treat 186 // mutation of pointee as mutation of the smart pointer itself. 187 const auto AsOperatorArrowThis = 188 cxxOperatorCallExpr(hasOverloadedOperatorName("->"), 189 callee(cxxMethodDecl(ofClass(isMoveOnly()), 190 returns(nonConstPointerType()))), 191 argumentCountIs(1), hasArgument(0, equalsNode(Exp))); 192 193 // Used as non-const-ref argument when calling a function. 194 // An argument is assumed to be non-const-ref when the function is unresolved. 195 const auto NonConstRefParam = forEachArgumentWithParam( 196 equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType()))); 197 const auto AsNonConstRefArg = anyOf( 198 callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam), 199 callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(), 200 cxxDependentScopeMemberExpr(), 201 hasType(templateTypeParmType())))), 202 hasAnyArgument(equalsNode(Exp))), 203 cxxUnresolvedConstructExpr(hasAnyArgument(equalsNode(Exp)))); 204 205 // Captured by a lambda by reference. 206 // If we're initializing a capture with 'Exp' directly then we're initializing 207 // a reference capture. 208 // For value captures there will be an ImplicitCastExpr <LValueToRValue>. 209 const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp)); 210 211 // Returned as non-const-ref. 212 // If we're returning 'Exp' directly then it's returned as non-const-ref. 213 // For returning by value there will be an ImplicitCastExpr <LValueToRValue>. 214 // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for 215 // adding const.) 216 const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp))); 217 218 const auto Matches = 219 match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, 220 AsAmpersandOperand, AsPointerFromArrayDecay, 221 AsOperatorArrowThis, AsNonConstRefArg, 222 AsLambdaRefCaptureInit, AsNonConstRefReturn)) 223 .bind("stmt")), 224 Stm, Context); 225 return selectFirst<Stmt>("stmt", Matches); 226 } 227 228 const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) { 229 // Check whether any member of 'Exp' is mutated. 230 const auto MemberExprs = 231 match(findAll(expr(anyOf(memberExpr(hasObjectExpression(equalsNode(Exp))), 232 cxxDependentScopeMemberExpr( 233 hasObjectExpression(equalsNode(Exp))))) 234 .bind("expr")), 235 Stm, Context); 236 return findExprMutation(MemberExprs); 237 } 238 239 const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) { 240 // Check whether any element of an array is mutated. 241 const auto SubscriptExprs = match( 242 findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp)))) 243 .bind("expr")), 244 Stm, Context); 245 return findExprMutation(SubscriptExprs); 246 } 247 248 const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) { 249 // If 'Exp' is casted to any non-const reference type, check the castExpr. 250 const auto Casts = 251 match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)), 252 anyOf(explicitCastExpr(hasDestinationType( 253 nonConstReferenceType())), 254 implicitCastExpr(hasImplicitDestinationType( 255 nonConstReferenceType())))) 256 .bind("expr")), 257 Stm, Context); 258 return findExprMutation(Casts); 259 } 260 261 const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { 262 // If range for looping over 'Exp' with a non-const reference loop variable, 263 // check all declRefExpr of the loop variable. 264 const auto LoopVars = 265 match(findAll(cxxForRangeStmt( 266 hasLoopVariable( 267 varDecl(hasType(nonConstReferenceType())).bind("decl")), 268 hasRangeInit(equalsNode(Exp)))), 269 Stm, Context); 270 return findDeclMutation(LoopVars); 271 } 272 273 const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) { 274 // Follow non-const reference returned by `operator*()` of move-only classes. 275 // These are typically smart pointers with unique ownership so we treat 276 // mutation of pointee as mutation of the smart pointer itself. 277 const auto Ref = match( 278 findAll(cxxOperatorCallExpr( 279 hasOverloadedOperatorName("*"), 280 callee(cxxMethodDecl(ofClass(isMoveOnly()), 281 returns(hasUnqualifiedDesugaredType( 282 nonConstReferenceType())))), 283 argumentCountIs(1), hasArgument(0, equalsNode(Exp))) 284 .bind("expr")), 285 Stm, Context); 286 if (const Stmt *S = findExprMutation(Ref)) 287 return S; 288 289 // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. 290 const auto Refs = match( 291 stmt(forEachDescendant( 292 varDecl( 293 hasType(nonConstReferenceType()), 294 hasInitializer(anyOf(equalsNode(Exp), 295 conditionalOperator(anyOf( 296 hasTrueExpression(equalsNode(Exp)), 297 hasFalseExpression(equalsNode(Exp)))))), 298 hasParent(declStmt().bind("stmt")), 299 // Don't follow the reference in range statement, we've handled 300 // that separately. 301 unless(hasParent(declStmt(hasParent( 302 cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt")))))))) 303 .bind("decl"))), 304 Stm, Context); 305 return findDeclMutation(Refs); 306 } 307 308 } // namespace clang 309