shuaiwang updated this revision to Diff 142870.
shuaiwang edited the summary of this revision.
shuaiwang added a comment.

Handle casts, ternary operator and lambdas.
Also optionally returns a chain of Stmt giving more details about how the 
conclusion is reached.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D45679

Files:
  clang-tidy/utils/ASTUtils.cpp
  clang-tidy/utils/ASTUtils.h
  unittests/clang-tidy/CMakeLists.txt
  unittests/clang-tidy/IsModifiedTest.cpp

Index: unittests/clang-tidy/IsModifiedTest.cpp
===================================================================
--- /dev/null
+++ unittests/clang-tidy/IsModifiedTest.cpp
@@ -0,0 +1,473 @@
+//===---- IsModifiedTest.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/ASTUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Tooling.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+using namespace clang::ast_matchers;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::ResultOf;
+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");
+}
+
+utils::ExprModificationKind
+isModified(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST,
+           SmallVectorImpl<std::string> *Chain) {
+  const auto E = selectFirst<Expr>("expr", Results);
+  const auto S = selectFirst<Stmt>("stmt", Results);
+  SmallVector<const Stmt *, 2> C;
+  const auto Kind = utils::isModified(*E, *S, &AST->getASTContext(), &C);
+  for (const auto *S : C) {
+    std::string buffer;
+    llvm::raw_string_ostream stream(buffer);
+    S->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy());
+    Chain->push_back(StringRef(stream.str()).trim().str());
+  }
+  return Kind;
+}
+
+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(IsModifiedTest, Trivial) {
+  const auto AST = tooling::buildASTFromCode("void f() { int x; x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, 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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre(ModExpr));
+}
+
+INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest,
+                        Values("++x", "--x", "x++", "x--"), );
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("x.mf()"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("x = 10"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ByValueArgument) {
+  const auto AST =
+      tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ByNonConstRefArgument) {
+  const auto AST =
+      tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("g(x)"));
+}
+
+TEST(IsModifiedTest, ByConstRefArgument) {
+  const auto AST = tooling::buildASTFromCode(
+      "void g(const int&); void f() { int x; g(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ByNonConstRRefArgument) {
+  const auto AST = tooling::buildASTFromCode(
+      "void g(int&&); void f() { int x; g(static_cast<int &&>(x)); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("g(static_cast<int &&>(x))"));
+}
+
+TEST(IsModifiedTest, ByConstRRefArgument) {
+  const auto AST = tooling::buildASTFromCode(
+      "void g(const int&&); void f() { int x; g(static_cast<int&&>(x)); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, Move) {
+  // Technically almost the same as ByNonConstRRefArgument, just double checking
+  // here.
+  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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("std::move(x)"));
+}
+
+TEST(IsModifiedTest, Forward) {
+  // Technically almost the same as ByNonConstRefArgument, just double checking
+  // here.
+  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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("std::forward<A &>(x)"));
+}
+
+TEST(IsModifiedTest, ReturnAsValue) {
+  const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ReturnAsNonConstRef) {
+  const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("return x;"));
+}
+
+TEST(IsModifiedTest, ReturnAsConstRef) {
+  const auto AST =
+      tooling::buildASTFromCode("const int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ReturnAsNonConstRRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "int&& f() { int x; return static_cast<int &&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 2> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre("static_cast<int &&>(x)",
+                                 "return static_cast<int &&>(x);"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, TakeAddress) {
+  const auto AST =
+      tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("&x"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("x"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 9> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("int &r0 = x;", "r0", "int &r1 = r0;", "r1",
+                                 "int &r2 = r1;", "r2", "int &r3 = r2;", "r3",
+                                 "r3 = 10"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 3> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("int &r = b ? x : y;", "r", "r = 10"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, ArrayElementModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 2> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("x[0]", "x[0] = 10"));
+}
+
+TEST(IsModifiedTest, ArrayElementNotModified) {
+  const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 4> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain,
+              ElementsAre("x.vb", "x.vb.va", "x.vb.va.vi", "x.vb.va.vi = 10"));
+}
+
+TEST(IsModifiedTest, 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());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, CastToValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; static_cast<double>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, CastToRefModified) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; static_cast<int &>(x) = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 2> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+  EXPECT_THAT(Chain, ElementsAre("static_cast<int &>(x)",
+                                 "static_cast<int &>(x) = 10"));
+}
+
+TEST(IsModifiedTest, CastToRefNotModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; static_cast<int&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, CastToConstRef) {
+  const auto AST = tooling::buildASTFromCode(
+      "void f() { int x; static_cast<const int&>(x); }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, LambdaDefaultCaptureByValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, LambdaExplicitCaptureByValue) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 0> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+  EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, LambdaDefaultCaptureByRef) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre(ResultOf(removeSpace, "[&](){x=10;}")));
+}
+
+TEST(IsModifiedTest, LambdaExplicitCaptureByRef) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  SmallVector<std::string, 1> Chain;
+  EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Escaped);
+  EXPECT_THAT(Chain, ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}")));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
Index: unittests/clang-tidy/CMakeLists.txt
===================================================================
--- unittests/clang-tidy/CMakeLists.txt
+++ unittests/clang-tidy/CMakeLists.txt
@@ -10,6 +10,7 @@
   ClangTidyDiagnosticConsumerTest.cpp
   ClangTidyOptionsTest.cpp
   IncludeInserterTest.cpp
+  IsModifiedTest.cpp
   GoogleModuleTest.cpp
   LLVMModuleTest.cpp
   NamespaceAliaserTest.cpp
Index: clang-tidy/utils/ASTUtils.h
===================================================================
--- clang-tidy/utils/ASTUtils.h
+++ clang-tidy/utils/ASTUtils.h
@@ -27,6 +27,27 @@
 bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM,
                                 const LangOptions &LangOpts,
                                 StringRef FlagName);
+
+enum ExprModificationKind {
+  EMK_NotModified, // The Expr is neither modified nor escaped.
+  EMK_Modified,    /// The Expr is directly modified.
+  EMK_Escaped,     /// The Expr escaped the scope being analyzed within.
+};
+
+/// Checks whether `Exp` is modified within `Stm` or a modifiable reference
+/// escaped `Stm`.
+///
+/// \param Exp - The `Expr` to be checked.
+/// \param Stm - The `Stmt` to perform the check within.
+/// \param Context - The ASTContext.
+/// \param Chain - If not nullptr and when returning EMK_Modified or
+/// EMK_Escaped, will be filled with a list of `Stmt` that leads to the
+/// conclusion, with the last element being the modifying expression or the
+/// escaping point.
+ExprModificationKind isModified(const Expr &Exp, const Stmt &Stm,
+                                ASTContext *Context,
+                                SmallVectorImpl<const Stmt *> *Chain = nullptr);
+
 } // namespace utils
 } // namespace tidy
 } // namespace clang
