xref: /llvm-project/clang/lib/Analysis/ExprMutationAnalyzer.cpp (revision 2946cd701067404b99c39fb29dc9c74bd7193eb3)
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