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