// unittests/ASTMatchers/ASTMatchersInternalTest.cpp - AST matcher unit tests // // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ASTMatchersTest.h" #include "clang/AST/PrettyPrinter.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Testing/Support/SupportHelpers.h" #include "gtest/gtest.h" namespace clang { namespace ast_matchers { using internal::DynTypedMatcher; #if GTEST_HAS_DEATH_TEST TEST(HasNameDeathTest, DiesOnEmptyName) { ASSERT_DEBUG_DEATH({ DeclarationMatcher HasEmptyName = recordDecl(hasName("")); EXPECT_TRUE(notMatches("class X {};", HasEmptyName)); }, ""); } TEST(HasNameDeathTest, DiesOnEmptyPattern) { ASSERT_DEBUG_DEATH({ DeclarationMatcher HasEmptyName = recordDecl(matchesName("")); EXPECT_TRUE(notMatches("class X {};", HasEmptyName)); }, ""); } // FIXME Re-enable these tests without breaking standalone builds. #if 0 // FIXME: Figure out why back traces aren't being generated on clang builds on // windows. #if ENABLE_BACKTRACES && (!defined(_MSC_VER) || !defined(__clang__)) AST_MATCHER(Decl, causeCrash) { abort(); return true; } TEST(MatcherCrashDeathTest, CrashOnMatcherDump) { llvm::EnablePrettyStackTrace(); auto Matcher = testing::HasSubstr( "ASTMatcher: Matching '' against:\n\tFunctionDecl foo : " ""); ASSERT_DEATH(matches("void foo();", functionDecl(causeCrash())), Matcher); } template static void crashTestNodeDump(MatcherT Matcher, ArrayRef MatchedNodes, StringRef Against, StringRef Code) { llvm::EnablePrettyStackTrace(); MatchFinder Finder; struct CrashCallback : public MatchFinder::MatchCallback { void run(const MatchFinder::MatchResult &Result) override { abort(); } std::optional getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } StringRef getID() const override { return "CrashTester"; } } Callback; Finder.addMatcher(std::move(Matcher), &Callback); if (MatchedNodes.empty()) { ASSERT_DEATH(tooling::runToolOnCode( newFrontendActionFactory(&Finder)->create(), Code), testing::HasSubstr( ("ASTMatcher: Processing 'CrashTester' against:\n\t" + Against + "\nNo bound nodes") .str())); } else { std::vector>> Matchers; Matchers.reserve(MatchedNodes.size()); for (auto Node : MatchedNodes) { Matchers.push_back(testing::HasSubstr(Node.str())); } auto CrashMatcher = testing::AllOf( testing::HasSubstr( ("ASTMatcher: Processing 'CrashTester' against:\n\t" + Against + "\n--- Bound Nodes Begin ---") .str()), testing::HasSubstr("--- Bound Nodes End ---"), testing::AllOfArray(Matchers)); ASSERT_DEATH(tooling::runToolOnCode( newFrontendActionFactory(&Finder)->create(), Code), CrashMatcher); } } TEST(MatcherCrashDeathTest, CrashOnCallbackDump) { crashTestNodeDump(forStmt(), {}, "ForStmt : ", "void foo() { for(;;); }"); crashTestNodeDump( forStmt(hasLoopInit(declStmt(hasSingleDecl( varDecl(hasType(qualType().bind("QT")), hasType(type().bind("T")), hasInitializer( integerLiteral().bind("IL"))) .bind("VD"))) .bind("DS"))) .bind("FS"), {"FS - { ForStmt : }", "DS - { DeclStmt : }", "IL - { IntegerLiteral : }", "QT - { QualType : int }", "T - { BuiltinType : int }", "VD - { VarDecl I : }"}, "ForStmt : ", R"cpp( void foo() { for (int I = 0; I < 5; ++I) { } } )cpp"); crashTestNodeDump( cxxRecordDecl(hasMethod(cxxMethodDecl(hasName("operator+")).bind("Op+"))) .bind("Unnamed"), {"Unnamed - { CXXRecordDecl (anonymous) : }", "Op+ - { CXXMethodDecl (anonymous struct)::operator+ : }"}, "CXXRecordDecl (anonymous) : ", "struct { int operator+(int) const; } Unnamed;"); crashTestNodeDump( cxxRecordDecl(hasMethod(cxxConstructorDecl(isDefaulted()).bind("Ctor")), hasMethod(cxxDestructorDecl(isDefaulted()).bind("Dtor"))), {"Ctor - { CXXConstructorDecl Foo::Foo : }", "Dtor - { CXXDestructorDecl Foo::~Foo : }"}, "CXXRecordDecl Foo : ", "struct Foo { Foo() = default; ~Foo() = default; };"); } #endif // ENABLE_BACKTRACES #endif #endif TEST(ConstructVariadic, MismatchedTypes_Regression) { EXPECT_TRUE( matches("const int a = 0;", internal::DynTypedMatcher::constructVariadic( internal::DynTypedMatcher::VO_AnyOf, ASTNodeKind::getFromNodeKind(), {isConstQualified(), arrayType()}) .convertTo())); } // For testing AST_MATCHER_P(). AST_MATCHER_P(Decl, just, internal::Matcher, AMatcher) { // Make sure all special variables are used: node, match_finder, // bound_nodes_builder, and the parameter named 'AMatcher'. return AMatcher.matches(Node, Finder, Builder); } TEST(AstMatcherPMacro, Works) { DeclarationMatcher HasClassB = just(has(recordDecl(hasName("B")).bind("b"))); EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };", HasClassB, std::make_unique>("b"))); EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };", HasClassB, std::make_unique>("a"))); EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };", HasClassB, std::make_unique>("b"))); } AST_POLYMORPHIC_MATCHER_P(polymorphicHas, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt), internal::Matcher, AMatcher) { return Finder->matchesChildOf( Node, AMatcher, Builder, ASTMatchFinder::BK_First); } TEST(AstPolymorphicMatcherPMacro, Works) { DeclarationMatcher HasClassB = polymorphicHas(recordDecl(hasName("B")).bind("b")); EXPECT_TRUE(matchAndVerifyResultTrue("class A { class B {}; };", HasClassB, std::make_unique>("b"))); EXPECT_TRUE(matchAndVerifyResultFalse("class A { class B {}; };", HasClassB, std::make_unique>("a"))); EXPECT_TRUE(matchAndVerifyResultFalse("class A { class C {}; };", HasClassB, std::make_unique>("b"))); StatementMatcher StatementHasClassB = polymorphicHas(recordDecl(hasName("B"))); EXPECT_TRUE(matches("void x() { class B {}; }", StatementHasClassB)); } TEST(MatchFinder, CheckProfiling) { MatchFinder::MatchFinderOptions Options; llvm::StringMap Records; Options.CheckProfiling.emplace(Records); MatchFinder Finder(std::move(Options)); struct NamedCallback : public MatchFinder::MatchCallback { void run(const MatchFinder::MatchResult &Result) override {} StringRef getID() const override { return "MyID"; } } Callback; Finder.addMatcher(decl(), &Callback); std::unique_ptr Factory( newFrontendActionFactory(&Finder)); ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;")); EXPECT_EQ(1u, Records.size()); EXPECT_EQ("MyID", Records.begin()->getKey()); } class VerifyStartOfTranslationUnit : public MatchFinder::MatchCallback { public: VerifyStartOfTranslationUnit() : Called(false) {} void run(const MatchFinder::MatchResult &Result) override { EXPECT_TRUE(Called); } void onStartOfTranslationUnit() override { Called = true; } bool Called; }; TEST(MatchFinder, InterceptsStartOfTranslationUnit) { MatchFinder Finder; VerifyStartOfTranslationUnit VerifyCallback; Finder.addMatcher(decl(), &VerifyCallback); std::unique_ptr Factory( newFrontendActionFactory(&Finder)); ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;")); EXPECT_TRUE(VerifyCallback.Called); VerifyCallback.Called = false; std::unique_ptr AST(tooling::buildASTFromCode("int x;")); ASSERT_TRUE(AST.get()); Finder.matchAST(AST->getASTContext()); EXPECT_TRUE(VerifyCallback.Called); } class VerifyEndOfTranslationUnit : public MatchFinder::MatchCallback { public: VerifyEndOfTranslationUnit() : Called(false) {} void run(const MatchFinder::MatchResult &Result) override { EXPECT_FALSE(Called); } void onEndOfTranslationUnit() override { Called = true; } bool Called; }; TEST(MatchFinder, InterceptsEndOfTranslationUnit) { MatchFinder Finder; VerifyEndOfTranslationUnit VerifyCallback; Finder.addMatcher(decl(), &VerifyCallback); std::unique_ptr Factory( newFrontendActionFactory(&Finder)); ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), "int x;")); EXPECT_TRUE(VerifyCallback.Called); VerifyCallback.Called = false; std::unique_ptr AST(tooling::buildASTFromCode("int x;")); ASSERT_TRUE(AST.get()); Finder.matchAST(AST->getASTContext()); EXPECT_TRUE(VerifyCallback.Called); } TEST(Matcher, matchOverEntireASTContext) { std::unique_ptr AST = clang::tooling::buildASTFromCode("struct { int *foo; };"); ASSERT_TRUE(AST.get()); auto PT = selectFirst( "x", match(pointerType().bind("x"), AST->getASTContext())); EXPECT_NE(nullptr, PT); } TEST(DynTypedMatcherTest, TraversalKindForwardsToImpl) { auto M = DynTypedMatcher(decl()); EXPECT_FALSE(M.getTraversalKind()); M = DynTypedMatcher(traverse(TK_AsIs, decl())); EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs)); } TEST(DynTypedMatcherTest, ConstructWithTraversalKindSetsTK) { auto M = DynTypedMatcher(decl()).withTraversalKind(TK_AsIs); EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_AsIs)); } TEST(DynTypedMatcherTest, ConstructWithTraversalKindOverridesNestedTK) { auto M = DynTypedMatcher(decl()).withTraversalKind(TK_AsIs).withTraversalKind( TK_IgnoreUnlessSpelledInSource); EXPECT_THAT(M.getTraversalKind(), llvm::ValueIs(TK_IgnoreUnlessSpelledInSource)); } TEST(IsInlineMatcher, IsInline) { EXPECT_TRUE(matches("void g(); inline void f();", functionDecl(isInline(), hasName("f")))); EXPECT_TRUE(matches("namespace n { inline namespace m {} }", namespaceDecl(isInline(), hasName("m")))); EXPECT_TRUE(matches("inline int Foo = 5;", varDecl(isInline(), hasName("Foo")), {Lang_CXX17})); } // FIXME: Figure out how to specify paths so the following tests pass on // Windows. #ifndef _WIN32 TEST(Matcher, IsExpansionInMainFileMatcher) { EXPECT_TRUE(matches("class X {};", recordDecl(hasName("X"), isExpansionInMainFile()))); EXPECT_TRUE(notMatches("", recordDecl(isExpansionInMainFile()))); FileContentMappings M; M.push_back(std::make_pair("/other", "class X {};")); EXPECT_TRUE(matchesConditionally("#include \n", recordDecl(isExpansionInMainFile()), false, {"-isystem/"}, M)); } TEST(Matcher, IsExpansionInSystemHeader) { FileContentMappings M; M.push_back(std::make_pair("/other", "class X {};")); EXPECT_TRUE(matchesConditionally("#include \"other\"\n", recordDecl(isExpansionInSystemHeader()), true, {"-isystem/"}, M)); EXPECT_TRUE(matchesConditionally("#include \"other\"\n", recordDecl(isExpansionInSystemHeader()), false, {"-I/"}, M)); EXPECT_TRUE(notMatches("class X {};", recordDecl(isExpansionInSystemHeader()))); EXPECT_TRUE(notMatches("", recordDecl(isExpansionInSystemHeader()))); } TEST(Matcher, IsExpansionInFileMatching) { FileContentMappings M; M.push_back(std::make_pair("/foo", "class A {};")); M.push_back(std::make_pair("/bar", "class B {};")); EXPECT_TRUE(matchesConditionally( "#include \n" "#include \n" "class X {};", recordDecl(isExpansionInFileMatching("b.*"), hasName("B")), true, {"-isystem/"}, M)); EXPECT_TRUE(matchesConditionally( "#include \n" "#include \n" "class X {};", recordDecl(isExpansionInFileMatching("f.*"), hasName("X")), false, {"-isystem/"}, M)); } #endif // _WIN32 } // end namespace ast_matchers } // end namespace clang