shuaiwang updated this revision to Diff 142739.
shuaiwang added a comment.

Change to just add a helper function `isModified`


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,225 @@
+//===---- 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 "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+using namespace clang::ast_matchers;
+
+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 isModified(const SmallVectorImpl<BoundNodes> &Results, ASTUnit *AST) {
+  const auto E = selectFirst<Expr>("expr", Results);
+  const auto S = selectFirst<Stmt>("stmt", Results);
+  return utils::isModified(*E, *S, &AST->getASTContext());
+}
+
+} // namespace
+
+TEST(IsModifiedTest, Trivial) {
+  const auto AST = tooling::buildASTFromCode("void f() { int x; x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+class AssignmentTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(AssignmentTest, AssignmentModifies) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; x " + GetParam() + " 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest,
+                        ::testing::Values("=", "+=", "-=", "*=", "/=", "%=",
+                                          "&=", "|=", "^=", "<<=", ">>="), );
+
+class IncDecTest : public ::testing::TestWithParam<std::string> {};
+
+TEST_P(IncDecTest, IncDecModifies) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x; " + GetParam() + "; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest,
+                        ::testing::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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+TEST(IsModifiedTest, ReturnAsValue) {
+  const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+TEST(IsModifiedTest, ReturnAsNonConstRef) {
+  const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+TEST(IsModifiedTest, ReturnAsConstRef) {
+  const auto AST =
+      tooling::buildASTFromCode("const int& f() { int x; return x; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+TEST(IsModifiedTest, FollowRefModified) {
+  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; r5 = 10; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+TEST(IsModifiedTest, ArrayElementNotModified) {
+  const auto AST =
+      tooling::buildASTFromCode("void f() { int x[2]; x[0]; }");
+  const auto Results =
+      match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_TRUE(isModified(Results, AST.get()));
+}
+
+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());
+  EXPECT_FALSE(isModified(Results, AST.get()));
+}
+
+} // 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,10 @@
 bool exprHasBitFlagWithSpelling(const Expr *Flags, const SourceManager &SM,
                                 const LangOptions &LangOpts,
                                 StringRef FlagName);
+
+// Checks whether `Exp` is (potentially) modified within `Stm`.
+bool isModified(const Expr& Exp, const Stmt& Stm, ASTContext* Context);
+
 } // 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,116 @@
   return true;
 }
 
+namespace {
+class MatchCallbackAdaptor : public MatchFinder::MatchCallback {
+public:
+  explicit MatchCallbackAdaptor(
+      std::function<void(const MatchFinder::MatchResult &)> Func)
+      : Func(std::move(Func)) {}
+  void run(const MatchFinder::MatchResult &Result) override { Func(Result); }
+
+private:
+  std::function<void(const MatchFinder::MatchResult &)> Func;
+};
+} // namespace
+
+bool isModified(const Expr &Exp, const Stmt &Stm, ASTContext *Context) {
+  // 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)))));
+
+  // 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));
+
+  // Returned as non-const-ref.
+  // If we're returning 'Exp' directly then it's returned as non-const-ref.
+  // Otherwise there will be an ImplicitCastExpr <LValueToRValue> in between.
+  const auto AsNonConstRefReturn = returnStmt(hasReturnValue(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)));
+
+  // Check whether 'Exp' is directly modified as a whole.
+  MatchFinder Finder;
+  bool HasMatch = false;
+  MatchCallbackAdaptor Callback(
+      [&HasMatch](const MatchFinder::MatchResult &) { HasMatch = true; });
+  Finder.addMatcher(
+      findAll(expr(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,
+                         AsNonConstRefArg, AsAmpersandOperand,
+                         AsPointerFromArrayDecay))),
+      &Callback);
+  Finder.addMatcher(findAll(AsNonConstRefReturn), &Callback);
+  Finder.match(Stm, *Context);
+  if (HasMatch)
+    return true;
+
+  // Check whether any member of 'Exp' is modified.
+  const auto MemberExprs = match(
+      findAll(memberExpr(hasObjectExpression(equalsNode(&Exp))).bind("expr")),
+      Stm, *Context);
+  for (const auto &Node : MemberExprs) {
+    if (isModified(*Node.getNodeAs<Expr>("expr"), Stm, Context))
+      return true;
+  }
+
+  // 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);
+  for (const auto &Node : SubscriptExprs) {
+    if (isModified(*Node.getNodeAs<Expr>("expr"), Stm, Context))
+      return true;
+  }
+
+  // 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(equalsNode(&Exp)))
+              .bind("decl"))),
+      Stm, *Context);
+  for (const auto &DeclNode : Decls) {
+    const auto Exprs = match(
+        findAll(declRefExpr(to(equalsNode(DeclNode.getNodeAs<Decl>("decl"))))
+                    .bind("expr")),
+        Stm, *Context);
+    for (const auto &ExprNode : Exprs) {
+      if (isModified(*ExprNode.getNodeAs<Expr>("expr"), Stm, Context))
+        return true;
+    }
+  }
+
+  return false;
+}
+
 } // 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