xref: /openbsd-src/gnu/llvm/clang/lib/Analysis/ExprMutationAnalyzer.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
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