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