Index: clang-tidy/utils/ASTUtils.cpp
===================================================================
--- clang-tidy/utils/ASTUtils.cpp
+++ clang-tidy/utils/ASTUtils.cpp
@@ -67,6 +67,158 @@
   return true;
 }
 
+namespace {
+AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr*, E) {
+  for (const auto* Init : Node.capture_inits()) {
+    if (Init == E) return true;
+  }
+  return false;
+}
+}  // namespace
+
+ExprModificationKind isModified(const Expr &Exp, const Stmt &Stm,
+                                ASTContext *Context,
+                                SmallVectorImpl<const Stmt *> *Chain) {
+  // 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'.
+  // In theory we can follow the pointer and see whether the pointer itself
+  // escaped 'Stm', or is dereferenced and the dereferencing expression is
+  // modified. This is left for future improvements.
+  const auto AsAmpersandOperand =
+      unaryOperator(hasOperatorName("&"),
+                    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 NonConstRefType =
+      referenceType(pointee(unless(isConstQualified())));
+  const auto NonConstRefParam = forEachArgumentWithParam(
+      equalsNode(&Exp), parmVarDecl(hasType(qualType(NonConstRefType))));
+  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)));
+
+  // Check whether 'Exp' is directly modified as a whole or escaped.
+  const auto ModOrEsc = match(
+      findAll(stmt(anyOf(
+          expr(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
+                     AsAmpersandOperand, AsPointerFromArrayDecay))
+              .bind("mod"),
+          expr(anyOf(AsNonConstRefArg, AsLambdaRefCaptureInit)).bind("esc"),
+          stmt(AsNonConstRefReturn).bind("esc")))),
+      Stm, *Context);
+  if (const auto *S = selectFirst<Stmt>("mod", ModOrEsc)) {
+    if (Chain != nullptr)
+      Chain->push_back(S);
+    return EMK_Modified;
+  }
+  if (const auto *S = selectFirst<Stmt>("esc", ModOrEsc)) {
+    if (Chain != nullptr)
+      Chain->push_back(S);
+    return EMK_Escaped;
+  }
+
+  const auto isExprModified = [&](ArrayRef<BoundNodes> Results) {
+    for (const auto &Node : Results) {
+      SmallVector<const Stmt *, 2> C;
+      const auto *E = Node.getNodeAs<Expr>("expr");
+      const auto Kind = isModified(*E, Stm, Context, Chain ? &C : nullptr);
+      if (Kind != EMK_NotModified) {
+        if (Chain != nullptr) {
+          Chain->push_back(E);
+          Chain->append(C.begin(), C.end());
+        }
+        return Kind;
+      }
+    }
+    return EMK_NotModified;
+  };
+
+  // Check whether any member of 'Exp' is modified.
+  const auto MemberExprs = match(
+      findAll(memberExpr(hasObjectExpression(equalsNode(&Exp))).bind("expr")),
+      Stm, *Context);
+  if (const auto Kind = isExprModified(MemberExprs))
+    return Kind;
+
+  // In the case of 'Exp' being an array, check whether any element is modified.
+  const auto SubscriptExprs = match(
+      findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(&Exp))))
+                  .bind("expr")),
+      Stm, *Context);
+  if (const auto Kind = isExprModified(SubscriptExprs))
+    return Kind;
+
+  // 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(NonConstRefType)),
+                         implicitCastExpr(
+                             hasImplicitDestinationType(NonConstRefType))))
+              .bind("expr")),
+      Stm, *Context);
+  if (const auto Kind = isExprModified(Casts))
+    return Kind;
+
+  // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
+  const auto Decls = match(
+      stmt(forEachDescendant(
+          varDecl(
+              hasType(referenceType(pointee(unless(isConstQualified())))),
+              hasInitializer(anyOf(equalsNode(&Exp),
+                                   conditionalOperator(anyOf(
+                                       hasTrueExpression(equalsNode(&Exp)),
+                                       hasFalseExpression(equalsNode(&Exp)))))),
+              hasParent(declStmt().bind("stmt")))
+              .bind("decl"))),
+      Stm, *Context);
+  for (const auto &DeclNode : Decls) {
+    if (Chain != nullptr)
+      Chain->push_back(DeclNode.getNodeAs<Stmt>("stmt"));
+    const auto Exprs = match(
+        findAll(declRefExpr(to(equalsNode(DeclNode.getNodeAs<Decl>("decl"))))
+                    .bind("expr")),
+        Stm, *Context);
+    if (const auto Kind = isExprModified(Exprs))
+      return Kind;
+    if (Chain != nullptr)
+      Chain->pop_back();
+  }
+
+  return EMK_NotModified;
+}
+
 } // namespace utils
 } // namespace tidy
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to