shuaiwang updated this revision to Diff 142882.
shuaiwang added a comment.
Better range for loop handling.
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,556 @@
+//===---- 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::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");
+}
+
+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
+ 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
+ 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;}")));
+}
+
+TEST(IsModifiedTest, RangeForOverArrayByRefModified) {
+ 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());
+ SmallVector<std::string, 3> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+ EXPECT_THAT(Chain, ElementsAre(StartsWith("int &e = "), "e", "e = 10"));
+}
+
+TEST(IsModifiedTest, RangeForOverArrayByRefNotModified) {
+ const auto AST =
+ tooling::buildASTFromCode("void f() { int x[2]; for (int& e : x) e; }");
+ 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, RangeForOverArrayByValue) {
+ 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());
+ SmallVector<std::string, 0> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+ EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, RangeForOverArrayByConstRef) {
+ 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());
+ SmallVector<std::string, 0> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+ EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+ SmallVector<std::string, 3> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_Modified);
+ EXPECT_THAT(Chain, ElementsAre(StartsWith("int &e = "), "e", "e = 10"));
+}
+
+TEST(IsModifiedTest, 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());
+ SmallVector<std::string, 0> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+ EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+ SmallVector<std::string, 0> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+ EXPECT_THAT(Chain, IsEmpty());
+}
+
+TEST(IsModifiedTest, 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());
+ SmallVector<std::string, 0> Chain;
+ EXPECT_EQ(isModified(Results, AST.get(), &Chain), utils::EMK_NotModified);
+ EXPECT_THAT(Chain, IsEmpty());
+}
+
+} // 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,194 @@
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;
+}
+
+AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
+ ast_matchers::internal::Matcher<DeclStmt>, InnerMatcher) {
+ const DeclStmt *const Range = Node.getRangeStmt();
+ return InnerMatcher.matches(*Range, Finder, Builder);
+}
+
+} // 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 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(NonConstRefType)).bind("decl")),
+ hasRangeInit(equalsNode(&Exp)))
+ .bind("stmt")),
+ Stm, *Context);
+ for (const auto &DeclNode : LoopVars) {
+ const auto *R = DeclNode.getNodeAs<CXXForRangeStmt>("stmt");
+ if (Chain != nullptr)
+ Chain->push_back(R->getLoopVarStmt());
+ 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();
+ }
+
+ // If 'Exp' is bound to a non-const reference, check all declRefExpr to that.
+ const auto Refs = match(
+ stmt(forEachDescendant(
+ varDecl(
+ hasType(NonConstRefType),
+ 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);
+ for (const auto &DeclNode : Refs) {
+ 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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits