[clang-tools-extra] r335736 - [clang-tidy] Add ExprMutationAnalyzer, that analyzes whether an expression is mutated within a statement.

Alexander Kornienko via cfe-commits cfe-commits at lists.llvm.org
Wed Jun 27 07:30:55 PDT 2018


Author: alexfh
Date: Wed Jun 27 07:30:55 2018
New Revision: 335736

URL: http://llvm.org/viewvc/llvm-project?rev=335736&view=rev
Log:
[clang-tidy] Add ExprMutationAnalyzer, that analyzes whether an expression is mutated within a statement.

Summary:
(Originally started as a clang-tidy check but there's already D45444 so shifted to just adding ExprMutationAnalyzer)

`ExprMutationAnalyzer` is a generally useful helper that can be used in different clang-tidy checks for checking whether a given expression is (potentially) mutated within a statement (typically the enclosing compound statement.)

This is a more general and more powerful/accurate version of isOnlyUsedAsConst, which is used in ForRangeCopyCheck, UnnecessaryCopyInitialization.

It should also be possible to construct checks like D45444 (suggest adding const to variable declaration) or https://bugs.llvm.org/show_bug.cgi?id=21981 (suggest adding const to member function) using this helper function.

This function is tested by itself and is intended to stay generally useful instead of tied to any particular check.

Reviewers: hokein, alexfh, aaron.ballman, ilya-biryukov, george.karpenkov

Reviewed By: aaron.ballman

Subscribers: lebedev.ri, shuaiwang, rnkovacs, hokein, alexfh, aaron.ballman, a.sidorin, Eugene.Zelenko, xazax.hun, JonasToth, klimek, mgorny, cfe-commits

Tags: #clang-tools-extra

Patch by Shuai Wang.

Differential Revision: https://reviews.llvm.org/D45679

Added:
    clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.cpp
    clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.h
    clang-tools-extra/trunk/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
Modified:
    clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt
    clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt

Modified: clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt?rev=335736&r1=335735&r2=335736&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt Wed Jun 27 07:30:55 2018
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS support)
 add_clang_library(clangTidyUtils
   ASTUtils.cpp
   DeclRefExprUtils.cpp
+  ExprMutationAnalyzer.cpp
   ExprSequence.cpp
   FixItHintUtils.cpp
   HeaderFileExtensionsUtils.cpp

Added: clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.cpp?rev=335736&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.cpp (added)
+++ clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.cpp Wed Jun 27 07:30:55 2018
@@ -0,0 +1,260 @@
+//===---------- ExprMutationAnalyzer.cpp - clang-tidy ---------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "ExprMutationAnalyzer.h"
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/STLExtras.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+using namespace ast_matchers;
+
+namespace {
+
+AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) {
+  return llvm::is_contained(Node.capture_inits(), E);
+}
+
+AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
+              ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
+  const DeclStmt *const Range = Node.getRangeStmt();
+  return InnerMatcher.matches(*Range, Finder, Builder);
+}
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>
+    cxxTypeidExpr;
+
+AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) {
+  return Node.isPotentiallyEvaluated();
+}
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXNoexceptExpr>
+    cxxNoexceptExpr;
+
+const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt,
+                                                          GenericSelectionExpr>
+    genericSelectionExpr;
+
+AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr,
+              ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
+  return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder);
+}
+
+const auto nonConstReferenceType = [] {
+  return referenceType(pointee(unless(isConstQualified())));
+};
+
+} // namespace
+
+const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) {
+  const auto Memoized = Results.find(Exp);
+  if (Memoized != Results.end())
+    return Memoized->second;
+
+  if (isUnevaluated(Exp))
+    return Results[Exp] = nullptr;
+
+  for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation,
+                             &ExprMutationAnalyzer::findMemberMutation,
+                             &ExprMutationAnalyzer::findArrayElementMutation,
+                             &ExprMutationAnalyzer::findCastMutation,
+                             &ExprMutationAnalyzer::findRangeLoopMutation,
+                             &ExprMutationAnalyzer::findReferenceMutation}) {
+    if (const Stmt *S = (this->*Finder)(Exp))
+      return Results[Exp] = S;
+  }
+
+  return Results[Exp] = nullptr;
+}
+
+bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) {
+  return selectFirst<Expr>(
+             "expr",
+             match(
+                 findAll(
+                     expr(equalsNode(Exp),
+                          anyOf(
+                              // `Exp` is part of the underlying expression of
+                              // decltype/typeof if it has an ancestor of
+                              // typeLoc.
+                              hasAncestor(typeLoc(unless(
+                                  hasAncestor(unaryExprOrTypeTraitExpr())))),
+                              hasAncestor(expr(anyOf(
+                                  // `UnaryExprOrTypeTraitExpr` is unevaluated
+                                  // unless it's sizeof on VLA.
+                                  unaryExprOrTypeTraitExpr(unless(sizeOfExpr(
+                                      hasArgumentOfType(variableArrayType())))),
+                                  // `CXXTypeidExpr` is unevaluated unless it's
+                                  // applied to an expression of glvalue of
+                                  // polymorphic class type.
+                                  cxxTypeidExpr(
+                                      unless(isPotentiallyEvaluated())),
+                                  // The controlling expression of
+                                  // `GenericSelectionExpr` is unevaluated.
+                                  genericSelectionExpr(hasControllingExpr(
+                                      hasDescendant(equalsNode(Exp)))),
+                                  cxxNoexceptExpr())))))
+                         .bind("expr")),
+                 *Stm, *Context)) != nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findExprMutation(ArrayRef<BoundNodes> Matches) {
+  for (const auto &Nodes : Matches) {
+    if (const Stmt *S = findMutation(Nodes.getNodeAs<Expr>("expr")))
+      return S;
+  }
+  return nullptr;
+}
+
+const Stmt *
+ExprMutationAnalyzer::findDeclMutation(ArrayRef<BoundNodes> Matches) {
+  for (const auto &DeclNodes : Matches) {
+    if (const Stmt *S = findDeclMutation(DeclNodes.getNodeAs<Decl>("decl")))
+      return S;
+  }
+  return nullptr;
+}
+
+const Stmt *ExprMutationAnalyzer::findDeclMutation(const Decl *Dec) {
+  const auto Refs = match(
+      findAll(declRefExpr(to(equalsNode(Dec))).bind("expr")), *Stm, *Context);
+  for (const auto &RefNodes : Refs) {
+    const auto *E = RefNodes.getNodeAs<Expr>("expr");
+    if (findMutation(E))
+      return E;
+  }
+  return nullptr;
+}
+
+const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
+  // LHS of any assignment operators.
+  const auto AsAssignmentLhs =
+      binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp)));
+
+  // Operand of increment/decrement operators.
+  const auto AsIncDecOperand =
+      unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
+                    hasUnaryOperand(equalsNode(Exp)));
+
+  // Invoking non-const member function.
+  const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
+  const auto AsNonConstThis =
+      expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))),
+                 cxxOperatorCallExpr(callee(NonConstMethod),
+                                     hasArgument(0, equalsNode(Exp)))));
+
+  // Taking address of 'Exp'.
+  // We're assuming 'Exp' is mutated as soon as its address is taken, though in
+  // theory we can follow the pointer and see whether it escaped `Stm` or is
+  // dereferenced and then mutated. This is left for future improvements.
+  const auto AsAmpersandOperand =
+      unaryOperator(hasOperatorName("&"),
+                    // A NoOp implicit cast is adding const.
+                    unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
+                    hasUnaryOperand(equalsNode(Exp)));
+  const auto AsPointerFromArrayDecay =
+      castExpr(hasCastKind(CK_ArrayToPointerDecay),
+               unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp)));
+
+  // Used as non-const-ref argument when calling a function.
+  const auto NonConstRefParam = forEachArgumentWithParam(
+      equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
+  const auto AsNonConstRefArg =
+      anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam));
+
+  // Captured by a lambda by reference.
+  // If we're initializing a capture with 'Exp' directly then we're initializing
+  // a reference capture.
+  // For value captures there will be an ImplicitCastExpr <LValueToRValue>.
+  const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp));
+
+  // Returned as non-const-ref.
+  // If we're returning 'Exp' directly then it's returned as non-const-ref.
+  // For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
+  // For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
+  // adding const.)
+  const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp)));
+
+  const auto Matches =
+      match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
+                               AsAmpersandOperand, AsPointerFromArrayDecay,
+                               AsNonConstRefArg, AsLambdaRefCaptureInit,
+                               AsNonConstRefReturn))
+                        .bind("stmt")),
+            *Stm, *Context);
+  return selectFirst<Stmt>("stmt", Matches);
+}
+
+const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) {
+  // Check whether any member of 'Exp' is mutated.
+  const auto MemberExprs = match(
+      findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")),
+      *Stm, *Context);
+  return findExprMutation(MemberExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) {
+  // Check whether any element of an array is mutated.
+  const auto SubscriptExprs = match(
+      findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp))))
+                  .bind("expr")),
+      *Stm, *Context);
+  return findExprMutation(SubscriptExprs);
+}
+
+const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) {
+  // If 'Exp' is casted to any non-const reference type, check the castExpr.
+  const auto Casts =
+      match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)),
+                             anyOf(explicitCastExpr(hasDestinationType(
+                                       nonConstReferenceType())),
+                                   implicitCastExpr(hasImplicitDestinationType(
+                                       nonConstReferenceType()))))
+                        .bind("expr")),
+            *Stm, *Context);
+  return findExprMutation(Casts);
+}
+
+const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) {
+  // If range for looping over 'Exp' with a non-const reference loop variable,
+  // check all declRefExpr of the loop variable.
+  const auto LoopVars =
+      match(findAll(cxxForRangeStmt(
+                hasLoopVariable(
+                    varDecl(hasType(nonConstReferenceType())).bind("decl")),
+                hasRangeInit(equalsNode(Exp)))),
+            *Stm, *Context);
+  return findDeclMutation(LoopVars);
+}
+
+const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) {
+  // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
+  const auto Refs = match(
+      stmt(forEachDescendant(
+          varDecl(
+              hasType(nonConstReferenceType()),
+              hasInitializer(anyOf(equalsNode(Exp),
+                                   conditionalOperator(anyOf(
+                                       hasTrueExpression(equalsNode(Exp)),
+                                       hasFalseExpression(equalsNode(Exp)))))),
+              hasParent(declStmt().bind("stmt")),
+              // Don't follow the reference in range statement, we've handled
+              // that separately.
+              unless(hasParent(declStmt(hasParent(
+                  cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt"))))))))
+              .bind("decl"))),
+      *Stm, *Context);
+  return findDeclMutation(Refs);
+}
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang

Added: clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.h?rev=335736&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.h (added)
+++ clang-tools-extra/trunk/clang-tidy/utils/ExprMutationAnalyzer.h Wed Jun 27 07:30:55 2018
@@ -0,0 +1,56 @@
+//===---------- ExprMutationAnalyzer.h - clang-tidy -----------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H
+
+#include <type_traits>
+
+#include "clang/AST/AST.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/DenseMap.h"
+
+namespace clang {
+namespace tidy {
+namespace utils {
+
+/// Analyzes whether any mutative operations are applied to an expression within
+/// a given statement.
+class ExprMutationAnalyzer {
+public:
+  ExprMutationAnalyzer(const Stmt *Stm, ASTContext *Context)
+      : Stm(Stm), Context(Context) {}
+
+  bool isMutated(const Decl *Dec) { return findDeclMutation(Dec) != nullptr; }
+  bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; }
+  const Stmt *findMutation(const Expr *Exp);
+
+private:
+  bool isUnevaluated(const Expr *Exp);
+
+  const Stmt *findExprMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+  const Stmt *findDeclMutation(ArrayRef<ast_matchers::BoundNodes> Matches);
+  const Stmt *findDeclMutation(const Decl *Dec);
+
+  const Stmt *findDirectMutation(const Expr *Exp);
+  const Stmt *findMemberMutation(const Expr *Exp);
+  const Stmt *findArrayElementMutation(const Expr *Exp);
+  const Stmt *findCastMutation(const Expr *Exp);
+  const Stmt *findRangeLoopMutation(const Expr *Exp);
+  const Stmt *findReferenceMutation(const Expr *Exp);
+
+  const Stmt *const Stm;
+  ASTContext *const Context;
+  llvm::DenseMap<const Expr *, const Stmt *> Results;
+};
+
+} // namespace utils
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H

Modified: clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt?rev=335736&r1=335735&r2=335736&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt Wed Jun 27 07:30:55 2018
@@ -9,6 +9,7 @@ include_directories(${CLANG_LINT_SOURCE_
 add_extra_unittest(ClangTidyTests
   ClangTidyDiagnosticConsumerTest.cpp
   ClangTidyOptionsTest.cpp
+  ExprMutationAnalyzerTest.cpp
   IncludeInserterTest.cpp
   GoogleModuleTest.cpp
   LLVMModuleTest.cpp

Added: clang-tools-extra/trunk/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp?rev=335736&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp (added)
+++ clang-tools-extra/trunk/unittests/clang-tidy/ExprMutationAnalyzerTest.cpp Wed Jun 27 07:30:55 2018
@@ -0,0 +1,609 @@
+//===---------- ExprMutationAnalyzerTest.cpp - clang-tidy -----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../clang-tidy/utils/ExprMutationAnalyzer.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cctype>
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+using namespace clang::ast_matchers;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::ResultOf;
+using ::testing::StartsWith;
+using ::testing::Values;
+
+namespace {
+
+using ExprMatcher = internal::Matcher<Expr>;
+using StmtMatcher = internal::Matcher<Stmt>;
+
+ExprMatcher declRefTo(StringRef Name) {
+  return declRefExpr(to(namedDecl(hasName(Name))));
+}
+
+StmtMatcher withEnclosingCompound(ExprMatcher Matcher) {
+  return expr(Matcher, hasAncestor(compoundStmt().bind("stmt"))).bind("expr");
+}
+
+bool isMutated(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+  const auto *const S = selectFirst<Stmt>("stmt", Results);
+  const auto *const E = selectFirst<Expr>("expr", Results);
+  return utils::ExprMutationAnalyzer(S, &AST->getASTContext()).isMutated(E);
+}
+
+SmallVector<std::string, 1>
+mutatedBy(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+  const auto *const S = selectFirst<Stmt>("stmt", Results);
+  SmallVector<std::string, 1> Chain;
+  utils::ExprMutationAnalyzer Analyzer(S, &AST->getASTContext());
+  for (const auto *E = selectFirst<Expr>("expr", Results); E != nullptr;) {
+    const Stmt *By = Analyzer.findMutation(E);
+    std::string buffer;
+    llvm::raw_string_ostream stream(buffer);
+    By->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy());
+    Chain.push_back(StringRef(stream.str()).trim().str());
+    E = dyn_cast<DeclRefExpr>(By);
+  }
+  return Chain;
+}
+
+std::string removeSpace(std::string s) {
+  s.erase(std::remove_if(s.begin(), s.end(),
+                         [](char c) { return std::isspace(c); }),
+          s.end());
+  return s;
+}
+
+} // namespace
+
+TEST(ExprMutationAnalyzerTest, Trivial) {
+  const auto AST = tooling::buildASTFromCode("void f() { int x; x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+class AssignmentTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(AssignmentTest, AssignmentModifies) {
+  const std::string ModExpr = "x " + GetParam() + " 10";
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest,
+                        Values("=", "+=", "-=", "*=", "/=", "%=", "&=", "|=",
+                               "^=", "<<=", ">>="), );
+
+class IncDecTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(IncDecTest, IncDecModifies) {
+  const std::string ModExpr = GetParam();
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest,
+                        Values("++x", "--x", "x++", "x--"), );
+
+TEST(ExprMutationAnalyzerTest, NonConstMemberFunc) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct Foo { void mf(); }; Foo x; x.mf(); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstMemberFunc) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct Foo { void mf() const; }; Foo x; x.mf(); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NonConstOperator) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct Foo { Foo& operator=(int); }; Foo x; x = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ConstOperator) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct Foo { int operator()() const; }; Foo x; x(); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByValueArgument) {
+  auto AST =
+      tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A {}; A operator+(A, int); A x; x + 1; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(int); }; int x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(); A(A); }; A x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRefArgument) {
+  auto AST =
+      tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)"));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A {}; A operator+(A&, int); A x; x + 1; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x + 1"));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(int&); }; int x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(); A(A&); }; A x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRefArgument) {
+  auto AST = tooling::buildASTFromCode(
+      "void g(const int&); void f() { int x; g(x); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A {}; A operator+(const A&, int); A x; x + 1; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(const int&); }; int x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A { A(); A(const A&); }; A x; A y(x); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ByNonConstRRefArgument) {
+  auto AST = tooling::buildASTFromCode(
+      "void g(int&&); void f() { int x; g(static_cast<int &&>(x)); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("g(static_cast<int &&>(x))"));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A {}; A operator+(A&&, int); "
+      "A x; static_cast<A &&>(x) + 1; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("static_cast<A &&>(x) + 1"));
+
+  AST = tooling::buildASTFromCode("void f() { struct A { A(int&&); }; "
+                                  "int x; A y(static_cast<int &&>(x)); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("static_cast<int &&>(x)"));
+
+  AST = tooling::buildASTFromCode("void f() { struct A { A(); A(A&&); }; "
+                                  "A x; A y(static_cast<A &&>(x)); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("static_cast<A &&>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
+  auto AST = tooling::buildASTFromCode(
+      "void g(const int&&); void f() { int x; g(static_cast<int&&>(x)); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { struct A {}; A operator+(const A&&, int); "
+      "A x; static_cast<A&&>(x) + 1; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("void f() { struct A { A(const int&&); }; "
+                                  "int x; A y(static_cast<int&&>(x)); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("void f() { struct A { A(); A(const A&&); }; "
+                                  "A x; A y(static_cast<A&&>(x)); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, Move) {
+  // Technically almost the same as ByNonConstRRefArgument, just double checking
+  const auto AST = tooling::buildASTFromCode(
+      "namespace std {"
+      "template<class T> struct remove_reference { typedef T type; };"
+      "template<class T> struct remove_reference<T&> { typedef T type; };"
+      "template<class T> struct remove_reference<T&&> { typedef T type; };"
+      "template<class T> typename std::remove_reference<T>::type&& "
+      "move(T&& t) noexcept; }"
+      "void f() { struct A {}; A x; std::move(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, Forward) {
+  // Technically almost the same as ByNonConstRefArgument, just double checking
+  const auto AST = tooling::buildASTFromCode(
+      "namespace std {"
+      "template<class T> struct remove_reference { typedef T type; };"
+      "template<class T> struct remove_reference<T&> { typedef T type; };"
+      "template<class T> struct remove_reference<T&&> { typedef T type; };"
+      "template<class T> T&& "
+      "forward(typename std::remove_reference<T>::type&) noexcept;"
+      "template<class T> T&& "
+      "forward(typename std::remove_reference<T>::type&&) noexcept;"
+      "void f() { struct A {}; A x; std::forward<A &>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("std::forward<A &>(x)"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsValue) {
+  const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRef) {
+  const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("return x;"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRef) {
+  const auto AST =
+      tooling::buildASTFromCode("const int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "int&& f() { int x; return static_cast<int &&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("return static_cast<int &&>(x);"));
+}
+
+TEST(ExprMutationAnalyzerTest, ReturnAsConstRRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "const int&& f() { int x; return static_cast<int&&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, TakeAddress) {
+  const auto AST =
+      tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("&x"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayToPointerDecay) {
+  const auto AST =
+      tooling::buildASTFromCode("void g(int*); void f() { int x[2]; g(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+      "int& r3 = r2; r3 = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("r0", "r1", "r2", "r3", "r3 = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowRefNotModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; "
+      "int& r3 = r2; int& r4 = r3; int& r5 = r4;}");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x, y; bool b; int &r = b ? x : y; r = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("r", "r = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, FollowConditionalRefNotModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x, y; bool b; int& r = b ? x : y; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x[0] = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, ArrayElementNotModified) {
+  const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct A { int vi; }; struct B { A va; }; "
+      "struct C { B vb; }; C x; x.vb.va.vi = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.vb.va.vi = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, NestedMemberNotModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { struct A { int vi; }; struct B { A va; }; "
+      "struct C { B vb; }; C x; x.vb.va.vi; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; static_cast<double>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; static_cast<int &>(x) = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre("static_cast<int &>(x) = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToRefNotModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; static_cast<int&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, CastToConstRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; static_cast<const int&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByRef) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre(ResultOf(removeSpace, "[&](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByRef) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()),
+              ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}")));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x[2]; for (int& e : x) e = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByRefNotModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x[2]; for (int& e : x) e; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByValue) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x[2]; for (int e : x) e = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForArrayByConstRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x[2]; for (const int& e : x) e; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefModified) {
+  const auto AST =
+      tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+                                "void f() { V x; for (int& e : x) e = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10"));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefNotModified) {
+  const auto AST =
+      tooling::buildASTFromCode("struct V { int* begin(); int* end(); };"
+                                "void f() { V x; for (int& e : x) e; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByValue) {
+  const auto AST = tooling::buildASTFromCode(
+      "struct V { const int* begin() const; const int* end() const; };"
+      "void f() { V x; for (int e : x) e; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, RangeForNonArrayByConstRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "struct V { const int* begin() const; const int* end() const; };"
+      "void f() { V x; for (const int& e : x) e; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, UnevaluatedExpressions) {
+  auto AST = tooling::buildASTFromCode(
+      "void f() { int x, y; decltype(x = 10) z = y; }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { int x, y; __typeof(x = 10) z = y; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { int x, y; __typeof__(x = 10) z = y; }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("void f() { int x; sizeof(x = 10); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("void f() { int x; alignof(x = 10); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("void f() { int x; noexcept(x = 10); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode("namespace std { class type_info; }"
+                                  "void f() { int x; typeid(x = 10); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+
+  AST = tooling::buildASTFromCode(
+      "void f() { int x; _Generic(x = 10, int: 0, default: 1); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isMutated(Results, AST.get()));
+}
+
+TEST(ExprMutationAnalyzerTest, NotUnevaluatedExpressions) {
+  auto AST = tooling::buildASTFromCode("void f() { int x; sizeof(int[x++]); }");
+  auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x++"));
+
+  AST = tooling::buildASTFromCode(
+      "namespace std { class type_info; }"
+      "struct A { virtual ~A(); }; struct B : A {};"
+      "struct X { A& f(); }; void f() { X x; typeid(x.f()); }");
+  Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.f()"));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang




More information about the cfe-commits mailing list