1e5dd7070Spatrick //===---------- ExprMutationAnalyzer.cpp ----------------------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
9a9ac8606Spatrick #include "clang/AST/Expr.h"
10a9ac8606Spatrick #include "clang/AST/OperationKinds.h"
11e5dd7070Spatrick #include "clang/ASTMatchers/ASTMatchFinder.h"
12a9ac8606Spatrick #include "clang/ASTMatchers/ASTMatchers.h"
13e5dd7070Spatrick #include "llvm/ADT/STLExtras.h"
14e5dd7070Spatrick
15e5dd7070Spatrick namespace clang {
16e5dd7070Spatrick using namespace ast_matchers;
17e5dd7070Spatrick
18e5dd7070Spatrick namespace {
19e5dd7070Spatrick
AST_MATCHER_P(LambdaExpr,hasCaptureInit,const Expr *,E)20e5dd7070Spatrick AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {
21e5dd7070Spatrick return llvm::is_contained(Node.capture_inits(), E);
22e5dd7070Spatrick }
23e5dd7070Spatrick
AST_MATCHER_P(CXXForRangeStmt,hasRangeStmt,ast_matchers::internal::Matcher<DeclStmt>,InnerMatcher)24e5dd7070Spatrick AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
25e5dd7070Spatrick ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
26e5dd7070Spatrick const DeclStmt *const Range = Node.getRangeStmt();
27e5dd7070Spatrick return InnerMatcher.matches(*Range, Finder, Builder);
28e5dd7070Spatrick }
29e5dd7070Spatrick
AST_MATCHER_P(Expr,maybeEvalCommaExpr,ast_matchers::internal::Matcher<Expr>,InnerMatcher)30a9ac8606Spatrick AST_MATCHER_P(Expr, maybeEvalCommaExpr, ast_matchers::internal::Matcher<Expr>,
31a9ac8606Spatrick InnerMatcher) {
32e5dd7070Spatrick const Expr *Result = &Node;
33e5dd7070Spatrick while (const auto *BOComma =
34e5dd7070Spatrick dyn_cast_or_null<BinaryOperator>(Result->IgnoreParens())) {
35e5dd7070Spatrick if (!BOComma->isCommaOp())
36e5dd7070Spatrick break;
37e5dd7070Spatrick Result = BOComma->getRHS();
38e5dd7070Spatrick }
39e5dd7070Spatrick return InnerMatcher.matches(*Result, Finder, Builder);
40e5dd7070Spatrick }
41e5dd7070Spatrick
AST_MATCHER_P(Stmt,canResolveToExpr,ast_matchers::internal::Matcher<Stmt>,InnerMatcher)42*12c85518Srobert AST_MATCHER_P(Stmt, canResolveToExpr, ast_matchers::internal::Matcher<Stmt>,
43a9ac8606Spatrick InnerMatcher) {
44*12c85518Srobert auto *Exp = dyn_cast<Expr>(&Node);
45*12c85518Srobert if (!Exp) {
46*12c85518Srobert return stmt().matches(Node, Finder, Builder);
47*12c85518Srobert }
48*12c85518Srobert
49a9ac8606Spatrick auto DerivedToBase = [](const ast_matchers::internal::Matcher<Expr> &Inner) {
50a9ac8606Spatrick return implicitCastExpr(anyOf(hasCastKind(CK_DerivedToBase),
51a9ac8606Spatrick hasCastKind(CK_UncheckedDerivedToBase)),
52a9ac8606Spatrick hasSourceExpression(Inner));
53a9ac8606Spatrick };
54a9ac8606Spatrick auto IgnoreDerivedToBase =
55a9ac8606Spatrick [&DerivedToBase](const ast_matchers::internal::Matcher<Expr> &Inner) {
56a9ac8606Spatrick return ignoringParens(expr(anyOf(Inner, DerivedToBase(Inner))));
57a9ac8606Spatrick };
58a9ac8606Spatrick
59a9ac8606Spatrick // The 'ConditionalOperator' matches on `<anything> ? <expr> : <expr>`.
60a9ac8606Spatrick // This matching must be recursive because `<expr>` can be anything resolving
61a9ac8606Spatrick // to the `InnerMatcher`, for example another conditional operator.
62a9ac8606Spatrick // The edge-case `BaseClass &b = <cond> ? DerivedVar1 : DerivedVar2;`
63a9ac8606Spatrick // is handled, too. The implicit cast happens outside of the conditional.
64a9ac8606Spatrick // This is matched by `IgnoreDerivedToBase(canResolveToExpr(InnerMatcher))`
65a9ac8606Spatrick // below.
66a9ac8606Spatrick auto const ConditionalOperator = conditionalOperator(anyOf(
67a9ac8606Spatrick hasTrueExpression(ignoringParens(canResolveToExpr(InnerMatcher))),
68a9ac8606Spatrick hasFalseExpression(ignoringParens(canResolveToExpr(InnerMatcher)))));
69a9ac8606Spatrick auto const ElvisOperator = binaryConditionalOperator(anyOf(
70a9ac8606Spatrick hasTrueExpression(ignoringParens(canResolveToExpr(InnerMatcher))),
71a9ac8606Spatrick hasFalseExpression(ignoringParens(canResolveToExpr(InnerMatcher)))));
72a9ac8606Spatrick
73a9ac8606Spatrick auto const ComplexMatcher = ignoringParens(
74a9ac8606Spatrick expr(anyOf(IgnoreDerivedToBase(InnerMatcher),
75a9ac8606Spatrick maybeEvalCommaExpr(IgnoreDerivedToBase(InnerMatcher)),
76a9ac8606Spatrick IgnoreDerivedToBase(ConditionalOperator),
77a9ac8606Spatrick IgnoreDerivedToBase(ElvisOperator))));
78a9ac8606Spatrick
79*12c85518Srobert return ComplexMatcher.matches(*Exp, Finder, Builder);
80a9ac8606Spatrick }
81a9ac8606Spatrick
82a9ac8606Spatrick // Similar to 'hasAnyArgument', but does not work because 'InitListExpr' does
83a9ac8606Spatrick // not have the 'arguments()' method.
AST_MATCHER_P(InitListExpr,hasAnyInit,ast_matchers::internal::Matcher<Expr>,InnerMatcher)84a9ac8606Spatrick AST_MATCHER_P(InitListExpr, hasAnyInit, ast_matchers::internal::Matcher<Expr>,
85a9ac8606Spatrick InnerMatcher) {
86a9ac8606Spatrick for (const Expr *Arg : Node.inits()) {
87a9ac8606Spatrick ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder);
88a9ac8606Spatrick if (InnerMatcher.matches(*Arg, Finder, &Result)) {
89a9ac8606Spatrick *Builder = std::move(Result);
90a9ac8606Spatrick return true;
91a9ac8606Spatrick }
92a9ac8606Spatrick }
93a9ac8606Spatrick return false;
94a9ac8606Spatrick }
95a9ac8606Spatrick
96e5dd7070Spatrick const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>
97e5dd7070Spatrick cxxTypeidExpr;
98e5dd7070Spatrick
AST_MATCHER(CXXTypeidExpr,isPotentiallyEvaluated)99e5dd7070Spatrick AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) {
100e5dd7070Spatrick return Node.isPotentiallyEvaluated();
101e5dd7070Spatrick }
102e5dd7070Spatrick
AST_MATCHER_P(GenericSelectionExpr,hasControllingExpr,ast_matchers::internal::Matcher<Expr>,InnerMatcher)103e5dd7070Spatrick AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr,
104e5dd7070Spatrick ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
105e5dd7070Spatrick return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder);
106e5dd7070Spatrick }
107e5dd7070Spatrick
__anona83f9f9a0402null108e5dd7070Spatrick const auto nonConstReferenceType = [] {
109e5dd7070Spatrick return hasUnqualifiedDesugaredType(
110e5dd7070Spatrick referenceType(pointee(unless(isConstQualified()))));
111e5dd7070Spatrick };
112e5dd7070Spatrick
__anona83f9f9a0502null113e5dd7070Spatrick const auto nonConstPointerType = [] {
114e5dd7070Spatrick return hasUnqualifiedDesugaredType(
115e5dd7070Spatrick pointerType(pointee(unless(isConstQualified()))));
116e5dd7070Spatrick };
117e5dd7070Spatrick
__anona83f9f9a0602null118e5dd7070Spatrick const auto isMoveOnly = [] {
119e5dd7070Spatrick return cxxRecordDecl(
120e5dd7070Spatrick hasMethod(cxxConstructorDecl(isMoveConstructor(), unless(isDeleted()))),
121e5dd7070Spatrick hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), unless(isDeleted()))),
122e5dd7070Spatrick unless(anyOf(hasMethod(cxxConstructorDecl(isCopyConstructor(),
123e5dd7070Spatrick unless(isDeleted()))),
124e5dd7070Spatrick hasMethod(cxxMethodDecl(isCopyAssignmentOperator(),
125e5dd7070Spatrick unless(isDeleted()))))));
126e5dd7070Spatrick };
127e5dd7070Spatrick
128e5dd7070Spatrick template <class T> struct NodeID;
129ec727ea7Spatrick template <> struct NodeID<Expr> { static constexpr StringRef value = "expr"; };
130ec727ea7Spatrick template <> struct NodeID<Decl> { static constexpr StringRef value = "decl"; };
131ec727ea7Spatrick constexpr StringRef NodeID<Expr>::value;
132ec727ea7Spatrick constexpr StringRef NodeID<Decl>::value;
133e5dd7070Spatrick
134e5dd7070Spatrick template <class T, class F = const Stmt *(ExprMutationAnalyzer::*)(const T *)>
tryEachMatch(ArrayRef<ast_matchers::BoundNodes> Matches,ExprMutationAnalyzer * Analyzer,F Finder)135e5dd7070Spatrick const Stmt *tryEachMatch(ArrayRef<ast_matchers::BoundNodes> Matches,
136e5dd7070Spatrick ExprMutationAnalyzer *Analyzer, F Finder) {
137e5dd7070Spatrick const StringRef ID = NodeID<T>::value;
138e5dd7070Spatrick for (const auto &Nodes : Matches) {
139e5dd7070Spatrick if (const Stmt *S = (Analyzer->*Finder)(Nodes.getNodeAs<T>(ID)))
140e5dd7070Spatrick return S;
141e5dd7070Spatrick }
142e5dd7070Spatrick return nullptr;
143e5dd7070Spatrick }
144e5dd7070Spatrick
145e5dd7070Spatrick } // namespace
146e5dd7070Spatrick
findMutation(const Expr * Exp)147e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) {
148e5dd7070Spatrick return findMutationMemoized(Exp,
149e5dd7070Spatrick {&ExprMutationAnalyzer::findDirectMutation,
150e5dd7070Spatrick &ExprMutationAnalyzer::findMemberMutation,
151e5dd7070Spatrick &ExprMutationAnalyzer::findArrayElementMutation,
152e5dd7070Spatrick &ExprMutationAnalyzer::findCastMutation,
153e5dd7070Spatrick &ExprMutationAnalyzer::findRangeLoopMutation,
154e5dd7070Spatrick &ExprMutationAnalyzer::findReferenceMutation,
155e5dd7070Spatrick &ExprMutationAnalyzer::findFunctionArgMutation},
156e5dd7070Spatrick Results);
157e5dd7070Spatrick }
158e5dd7070Spatrick
findMutation(const Decl * Dec)159e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findMutation(const Decl *Dec) {
160e5dd7070Spatrick return tryEachDeclRef(Dec, &ExprMutationAnalyzer::findMutation);
161e5dd7070Spatrick }
162e5dd7070Spatrick
findPointeeMutation(const Expr * Exp)163e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findPointeeMutation(const Expr *Exp) {
164e5dd7070Spatrick return findMutationMemoized(Exp, {/*TODO*/}, PointeeResults);
165e5dd7070Spatrick }
166e5dd7070Spatrick
findPointeeMutation(const Decl * Dec)167e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findPointeeMutation(const Decl *Dec) {
168e5dd7070Spatrick return tryEachDeclRef(Dec, &ExprMutationAnalyzer::findPointeeMutation);
169e5dd7070Spatrick }
170e5dd7070Spatrick
findMutationMemoized(const Expr * Exp,llvm::ArrayRef<MutationFinder> Finders,ResultMap & MemoizedResults)171e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findMutationMemoized(
172e5dd7070Spatrick const Expr *Exp, llvm::ArrayRef<MutationFinder> Finders,
173e5dd7070Spatrick ResultMap &MemoizedResults) {
174e5dd7070Spatrick const auto Memoized = MemoizedResults.find(Exp);
175e5dd7070Spatrick if (Memoized != MemoizedResults.end())
176e5dd7070Spatrick return Memoized->second;
177e5dd7070Spatrick
178e5dd7070Spatrick if (isUnevaluated(Exp))
179e5dd7070Spatrick return MemoizedResults[Exp] = nullptr;
180e5dd7070Spatrick
181e5dd7070Spatrick for (const auto &Finder : Finders) {
182e5dd7070Spatrick if (const Stmt *S = (this->*Finder)(Exp))
183e5dd7070Spatrick return MemoizedResults[Exp] = S;
184e5dd7070Spatrick }
185e5dd7070Spatrick
186e5dd7070Spatrick return MemoizedResults[Exp] = nullptr;
187e5dd7070Spatrick }
188e5dd7070Spatrick
tryEachDeclRef(const Decl * Dec,MutationFinder Finder)189e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::tryEachDeclRef(const Decl *Dec,
190e5dd7070Spatrick MutationFinder Finder) {
191e5dd7070Spatrick const auto Refs =
192e5dd7070Spatrick match(findAll(declRefExpr(to(equalsNode(Dec))).bind(NodeID<Expr>::value)),
193e5dd7070Spatrick Stm, Context);
194e5dd7070Spatrick for (const auto &RefNodes : Refs) {
195e5dd7070Spatrick const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);
196e5dd7070Spatrick if ((this->*Finder)(E))
197e5dd7070Spatrick return E;
198e5dd7070Spatrick }
199e5dd7070Spatrick return nullptr;
200e5dd7070Spatrick }
201e5dd7070Spatrick
isUnevaluated(const Stmt * Exp,const Stmt & Stm,ASTContext & Context)202*12c85518Srobert bool ExprMutationAnalyzer::isUnevaluated(const Stmt *Exp, const Stmt &Stm,
203*12c85518Srobert ASTContext &Context) {
204*12c85518Srobert return selectFirst<Stmt>(
205e5dd7070Spatrick NodeID<Expr>::value,
206e5dd7070Spatrick match(
207e5dd7070Spatrick findAll(
208*12c85518Srobert stmt(canResolveToExpr(equalsNode(Exp)),
209e5dd7070Spatrick anyOf(
210e5dd7070Spatrick // `Exp` is part of the underlying expression of
211e5dd7070Spatrick // decltype/typeof if it has an ancestor of
212e5dd7070Spatrick // typeLoc.
213e5dd7070Spatrick hasAncestor(typeLoc(unless(
214e5dd7070Spatrick hasAncestor(unaryExprOrTypeTraitExpr())))),
215e5dd7070Spatrick hasAncestor(expr(anyOf(
216e5dd7070Spatrick // `UnaryExprOrTypeTraitExpr` is unevaluated
217e5dd7070Spatrick // unless it's sizeof on VLA.
218e5dd7070Spatrick unaryExprOrTypeTraitExpr(unless(sizeOfExpr(
219e5dd7070Spatrick hasArgumentOfType(variableArrayType())))),
220e5dd7070Spatrick // `CXXTypeidExpr` is unevaluated unless it's
221e5dd7070Spatrick // applied to an expression of glvalue of
222e5dd7070Spatrick // polymorphic class type.
223e5dd7070Spatrick cxxTypeidExpr(
224e5dd7070Spatrick unless(isPotentiallyEvaluated())),
225e5dd7070Spatrick // The controlling expression of
226e5dd7070Spatrick // `GenericSelectionExpr` is unevaluated.
227e5dd7070Spatrick genericSelectionExpr(hasControllingExpr(
228e5dd7070Spatrick hasDescendant(equalsNode(Exp)))),
229e5dd7070Spatrick cxxNoexceptExpr())))))
230e5dd7070Spatrick .bind(NodeID<Expr>::value)),
231e5dd7070Spatrick Stm, Context)) != nullptr;
232e5dd7070Spatrick }
233e5dd7070Spatrick
isUnevaluated(const Expr * Exp)234*12c85518Srobert bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) {
235*12c85518Srobert return isUnevaluated(Exp, Stm, Context);
236*12c85518Srobert }
237*12c85518Srobert
238e5dd7070Spatrick const Stmt *
findExprMutation(ArrayRef<BoundNodes> Matches)239e5dd7070Spatrick ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {
240e5dd7070Spatrick return tryEachMatch<Expr>(Matches, this, &ExprMutationAnalyzer::findMutation);
241e5dd7070Spatrick }
242e5dd7070Spatrick
243e5dd7070Spatrick const Stmt *
findDeclMutation(ArrayRef<BoundNodes> Matches)244e5dd7070Spatrick ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {
245e5dd7070Spatrick return tryEachMatch<Decl>(Matches, this, &ExprMutationAnalyzer::findMutation);
246e5dd7070Spatrick }
247e5dd7070Spatrick
findExprPointeeMutation(ArrayRef<ast_matchers::BoundNodes> Matches)248e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findExprPointeeMutation(
249e5dd7070Spatrick ArrayRef<ast_matchers::BoundNodes> Matches) {
250e5dd7070Spatrick return tryEachMatch<Expr>(Matches, this,
251e5dd7070Spatrick &ExprMutationAnalyzer::findPointeeMutation);
252e5dd7070Spatrick }
253e5dd7070Spatrick
findDeclPointeeMutation(ArrayRef<ast_matchers::BoundNodes> Matches)254e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findDeclPointeeMutation(
255e5dd7070Spatrick ArrayRef<ast_matchers::BoundNodes> Matches) {
256e5dd7070Spatrick return tryEachMatch<Decl>(Matches, this,
257e5dd7070Spatrick &ExprMutationAnalyzer::findPointeeMutation);
258e5dd7070Spatrick }
259e5dd7070Spatrick
findDirectMutation(const Expr * Exp)260e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
261e5dd7070Spatrick // LHS of any assignment operators.
262ec727ea7Spatrick const auto AsAssignmentLhs = binaryOperator(
263a9ac8606Spatrick isAssignmentOperator(), hasLHS(canResolveToExpr(equalsNode(Exp))));
264e5dd7070Spatrick
265e5dd7070Spatrick // Operand of increment/decrement operators.
266e5dd7070Spatrick const auto AsIncDecOperand =
267e5dd7070Spatrick unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
268a9ac8606Spatrick hasUnaryOperand(canResolveToExpr(equalsNode(Exp))));
269e5dd7070Spatrick
270e5dd7070Spatrick // Invoking non-const member function.
271e5dd7070Spatrick // A member function is assumed to be non-const when it is unresolved.
272e5dd7070Spatrick const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
273a9ac8606Spatrick
274a9ac8606Spatrick const auto AsNonConstThis = expr(anyOf(
275a9ac8606Spatrick cxxMemberCallExpr(callee(NonConstMethod),
276a9ac8606Spatrick on(canResolveToExpr(equalsNode(Exp)))),
277e5dd7070Spatrick cxxOperatorCallExpr(callee(NonConstMethod),
278a9ac8606Spatrick hasArgument(0, canResolveToExpr(equalsNode(Exp)))),
279a9ac8606Spatrick // In case of a templated type, calling overloaded operators is not
280a9ac8606Spatrick // resolved and modelled as `binaryOperator` on a dependent type.
281a9ac8606Spatrick // Such instances are considered a modification, because they can modify
282a9ac8606Spatrick // in different instantiations of the template.
283a9ac8606Spatrick binaryOperator(hasEitherOperand(
284a9ac8606Spatrick allOf(ignoringImpCasts(canResolveToExpr(equalsNode(Exp))),
285a9ac8606Spatrick isTypeDependent()))),
286a9ac8606Spatrick // Within class templates and member functions the member expression might
287a9ac8606Spatrick // not be resolved. In that case, the `callExpr` is considered to be a
288a9ac8606Spatrick // modification.
289a9ac8606Spatrick callExpr(
290a9ac8606Spatrick callee(expr(anyOf(unresolvedMemberExpr(hasObjectExpression(
291a9ac8606Spatrick canResolveToExpr(equalsNode(Exp)))),
292a9ac8606Spatrick cxxDependentScopeMemberExpr(hasObjectExpression(
293a9ac8606Spatrick canResolveToExpr(equalsNode(Exp)))))))),
294a9ac8606Spatrick // Match on a call to a known method, but the call itself is type
295a9ac8606Spatrick // dependent (e.g. `vector<T> v; v.push(T{});` in a templated function).
296a9ac8606Spatrick callExpr(allOf(isTypeDependent(),
297a9ac8606Spatrick callee(memberExpr(hasDeclaration(NonConstMethod),
298a9ac8606Spatrick hasObjectExpression(canResolveToExpr(
299a9ac8606Spatrick equalsNode(Exp)))))))));
300e5dd7070Spatrick
301e5dd7070Spatrick // Taking address of 'Exp'.
302e5dd7070Spatrick // We're assuming 'Exp' is mutated as soon as its address is taken, though in
303e5dd7070Spatrick // theory we can follow the pointer and see whether it escaped `Stm` or is
304e5dd7070Spatrick // dereferenced and then mutated. This is left for future improvements.
305e5dd7070Spatrick const auto AsAmpersandOperand =
306e5dd7070Spatrick unaryOperator(hasOperatorName("&"),
307e5dd7070Spatrick // A NoOp implicit cast is adding const.
308e5dd7070Spatrick unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
309a9ac8606Spatrick hasUnaryOperand(canResolveToExpr(equalsNode(Exp))));
310e5dd7070Spatrick const auto AsPointerFromArrayDecay =
311e5dd7070Spatrick castExpr(hasCastKind(CK_ArrayToPointerDecay),
312e5dd7070Spatrick unless(hasParent(arraySubscriptExpr())),
313a9ac8606Spatrick has(canResolveToExpr(equalsNode(Exp))));
314e5dd7070Spatrick // Treat calling `operator->()` of move-only classes as taking address.
315e5dd7070Spatrick // These are typically smart pointers with unique ownership so we treat
316e5dd7070Spatrick // mutation of pointee as mutation of the smart pointer itself.
317a9ac8606Spatrick const auto AsOperatorArrowThis = cxxOperatorCallExpr(
318a9ac8606Spatrick hasOverloadedOperatorName("->"),
319a9ac8606Spatrick callee(
320a9ac8606Spatrick cxxMethodDecl(ofClass(isMoveOnly()), returns(nonConstPointerType()))),
321a9ac8606Spatrick argumentCountIs(1), hasArgument(0, canResolveToExpr(equalsNode(Exp))));
322e5dd7070Spatrick
323e5dd7070Spatrick // Used as non-const-ref argument when calling a function.
324e5dd7070Spatrick // An argument is assumed to be non-const-ref when the function is unresolved.
325e5dd7070Spatrick // Instantiated template functions are not handled here but in
326e5dd7070Spatrick // findFunctionArgMutation which has additional smarts for handling forwarding
327e5dd7070Spatrick // references.
328a9ac8606Spatrick const auto NonConstRefParam = forEachArgumentWithParamType(
329a9ac8606Spatrick anyOf(canResolveToExpr(equalsNode(Exp)),
330a9ac8606Spatrick memberExpr(hasObjectExpression(canResolveToExpr(equalsNode(Exp))))),
331a9ac8606Spatrick nonConstReferenceType());
332e5dd7070Spatrick const auto NotInstantiated = unless(hasDeclaration(isInstantiated()));
333a9ac8606Spatrick const auto TypeDependentCallee =
334a9ac8606Spatrick callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(),
335a9ac8606Spatrick cxxDependentScopeMemberExpr(),
336a9ac8606Spatrick hasType(templateTypeParmType()), isTypeDependent())));
337a9ac8606Spatrick
338e5dd7070Spatrick const auto AsNonConstRefArg = anyOf(
339e5dd7070Spatrick callExpr(NonConstRefParam, NotInstantiated),
340e5dd7070Spatrick cxxConstructExpr(NonConstRefParam, NotInstantiated),
341a9ac8606Spatrick callExpr(TypeDependentCallee,
342a9ac8606Spatrick hasAnyArgument(canResolveToExpr(equalsNode(Exp)))),
343a9ac8606Spatrick cxxUnresolvedConstructExpr(
344a9ac8606Spatrick hasAnyArgument(canResolveToExpr(equalsNode(Exp)))),
345a9ac8606Spatrick // Previous False Positive in the following Code:
346a9ac8606Spatrick // `template <typename T> void f() { int i = 42; new Type<T>(i); }`
347a9ac8606Spatrick // Where the constructor of `Type` takes its argument as reference.
348a9ac8606Spatrick // The AST does not resolve in a `cxxConstructExpr` because it is
349a9ac8606Spatrick // type-dependent.
350a9ac8606Spatrick parenListExpr(hasDescendant(expr(canResolveToExpr(equalsNode(Exp))))),
351a9ac8606Spatrick // If the initializer is for a reference type, there is no cast for
352a9ac8606Spatrick // the variable. Values are cast to RValue first.
353a9ac8606Spatrick initListExpr(hasAnyInit(expr(canResolveToExpr(equalsNode(Exp))))));
354e5dd7070Spatrick
355e5dd7070Spatrick // Captured by a lambda by reference.
356e5dd7070Spatrick // If we're initializing a capture with 'Exp' directly then we're initializing
357e5dd7070Spatrick // a reference capture.
358e5dd7070Spatrick // For value captures there will be an ImplicitCastExpr <LValueToRValue>.
359e5dd7070Spatrick const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));
360e5dd7070Spatrick
361e5dd7070Spatrick // Returned as non-const-ref.
362e5dd7070Spatrick // If we're returning 'Exp' directly then it's returned as non-const-ref.
363e5dd7070Spatrick // For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
364e5dd7070Spatrick // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
365e5dd7070Spatrick // adding const.)
366a9ac8606Spatrick const auto AsNonConstRefReturn =
367a9ac8606Spatrick returnStmt(hasReturnValue(canResolveToExpr(equalsNode(Exp))));
368a9ac8606Spatrick
369a9ac8606Spatrick // It is used as a non-const-reference for initalizing a range-for loop.
370a9ac8606Spatrick const auto AsNonConstRefRangeInit = cxxForRangeStmt(
371a9ac8606Spatrick hasRangeInit(declRefExpr(allOf(canResolveToExpr(equalsNode(Exp)),
372a9ac8606Spatrick hasType(nonConstReferenceType())))));
373e5dd7070Spatrick
374ec727ea7Spatrick const auto Matches = match(
375a9ac8606Spatrick traverse(TK_AsIs,
376a9ac8606Spatrick findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand,
377a9ac8606Spatrick AsNonConstThis, AsAmpersandOperand,
378a9ac8606Spatrick AsPointerFromArrayDecay, AsOperatorArrowThis,
379a9ac8606Spatrick AsNonConstRefArg, AsLambdaRefCaptureInit,
380a9ac8606Spatrick AsNonConstRefReturn, AsNonConstRefRangeInit))
381ec727ea7Spatrick .bind("stmt"))),
382e5dd7070Spatrick Stm, Context);
383e5dd7070Spatrick return selectFirst<Stmt>("stmt", Matches);
384e5dd7070Spatrick }
385e5dd7070Spatrick
findMemberMutation(const Expr * Exp)386e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) {
387e5dd7070Spatrick // Check whether any member of 'Exp' is mutated.
388e5dd7070Spatrick const auto MemberExprs =
389a9ac8606Spatrick match(findAll(expr(anyOf(memberExpr(hasObjectExpression(
390a9ac8606Spatrick canResolveToExpr(equalsNode(Exp)))),
391a9ac8606Spatrick cxxDependentScopeMemberExpr(hasObjectExpression(
392a9ac8606Spatrick canResolveToExpr(equalsNode(Exp))))))
393e5dd7070Spatrick .bind(NodeID<Expr>::value)),
394e5dd7070Spatrick Stm, Context);
395e5dd7070Spatrick return findExprMutation(MemberExprs);
396e5dd7070Spatrick }
397e5dd7070Spatrick
findArrayElementMutation(const Expr * Exp)398e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) {
399e5dd7070Spatrick // Check whether any element of an array is mutated.
400a9ac8606Spatrick const auto SubscriptExprs =
401a9ac8606Spatrick match(findAll(arraySubscriptExpr(
402a9ac8606Spatrick anyOf(hasBase(canResolveToExpr(equalsNode(Exp))),
403a9ac8606Spatrick hasBase(implicitCastExpr(
404a9ac8606Spatrick allOf(hasCastKind(CK_ArrayToPointerDecay),
405a9ac8606Spatrick hasSourceExpression(canResolveToExpr(
406a9ac8606Spatrick equalsNode(Exp))))))))
407e5dd7070Spatrick .bind(NodeID<Expr>::value)),
408e5dd7070Spatrick Stm, Context);
409e5dd7070Spatrick return findExprMutation(SubscriptExprs);
410e5dd7070Spatrick }
411e5dd7070Spatrick
findCastMutation(const Expr * Exp)412e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) {
413a9ac8606Spatrick // If the 'Exp' is explicitly casted to a non-const reference type the
414a9ac8606Spatrick // 'Exp' is considered to be modified.
415a9ac8606Spatrick const auto ExplicitCast = match(
416a9ac8606Spatrick findAll(
417a9ac8606Spatrick stmt(castExpr(hasSourceExpression(canResolveToExpr(equalsNode(Exp))),
418a9ac8606Spatrick explicitCastExpr(
419a9ac8606Spatrick hasDestinationType(nonConstReferenceType()))))
420a9ac8606Spatrick .bind("stmt")),
421a9ac8606Spatrick Stm, Context);
422a9ac8606Spatrick
423a9ac8606Spatrick if (const auto *CastStmt = selectFirst<Stmt>("stmt", ExplicitCast))
424a9ac8606Spatrick return CastStmt;
425a9ac8606Spatrick
426e5dd7070Spatrick // If 'Exp' is casted to any non-const reference type, check the castExpr.
427a9ac8606Spatrick const auto Casts = match(
428a9ac8606Spatrick findAll(
429a9ac8606Spatrick expr(castExpr(hasSourceExpression(canResolveToExpr(equalsNode(Exp))),
430a9ac8606Spatrick anyOf(explicitCastExpr(
431a9ac8606Spatrick hasDestinationType(nonConstReferenceType())),
432e5dd7070Spatrick implicitCastExpr(hasImplicitDestinationType(
433a9ac8606Spatrick nonConstReferenceType())))))
434e5dd7070Spatrick .bind(NodeID<Expr>::value)),
435e5dd7070Spatrick Stm, Context);
436a9ac8606Spatrick
437e5dd7070Spatrick if (const Stmt *S = findExprMutation(Casts))
438e5dd7070Spatrick return S;
439e5dd7070Spatrick // Treat std::{move,forward} as cast.
440e5dd7070Spatrick const auto Calls =
441e5dd7070Spatrick match(findAll(callExpr(callee(namedDecl(
442e5dd7070Spatrick hasAnyName("::std::move", "::std::forward"))),
443a9ac8606Spatrick hasArgument(0, canResolveToExpr(equalsNode(Exp))))
444e5dd7070Spatrick .bind("expr")),
445e5dd7070Spatrick Stm, Context);
446e5dd7070Spatrick return findExprMutation(Calls);
447e5dd7070Spatrick }
448e5dd7070Spatrick
findRangeLoopMutation(const Expr * Exp)449e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
450a9ac8606Spatrick // Keep the ordering for the specific initialization matches to happen first,
451a9ac8606Spatrick // because it is cheaper to match all potential modifications of the loop
452a9ac8606Spatrick // variable.
453a9ac8606Spatrick
454a9ac8606Spatrick // The range variable is a reference to a builtin array. In that case the
455a9ac8606Spatrick // array is considered modified if the loop-variable is a non-const reference.
456a9ac8606Spatrick const auto DeclStmtToNonRefToArray = declStmt(hasSingleDecl(varDecl(hasType(
457a9ac8606Spatrick hasUnqualifiedDesugaredType(referenceType(pointee(arrayType())))))));
458*12c85518Srobert const auto RefToArrayRefToElements =
459*12c85518Srobert match(findAll(stmt(cxxForRangeStmt(
460*12c85518Srobert hasLoopVariable(
461*12c85518Srobert varDecl(anyOf(hasType(nonConstReferenceType()),
462*12c85518Srobert hasType(nonConstPointerType())))
463a9ac8606Spatrick .bind(NodeID<Decl>::value)),
464a9ac8606Spatrick hasRangeStmt(DeclStmtToNonRefToArray),
465a9ac8606Spatrick hasRangeInit(canResolveToExpr(equalsNode(Exp)))))
466a9ac8606Spatrick .bind("stmt")),
467a9ac8606Spatrick Stm, Context);
468a9ac8606Spatrick
469a9ac8606Spatrick if (const auto *BadRangeInitFromArray =
470a9ac8606Spatrick selectFirst<Stmt>("stmt", RefToArrayRefToElements))
471a9ac8606Spatrick return BadRangeInitFromArray;
472a9ac8606Spatrick
473a9ac8606Spatrick // Small helper to match special cases in range-for loops.
474a9ac8606Spatrick //
475a9ac8606Spatrick // It is possible that containers do not provide a const-overload for their
476a9ac8606Spatrick // iterator accessors. If this is the case, the variable is used non-const
477a9ac8606Spatrick // no matter what happens in the loop. This requires special detection as it
478a9ac8606Spatrick // is then faster to find all mutations of the loop variable.
479a9ac8606Spatrick // It aims at a different modification as well.
480a9ac8606Spatrick const auto HasAnyNonConstIterator =
481a9ac8606Spatrick anyOf(allOf(hasMethod(allOf(hasName("begin"), unless(isConst()))),
482a9ac8606Spatrick unless(hasMethod(allOf(hasName("begin"), isConst())))),
483a9ac8606Spatrick allOf(hasMethod(allOf(hasName("end"), unless(isConst()))),
484a9ac8606Spatrick unless(hasMethod(allOf(hasName("end"), isConst())))));
485a9ac8606Spatrick
486a9ac8606Spatrick const auto DeclStmtToNonConstIteratorContainer = declStmt(
487a9ac8606Spatrick hasSingleDecl(varDecl(hasType(hasUnqualifiedDesugaredType(referenceType(
488a9ac8606Spatrick pointee(hasDeclaration(cxxRecordDecl(HasAnyNonConstIterator)))))))));
489a9ac8606Spatrick
490a9ac8606Spatrick const auto RefToContainerBadIterators =
491a9ac8606Spatrick match(findAll(stmt(cxxForRangeStmt(allOf(
492a9ac8606Spatrick hasRangeStmt(DeclStmtToNonConstIteratorContainer),
493a9ac8606Spatrick hasRangeInit(canResolveToExpr(equalsNode(Exp))))))
494a9ac8606Spatrick .bind("stmt")),
495a9ac8606Spatrick Stm, Context);
496a9ac8606Spatrick
497a9ac8606Spatrick if (const auto *BadIteratorsContainer =
498a9ac8606Spatrick selectFirst<Stmt>("stmt", RefToContainerBadIterators))
499a9ac8606Spatrick return BadIteratorsContainer;
500a9ac8606Spatrick
501e5dd7070Spatrick // If range for looping over 'Exp' with a non-const reference loop variable,
502e5dd7070Spatrick // check all declRefExpr of the loop variable.
503e5dd7070Spatrick const auto LoopVars =
504e5dd7070Spatrick match(findAll(cxxForRangeStmt(
505e5dd7070Spatrick hasLoopVariable(varDecl(hasType(nonConstReferenceType()))
506e5dd7070Spatrick .bind(NodeID<Decl>::value)),
507a9ac8606Spatrick hasRangeInit(canResolveToExpr(equalsNode(Exp))))),
508e5dd7070Spatrick Stm, Context);
509e5dd7070Spatrick return findDeclMutation(LoopVars);
510e5dd7070Spatrick }
511e5dd7070Spatrick
findReferenceMutation(const Expr * Exp)512e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) {
513e5dd7070Spatrick // Follow non-const reference returned by `operator*()` of move-only classes.
514e5dd7070Spatrick // These are typically smart pointers with unique ownership so we treat
515e5dd7070Spatrick // mutation of pointee as mutation of the smart pointer itself.
516e5dd7070Spatrick const auto Ref =
517e5dd7070Spatrick match(findAll(cxxOperatorCallExpr(
518e5dd7070Spatrick hasOverloadedOperatorName("*"),
519e5dd7070Spatrick callee(cxxMethodDecl(ofClass(isMoveOnly()),
520e5dd7070Spatrick returns(nonConstReferenceType()))),
521a9ac8606Spatrick argumentCountIs(1),
522a9ac8606Spatrick hasArgument(0, canResolveToExpr(equalsNode(Exp))))
523e5dd7070Spatrick .bind(NodeID<Expr>::value)),
524e5dd7070Spatrick Stm, Context);
525e5dd7070Spatrick if (const Stmt *S = findExprMutation(Ref))
526e5dd7070Spatrick return S;
527e5dd7070Spatrick
528e5dd7070Spatrick // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
529e5dd7070Spatrick const auto Refs = match(
530e5dd7070Spatrick stmt(forEachDescendant(
531e5dd7070Spatrick varDecl(
532e5dd7070Spatrick hasType(nonConstReferenceType()),
533a9ac8606Spatrick hasInitializer(anyOf(canResolveToExpr(equalsNode(Exp)),
534a9ac8606Spatrick memberExpr(hasObjectExpression(
535a9ac8606Spatrick canResolveToExpr(equalsNode(Exp)))))),
536e5dd7070Spatrick hasParent(declStmt().bind("stmt")),
537a9ac8606Spatrick // Don't follow the reference in range statement, we've
538a9ac8606Spatrick // handled that separately.
539e5dd7070Spatrick unless(hasParent(declStmt(hasParent(
540e5dd7070Spatrick cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt"))))))))
541e5dd7070Spatrick .bind(NodeID<Decl>::value))),
542e5dd7070Spatrick Stm, Context);
543e5dd7070Spatrick return findDeclMutation(Refs);
544e5dd7070Spatrick }
545e5dd7070Spatrick
findFunctionArgMutation(const Expr * Exp)546e5dd7070Spatrick const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) {
547e5dd7070Spatrick const auto NonConstRefParam = forEachArgumentWithParam(
548a9ac8606Spatrick canResolveToExpr(equalsNode(Exp)),
549e5dd7070Spatrick parmVarDecl(hasType(nonConstReferenceType())).bind("parm"));
550e5dd7070Spatrick const auto IsInstantiated = hasDeclaration(isInstantiated());
551e5dd7070Spatrick const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));
552e5dd7070Spatrick const auto Matches = match(
553ec727ea7Spatrick traverse(
554a9ac8606Spatrick TK_AsIs,
555ec727ea7Spatrick findAll(
556ec727ea7Spatrick expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl,
557e5dd7070Spatrick unless(callee(namedDecl(hasAnyName(
558e5dd7070Spatrick "::std::move", "::std::forward"))))),
559e5dd7070Spatrick cxxConstructExpr(NonConstRefParam, IsInstantiated,
560e5dd7070Spatrick FuncDecl)))
561ec727ea7Spatrick .bind(NodeID<Expr>::value))),
562e5dd7070Spatrick Stm, Context);
563e5dd7070Spatrick for (const auto &Nodes : Matches) {
564e5dd7070Spatrick const auto *Exp = Nodes.getNodeAs<Expr>(NodeID<Expr>::value);
565e5dd7070Spatrick const auto *Func = Nodes.getNodeAs<FunctionDecl>("func");
566e5dd7070Spatrick if (!Func->getBody() || !Func->getPrimaryTemplate())
567e5dd7070Spatrick return Exp;
568e5dd7070Spatrick
569e5dd7070Spatrick const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm");
570e5dd7070Spatrick const ArrayRef<ParmVarDecl *> AllParams =
571e5dd7070Spatrick Func->getPrimaryTemplate()->getTemplatedDecl()->parameters();
572e5dd7070Spatrick QualType ParmType =
573e5dd7070Spatrick AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(),
574e5dd7070Spatrick AllParams.size() - 1)]
575e5dd7070Spatrick ->getType();
576e5dd7070Spatrick if (const auto *T = ParmType->getAs<PackExpansionType>())
577e5dd7070Spatrick ParmType = T->getPattern();
578e5dd7070Spatrick
579e5dd7070Spatrick // If param type is forwarding reference, follow into the function
580e5dd7070Spatrick // definition and see whether the param is mutated inside.
581e5dd7070Spatrick if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) {
582e5dd7070Spatrick if (!RefType->getPointeeType().getQualifiers() &&
583e5dd7070Spatrick RefType->getPointeeType()->getAs<TemplateTypeParmType>()) {
584e5dd7070Spatrick std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer =
585e5dd7070Spatrick FuncParmAnalyzer[Func];
586e5dd7070Spatrick if (!Analyzer)
587e5dd7070Spatrick Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context));
588e5dd7070Spatrick if (Analyzer->findMutation(Parm))
589e5dd7070Spatrick return Exp;
590e5dd7070Spatrick continue;
591e5dd7070Spatrick }
592e5dd7070Spatrick }
593e5dd7070Spatrick // Not forwarding reference.
594e5dd7070Spatrick return Exp;
595e5dd7070Spatrick }
596e5dd7070Spatrick return nullptr;
597e5dd7070Spatrick }
598e5dd7070Spatrick
FunctionParmMutationAnalyzer(const FunctionDecl & Func,ASTContext & Context)599e5dd7070Spatrick FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(
600e5dd7070Spatrick const FunctionDecl &Func, ASTContext &Context)
601e5dd7070Spatrick : BodyAnalyzer(*Func.getBody(), Context) {
602e5dd7070Spatrick if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) {
603e5dd7070Spatrick // CXXCtorInitializer might also mutate Param but they're not part of
604e5dd7070Spatrick // function body, check them eagerly here since they're typically trivial.
605e5dd7070Spatrick for (const CXXCtorInitializer *Init : Ctor->inits()) {
606e5dd7070Spatrick ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context);
607e5dd7070Spatrick for (const ParmVarDecl *Parm : Ctor->parameters()) {
608e5dd7070Spatrick if (Results.find(Parm) != Results.end())
609e5dd7070Spatrick continue;
610e5dd7070Spatrick if (const Stmt *S = InitAnalyzer.findMutation(Parm))
611e5dd7070Spatrick Results[Parm] = S;
612e5dd7070Spatrick }
613e5dd7070Spatrick }
614e5dd7070Spatrick }
615e5dd7070Spatrick }
616e5dd7070Spatrick
617e5dd7070Spatrick const Stmt *
findMutation(const ParmVarDecl * Parm)618e5dd7070Spatrick FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) {
619e5dd7070Spatrick const auto Memoized = Results.find(Parm);
620e5dd7070Spatrick if (Memoized != Results.end())
621e5dd7070Spatrick return Memoized->second;
622e5dd7070Spatrick
623e5dd7070Spatrick if (const Stmt *S = BodyAnalyzer.findMutation(Parm))
624e5dd7070Spatrick return Results[Parm] = S;
625e5dd7070Spatrick
626e5dd7070Spatrick return Results[Parm] = nullptr;
627e5dd7070Spatrick }
628e5dd7070Spatrick
629e5dd7070Spatrick } // namespace clang
630