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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits