Index: clang-tidy/utils/Matchers.h =================================================================== --- clang-tidy/utils/Matchers.h +++ clang-tidy/utils/Matchers.h @@ -12,6 +12,8 @@ #include "TypeTraits.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CFG.h" namespace clang { namespace tidy { @@ -48,6 +50,67 @@ return referenceType(pointee(qualType(isConstQualified()))); } +// Matches the next statement within the parent statement sequence. +AST_MATCHER_P(Stmt, hasSuccessor, + ast_matchers::internal::Matcher, InnerMatcher) { + using namespace ast_matchers; + + // We get the first parent, making sure that we're not in a case statement + // not in a compound statement directly inside a switch, because this causes + // the buildCFG call to crash. + auto Parent = selectFirst( + "parent", + match( + stmt(hasAncestor(stmt( + unless(caseStmt()), + unless(compoundStmt(hasParent(switchStmt()))), + stmt().bind("parent")))), + Node, Finder->getASTContext())); + + // We build a Control Flow Graph (CFG) from the parent statement. + std::unique_ptr StatementCFG = + CFG::buildCFG(nullptr, const_cast(Parent), &Finder->getASTContext(), + CFG::BuildOptions()); + + if (!StatementCFG) { + return false; + } + + // We iterate through all the CFGBlocks, which basically means that we go over + // all the possible branches of the code and therefore cover all statements. + for (auto& Block : *StatementCFG) { + if (!Block) { + continue; + } + + // We iterate through all the statements of the block. + bool ReturnNextStmt = false; + for (auto& BlockItem : *Block) { + Optional CFGStatement = BlockItem.getAs(); + if (!CFGStatement) { + if (ReturnNextStmt) { + return false; + } + + continue; + } + + // If we found the next statement, we apply the inner matcher and return + // the result. + if (ReturnNextStmt) { + return InnerMatcher.matches(*CFGStatement->getStmt(), Finder, Builder); + } + + if (CFGStatement->getStmt() == &Node) { + ReturnNextStmt = true; + } + } + } + + // If we didn't find a successor, we just return false. + return false; +} + } // namespace matchers } // namespace tidy } // namespace clang Index: unittests/clang-tidy/CMakeLists.txt =================================================================== --- unittests/clang-tidy/CMakeLists.txt +++ unittests/clang-tidy/CMakeLists.txt @@ -12,6 +12,7 @@ IncludeInserterTest.cpp GoogleModuleTest.cpp LLVMModuleTest.cpp + MatchersUtilsTest.cpp NamespaceAliaserTest.cpp ObjCModuleTest.cpp OverlappingReplacementsTest.cpp Index: unittests/clang-tidy/MatchersUtilsTest.cpp =================================================================== --- /dev/null +++ unittests/clang-tidy/MatchersUtilsTest.cpp @@ -0,0 +1,25 @@ +#include "../../../unittests/ASTMatchers/ASTMatchersTest.h" +#include "utils/Matchers.h" + +namespace clang { +namespace tidy { +namespace test { + +using namespace ast_matchers; +using namespace matchers; + +TEST(StatementMatcher, HasSuccessor) { + StatementMatcher DeclHasSuccessorReturnStmt = + declStmt(hasSuccessor(returnStmt())); + + EXPECT_TRUE(matches( + "void foo() { int bar; return; }", + DeclHasSuccessorReturnStmt)); + EXPECT_TRUE(notMatches( + "void foo() { int bar; bar++; return; }", + DeclHasSuccessorReturnStmt)); +} + +} +} +}