diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index bca2d8425b3f5..3e874b37c05c1 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -95,6 +95,7 @@ #include #include #include +#include #include #include @@ -5889,7 +5890,6 @@ AST_MATCHER_P(FunctionDecl, hasAnyBody, InnerMatcher.matches(*Statement, Finder, Builder)); } - /// Matches compound statements where at least one substatement matches /// a given matcher. Also matches StmtExprs that have CompoundStmt as children. /// @@ -5911,6 +5911,30 @@ AST_POLYMORPHIC_MATCHER_P(hasAnySubstatement, Builder) != CS->body_end(); } +/// Matches compound statements that contain adjacent substatements matching +/// the provided sequence of matchers. Also matches StmtExprs that have +/// CompoundStmt as children. +/// +/// Given +/// \code +/// { {}; 1+2; } +/// \endcode +/// hasAdjSubstatements(compoundStmt(), binaryOperator()) +/// matches '{ {}; 1+2; }' +/// with compoundStmt() +/// matching '{}' +/// with binaryOperator() +/// matching '1+2' +/// +/// hasAdjSubstatements(compoundStmt(), binaryOperator(), returnStmt()) +/// Is equivalent to matching a compound statement that contains +/// a compound statement immediately followed by a binary operator +/// immediately followed by a return statement. +extern const internal::VariadicFunction< + internal::HasAdjSubstatementsMatcherType, internal::Matcher, + internal::hasAdjSubstatementsFunc> + hasAdjSubstatements; + /// Checks that a compound statement contains a specific number of /// child statements. /// diff --git a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h index c050fb7d797e3..5fb63af28dc1b 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchersInternal.h +++ b/clang/include/clang/ASTMatchers/ASTMatchersInternal.h @@ -2283,6 +2283,37 @@ using HasOpNameMatcher = HasOpNameMatcher hasAnyOperatorNameFunc(ArrayRef NameRefs); +/// Matches nodes of type T (CompoundStmt or StmtExpr) that contain a sequence +/// of consecutive substatements matching the provided matchers in order. +/// +/// See \c hasAdjSubstatements() in ASTMatchers.h for details. +template >> +class HasAdjSubstatementsMatcher : public MatcherInterface { + static_assert(std::is_same::value || + std::is_same::value, + "Matcher only supports `CompoundStmt` and `StmtExpr`"); + static_assert(std::is_same>>::value, + "Matcher ArgT must be std::vector>"); + +public: + explicit HasAdjSubstatementsMatcher(std::vector> Matchers) + : Matchers(std::move(Matchers)) {} + + bool matches(const T &Node, ASTMatchFinder *Finder, + BoundNodesTreeBuilder *Builder) const override; + +private: + std::vector> Matchers; +}; + +using HasAdjSubstatementsMatcherType = + PolymorphicMatcher), + std::vector>>; + +HasAdjSubstatementsMatcherType +hasAdjSubstatementsFunc(ArrayRef *> MatcherRefs); + using HasOverloadOpNameMatcher = PolymorphicMatcher), diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp index 0874b3d0c45f5..14ff5d919eae2 100644 --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -31,6 +31,7 @@ #include "llvm/Support/Regex.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -467,6 +468,58 @@ hasAnyOverloadedOperatorNameFunc(ArrayRef NameRefs) { return HasOverloadOpNameMatcher(vectorFromRefs(NameRefs)); } +static std::vector> +vectorFromMatcherRefs(ArrayRef *> MatcherRefs) { + std::vector> Matchers; + Matchers.reserve(MatcherRefs.size()); + for (auto *Matcher : MatcherRefs) + Matchers.push_back(*Matcher); + return Matchers; +} + +HasAdjSubstatementsMatcherType +hasAdjSubstatementsFunc(ArrayRef *> MatcherRefs) { + return HasAdjSubstatementsMatcherType(vectorFromMatcherRefs(MatcherRefs)); +} + +template +bool HasAdjSubstatementsMatcher::matches( + const T &Node, ASTMatchFinder *Finder, + BoundNodesTreeBuilder *Builder) const { + const CompoundStmt *CS = CompoundStmtMatcher::get(Node); + if (!CS) + return false; + + // Use llvm::search with lambda predicate that matches statements against + // matchers and accumulates BoundNodesTreeBuilder state + BoundNodesTreeBuilder CurrentBuilder; + const auto Found = llvm::search( + CS->body(), Matchers, + [&](const Stmt *StmtPtr, const Matcher &Matcher) mutable { + BoundNodesTreeBuilder StepBuilder; + StepBuilder.addMatch(CurrentBuilder); + if (!Matcher.matches(*StmtPtr, Finder, &StepBuilder)) { + // reset the state + CurrentBuilder = {}; + return false; + } + // Invalidate the state + CurrentBuilder = StepBuilder; + return true; + }); + + if (Found == CS->body_end()) + return false; + + Builder->addMatch(CurrentBuilder); + return true; +} + +template bool HasAdjSubstatementsMatcher::matches( + const CompoundStmt &, ASTMatchFinder *, BoundNodesTreeBuilder *) const; +template bool HasAdjSubstatementsMatcher::matches( + const StmtExpr &, ASTMatchFinder *, BoundNodesTreeBuilder *) const; + HasNameMatcher::HasNameMatcher(std::vector N) : UseUnqualifiedMatch( llvm::all_of(N, [](StringRef Name) { return !Name.contains("::"); })), @@ -1046,6 +1099,10 @@ const internal::VariadicFunction, StringRef, const internal::VariadicFunction hasAnyOperatorName = {}; +const internal::VariadicFunction, + internal::hasAdjSubstatementsFunc> + hasAdjSubstatements = {}; const internal::VariadicFunction hasAnyOverloadedOperatorName = {}; diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp index 66848f7c42127..7b21a60c5f189 100644 --- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp @@ -288,6 +288,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(hasAnyPlacementArg); REGISTER_MATCHER(hasAnySelector); REGISTER_MATCHER(hasAnySubstatement); + REGISTER_MATCHER(hasAdjSubstatements); REGISTER_MATCHER(hasAnyTemplateArgument); REGISTER_MATCHER(hasAnyTemplateArgumentLoc); REGISTER_MATCHER(hasAnyUsingShadowDecl); diff --git a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp index c0a03deb5b543..f113f2a114757 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp @@ -2399,6 +2399,199 @@ TEST(HasAnySubstatement, FindsSubstatementBetweenOthers) { compoundStmt(hasAnySubstatement(forStmt())))); } +TEST(HasAdjSubstatements, MatchesAdjacentSubstatements) { + // Basic case: compound statement followed by binary operator + EXPECT_TRUE(matches( + "void f() { {} 1+2; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, DoesNotMatchNonAdjacentSubstatements) { + // Statements exist but not adjacent + EXPECT_TRUE(notMatches( + "void f() { {} 1; 1+2; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, MatchesInNestedCompoundStatements) { + // Should match in nested compound statements + EXPECT_TRUE(matches( + "void f() { if (true) { {} 1+2; } }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, MatchesFirstAdjacentPair) { + // When multiple adjacent pairs exist, should match the first one + EXPECT_TRUE(matches( + "void f() { {} 1+2; {} 3+4; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, DoesNotMatchEmptyCompound) { + // Empty compound statement has no adjacent pairs + EXPECT_TRUE(notMatches( + "void f() { }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, DoesNotMatchSingleStatement) { + // Single statement has no adjacent pairs + EXPECT_TRUE(notMatches( + "void f() { 1+2; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, MatchesDifferentStatementTypes) { + // Test with different statement types + EXPECT_TRUE( + matches("void f() { for (;;); while (true); }", + compoundStmt(hasAdjSubstatements(forStmt(), whileStmt())))); + + EXPECT_TRUE( + matches("void f() { int x; return; }", + compoundStmt(hasAdjSubstatements(declStmt(), returnStmt())))); +} + +TEST(HasAdjSubstatements, WorksWithStmtExpr) { + // Test that it works with StmtExpr (polymorphic support) + EXPECT_TRUE( + matches("void f() { int x = ({ {} 1+2; }); }", + stmtExpr(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, DoesNotMatchWrongOrder) { + // The order matters - binaryOperator must come after compoundStmt + EXPECT_TRUE(notMatches( + "void f() { 1+2; {} }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, MatchesWithStatementsBetween) { + // Should still match even if there are other statements before/after + EXPECT_TRUE(matches( + "void f() { int x; {} 1+2; int y; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesThreeAdjacentSubstatements) { + // Test variadic version with 3 matchers + EXPECT_TRUE( + matches("void f() { {} 1+2; 3+4; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesFourAdjacentSubstatements) { + // Test variadic version with 4 matchers + EXPECT_TRUE(matches( + "void f() { int x; return; {} 1+2; }", + compoundStmt(hasAdjSubstatements(declStmt(), returnStmt(), compoundStmt(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesFiveAdjacentSubstatements) { + // Test variadic version with 5 matchers + EXPECT_TRUE(matches( + "void f() { for (;;); while (true); if (true) {} return; 1+2; }", + compoundStmt(hasAdjSubstatements(forStmt(), whileStmt(), ifStmt(), + returnStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicDoesNotMatchNonAdjacentSequence) { + // Three matchers but statements are not all adjacent + EXPECT_TRUE( + notMatches("void f() { {} 1; 1+2; 3+4; }", + compoundStmt(hasAdjSubstatements( + compoundStmt(), binaryOperator(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicDoesNotMatchPartialSequence) { + // First two match but third doesn't + EXPECT_TRUE( + notMatches("void f() { {} 1+2; return; }", + compoundStmt(hasAdjSubstatements( + compoundStmt(), binaryOperator(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesInNestedCompound) { + // Test variadic version in nested compound statements + EXPECT_TRUE( + matches("void f() { if (true) { {} 1+2; 3+4; } }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesWithDifferentTypes) { + // Test variadic version with different statement types + EXPECT_TRUE(matches( + "void f() { for (;;); while (true); if (true) {} }", + compoundStmt(hasAdjSubstatements(forStmt(), whileStmt(), ifStmt())))); +} + +TEST(HasAdjSubstatements, VariadicDoesNotMatchWrongOrder) { + // Order matters in variadic version + EXPECT_TRUE( + notMatches("void f() { 1+2; {} 3+4; }", + compoundStmt(hasAdjSubstatements( + compoundStmt(), binaryOperator(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesFirstSequence) { + // When multiple sequences exist, should match the first one + EXPECT_TRUE( + matches("void f() { {} 1+2; 3+4; {} 5+6; 7+8; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicWorksWithStmtExpr) { + // Test variadic version with StmtExpr + EXPECT_TRUE( + matches("void f() { int x = ({ {} 1+2; 3+4; }); }", + stmtExpr(hasAdjSubstatements(compoundStmt(), binaryOperator(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicRequiresMinimumStatements) { + // Need at least as many statements as matchers + EXPECT_TRUE( + notMatches("void f() { {} 1+2; }", + compoundStmt(hasAdjSubstatements( + compoundStmt(), binaryOperator(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesWithStatementsBetween) { + // Should still match even if there are other statements before/after + EXPECT_TRUE( + matches("void f() { int x; {} 1+2; 3+4; int y; }", + compoundStmt(hasAdjSubstatements(compoundStmt(), binaryOperator(), + binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesComplexSequence) { + // Test with a complex sequence of different statement types + EXPECT_TRUE(matches( + "void f() { int a; int b; return; {} 1+2; }", + compoundStmt(hasAdjSubstatements(declStmt(), declStmt(), returnStmt(), + compoundStmt(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicDoesNotMatchGapInSequence) { + // Sequence has a gap in the middle + EXPECT_TRUE( + notMatches("void f() { {} 1+2; int x; 3+4; }", + compoundStmt(hasAdjSubstatements( + compoundStmt(), binaryOperator(), binaryOperator())))); +} + +TEST(HasAdjSubstatements, VariadicMatchesLongSequence) { + // Test with a longer sequence (6 statements) + EXPECT_TRUE(matches("void f() { int a; int b; int c; return; {} 1+2; }", + compoundStmt(hasAdjSubstatements( + declStmt(), declStmt(), declStmt(), returnStmt(), + compoundStmt(), binaryOperator())))); +} + TEST(Member, MatchesMemberAllocationFunction) { // Fails in C++11 mode EXPECT_TRUE(matchesConditionally( diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h index af0e4a36be1b1..f75830ecc53a8 100644 --- a/llvm/include/llvm/ADT/STLExtras.h +++ b/llvm/include/llvm/ADT/STLExtras.h @@ -1778,6 +1778,12 @@ OutputIt copy_if(R &&Range, OutputIt Out, UnaryPredicate P) { return std::copy_if(adl_begin(Range), adl_end(Range), Out, P); } +template +auto search(R1 &&Range1, R2 &&Range2, BinaryPredicate P) { + return std::search(adl_begin(Range1), adl_end(Range1), adl_begin(Range2), + adl_end(Range2), P); +} + /// Return the single value in \p Range that satisfies /// \p P( *, AllowRepeats)->T * returning nullptr /// when no values or multiple values were found.