//== unittests/ASTMatchers/ASTMatchersNodeTest.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/ADT/Triple.h" #include "llvm/Support/Host.h" #include "gtest/gtest.h" namespace clang { namespace ast_matchers { TEST_P(ASTMatchersTest, Decl_CXX) { if (!GetParam().isCXX()) { // FIXME: Add a test for `decl()` that does not depend on C++. return; } EXPECT_TRUE(notMatches("", decl(usingDecl()))); EXPECT_TRUE( matches("namespace x { class X {}; } using x::X;", decl(usingDecl()))); } TEST_P(ASTMatchersTest, NameableDeclaration_MatchesVariousDecls) { DeclarationMatcher NamedX = namedDecl(hasName("X")); EXPECT_TRUE(matches("typedef int X;", NamedX)); EXPECT_TRUE(matches("int X;", NamedX)); EXPECT_TRUE(matches("void foo() { int X; }", NamedX)); EXPECT_TRUE(matches("enum X { A, B, C };", NamedX)); EXPECT_TRUE(notMatches("#define X 1", NamedX)); } TEST_P(ASTMatchersTest, NamedDecl_CXX) { if (!GetParam().isCXX()) { return; } DeclarationMatcher NamedX = namedDecl(hasName("X")); EXPECT_TRUE(matches("class foo { virtual void X(); };", NamedX)); EXPECT_TRUE(matches("void foo() try { } catch(int X) { }", NamedX)); EXPECT_TRUE(matches("namespace X { }", NamedX)); } TEST_P(ASTMatchersTest, MatchesNameRE) { DeclarationMatcher NamedX = namedDecl(matchesName("::X")); EXPECT_TRUE(matches("typedef int Xa;", NamedX)); EXPECT_TRUE(matches("int Xb;", NamedX)); EXPECT_TRUE(matches("void foo() { int Xgh; }", NamedX)); EXPECT_TRUE(matches("enum X { A, B, C };", NamedX)); EXPECT_TRUE(notMatches("#define Xkl 1", NamedX)); DeclarationMatcher StartsWithNo = namedDecl(matchesName("::no")); EXPECT_TRUE(matches("int no_foo;", StartsWithNo)); DeclarationMatcher Abc = namedDecl(matchesName("a.*b.*c")); EXPECT_TRUE(matches("int abc;", Abc)); EXPECT_TRUE(matches("int aFOObBARc;", Abc)); EXPECT_TRUE(notMatches("int cab;", Abc)); EXPECT_TRUE(matches("int cabc;", Abc)); DeclarationMatcher StartsWithK = namedDecl(matchesName(":k[^:]*$")); EXPECT_TRUE(matches("int k;", StartsWithK)); EXPECT_TRUE(matches("int kAbc;", StartsWithK)); } TEST_P(ASTMatchersTest, MatchesNameRE_CXX) { if (!GetParam().isCXX()) { return; } DeclarationMatcher NamedX = namedDecl(matchesName("::X")); EXPECT_TRUE(matches("class foo { virtual void Xc(); };", NamedX)); EXPECT_TRUE(matches("void foo() try { } catch(int Xdef) { }", NamedX)); EXPECT_TRUE(matches("namespace Xij { }", NamedX)); DeclarationMatcher StartsWithNo = namedDecl(matchesName("::no")); EXPECT_TRUE(matches("class foo { virtual void nobody(); };", StartsWithNo)); DeclarationMatcher StartsWithK = namedDecl(matchesName(":k[^:]*$")); EXPECT_TRUE(matches("namespace x { int kTest; }", StartsWithK)); EXPECT_TRUE(matches("class C { int k; };", StartsWithK)); EXPECT_TRUE(notMatches("class C { int ckc; };", StartsWithK)); EXPECT_TRUE(notMatches("int K;", StartsWithK)); DeclarationMatcher StartsWithKIgnoreCase = namedDecl(matchesName(":k[^:]*$", llvm::Regex::IgnoreCase)); EXPECT_TRUE(matches("int k;", StartsWithKIgnoreCase)); EXPECT_TRUE(matches("int K;", StartsWithKIgnoreCase)); } TEST_P(ASTMatchersTest, DeclarationMatcher_MatchClass) { if (!GetParam().isCXX()) { return; } DeclarationMatcher ClassX = recordDecl(recordDecl(hasName("X"))); EXPECT_TRUE(matches("class X;", ClassX)); EXPECT_TRUE(matches("class X {};", ClassX)); EXPECT_TRUE(matches("template class X {};", ClassX)); EXPECT_TRUE(notMatches("", ClassX)); } TEST_P(ASTMatchersTest, TranslationUnitDecl) { if (!GetParam().isCXX()) { // FIXME: Add a test for `translationUnitDecl()` that does not depend on // C++. return; } StringRef Code = "int MyVar1;\n" "namespace NameSpace {\n" "int MyVar2;\n" "} // namespace NameSpace\n"; EXPECT_TRUE(matches( Code, varDecl(hasName("MyVar1"), hasDeclContext(translationUnitDecl())))); EXPECT_FALSE(matches( Code, varDecl(hasName("MyVar2"), hasDeclContext(translationUnitDecl())))); EXPECT_TRUE(matches( Code, varDecl(hasName("MyVar2"), hasDeclContext(decl(hasDeclContext(translationUnitDecl())))))); } TEST_P(ASTMatchersTest, LinkageSpecDecl) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(matches("extern \"C\" { void foo() {}; }", linkageSpecDecl())); EXPECT_TRUE(notMatches("void foo() {};", linkageSpecDecl())); } TEST_P(ASTMatchersTest, ClassTemplateDecl_DoesNotMatchClass) { if (!GetParam().isCXX()) { return; } DeclarationMatcher ClassX = classTemplateDecl(hasName("X")); EXPECT_TRUE(notMatches("class X;", ClassX)); EXPECT_TRUE(notMatches("class X {};", ClassX)); } TEST_P(ASTMatchersTest, ClassTemplateDecl_MatchesClassTemplate) { if (!GetParam().isCXX()) { return; } DeclarationMatcher ClassX = classTemplateDecl(hasName("X")); EXPECT_TRUE(matches("template class X {};", ClassX)); EXPECT_TRUE(matches("class Z { template class X {}; };", ClassX)); } TEST_P(ASTMatchersTest, ClassTemplateDecl_DoesNotMatchClassTemplateExplicitSpecialization) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(notMatches( "template class X { };" "template<> class X { int a; };", classTemplateDecl(hasName("X"), hasDescendant(fieldDecl(hasName("a")))))); } TEST_P(ASTMatchersTest, ClassTemplateDecl_DoesNotMatchClassTemplatePartialSpecialization) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(notMatches( "template class X { };" "template class X { int a; };", classTemplateDecl(hasName("X"), hasDescendant(fieldDecl(hasName("a")))))); } TEST(ASTMatchersTestCUDA, CUDAKernelCallExpr) { EXPECT_TRUE(matchesWithCuda("__global__ void f() { }" "void g() { f<<<1, 2>>>(); }", cudaKernelCallExpr())); EXPECT_TRUE(notMatchesWithCuda("void f() {}", cudaKernelCallExpr())); } TEST(ASTMatchersTestCUDA, HasAttrCUDA) { EXPECT_TRUE(matchesWithCuda("__attribute__((device)) void f() {}", hasAttr(clang::attr::CUDADevice))); EXPECT_FALSE(notMatchesWithCuda("__attribute__((global)) void f() {}", hasAttr(clang::attr::CUDAGlobal))); } TEST_P(ASTMatchersTest, ValueDecl) { if (!GetParam().isCXX()) { // FIXME: Fix this test in non-C++ language modes. return; } EXPECT_TRUE(matches("enum EnumType { EnumValue };", valueDecl(hasType(asString("enum EnumType"))))); EXPECT_TRUE(matches("void FunctionDecl();", valueDecl(hasType(asString("void (void)"))))); } TEST_P(ASTMatchersTest, FriendDecl) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(matches("class Y { friend class X; };", friendDecl(hasType(asString("class X"))))); EXPECT_TRUE(matches("class Y { friend class X; };", friendDecl(hasType(recordDecl(hasName("X")))))); EXPECT_TRUE(matches("class Y { friend void f(); };", functionDecl(hasName("f"), hasParent(friendDecl())))); } TEST_P(ASTMatchersTest, EnumDecl_DoesNotMatchClasses) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(notMatches("class X {};", enumDecl(hasName("X")))); } TEST_P(ASTMatchersTest, EnumDecl_MatchesEnums) { if (!GetParam().isCXX()) { // FIXME: Fix this test in non-C++ language modes. return; } EXPECT_TRUE(matches("enum X {};", enumDecl(hasName("X")))); } TEST_P(ASTMatchersTest, EnumConstantDecl) { if (!GetParam().isCXX()) { // FIXME: Fix this test in non-C++ language modes. return; } DeclarationMatcher Matcher = enumConstantDecl(hasName("A")); EXPECT_TRUE(matches("enum X{ A };", Matcher)); EXPECT_TRUE(notMatches("enum X{ B };", Matcher)); EXPECT_TRUE(notMatches("enum X {};", Matcher)); } TEST_P(ASTMatchersTest, TagDecl) { if (!GetParam().isCXX()) { // FIXME: Fix this test in non-C++ language modes. return; } EXPECT_TRUE(matches("struct X {};", tagDecl(hasName("X")))); EXPECT_TRUE(matches("union U {};", tagDecl(hasName("U")))); EXPECT_TRUE(matches("enum E {};", tagDecl(hasName("E")))); } TEST_P(ASTMatchersTest, TagDecl_CXX) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(matches("class C {};", tagDecl(hasName("C")))); } TEST_P(ASTMatchersTest, UnresolvedLookupExpr) { if (!GetParam().isCXX() || GetParam().hasDelayedTemplateParsing()) { // FIXME: Fix this test to work with delayed template parsing. return; } EXPECT_TRUE(matches("template" "T foo() { T a; return a; }" "template" "void bar() {" " foo();" "}", unresolvedLookupExpr())); } TEST_P(ASTMatchersTest, UsesADL) { if (!GetParam().isCXX()) { return; } StatementMatcher ADLMatch = callExpr(usesADL()); StatementMatcher ADLMatchOper = cxxOperatorCallExpr(usesADL()); StringRef NS_Str = R"cpp( namespace NS { struct X {}; void f(X); void operator+(X, X); } struct MyX {}; void f(...); void operator+(MyX, MyX); )cpp"; auto MkStr = [&](StringRef Body) { return (NS_Str + "void test_fn() { " + Body + " }").str(); }; EXPECT_TRUE(matches(MkStr("NS::X x; f(x);"), ADLMatch)); EXPECT_TRUE(notMatches(MkStr("NS::X x; NS::f(x);"), ADLMatch)); EXPECT_TRUE(notMatches(MkStr("MyX x; f(x);"), ADLMatch)); EXPECT_TRUE(notMatches(MkStr("NS::X x; using NS::f; f(x);"), ADLMatch)); // Operator call expressions EXPECT_TRUE(matches(MkStr("NS::X x; x + x;"), ADLMatch)); EXPECT_TRUE(matches(MkStr("NS::X x; x + x;"), ADLMatchOper)); EXPECT_TRUE(notMatches(MkStr("MyX x; x + x;"), ADLMatch)); EXPECT_TRUE(notMatches(MkStr("MyX x; x + x;"), ADLMatchOper)); EXPECT_TRUE(matches(MkStr("NS::X x; operator+(x, x);"), ADLMatch)); EXPECT_TRUE(notMatches(MkStr("NS::X x; NS::operator+(x, x);"), ADLMatch)); } TEST_P(ASTMatchersTest, CallExpr_CXX) { if (!GetParam().isCXX()) { // FIXME: Add a test for `callExpr()` that does not depend on C++. return; } // FIXME: Do we want to overload Call() to directly take // Matcher, too? StatementMatcher MethodX = callExpr(hasDeclaration(cxxMethodDecl(hasName("x")))); EXPECT_TRUE(matches("class Y { void x() { x(); } };", MethodX)); EXPECT_TRUE(notMatches("class Y { void x() {} };", MethodX)); StatementMatcher MethodOnY = cxxMemberCallExpr(on(hasType(recordDecl(hasName("Y"))))); EXPECT_TRUE(matches("class Y { public: void x(); }; void z() { Y y; y.x(); }", MethodOnY)); EXPECT_TRUE(matches("class Y { public: void x(); }; void z(Y &y) { y.x(); }", MethodOnY)); EXPECT_TRUE(notMatches( "class Y { public: void x(); }; void z(Y *&y) { y->x(); }", MethodOnY)); EXPECT_TRUE(notMatches( "class Y { public: void x(); }; void z(Y y[]) { y->x(); }", MethodOnY)); EXPECT_TRUE(notMatches( "class Y { public: void x(); }; void z() { Y *y; y->x(); }", MethodOnY)); StatementMatcher MethodOnYPointer = cxxMemberCallExpr(on(hasType(pointsTo(recordDecl(hasName("Y")))))); EXPECT_TRUE( matches("class Y { public: void x(); }; void z() { Y *y; y->x(); }", MethodOnYPointer)); EXPECT_TRUE( matches("class Y { public: void x(); }; void z(Y *&y) { y->x(); }", MethodOnYPointer)); EXPECT_TRUE( matches("class Y { public: void x(); }; void z(Y y[]) { y->x(); }", MethodOnYPointer)); EXPECT_TRUE( notMatches("class Y { public: void x(); }; void z() { Y y; y.x(); }", MethodOnYPointer)); EXPECT_TRUE( notMatches("class Y { public: void x(); }; void z(Y &y) { y.x(); }", MethodOnYPointer)); } TEST_P(ASTMatchersTest, LambdaExpr) { if (!GetParam().isCXX11OrLater()) { return; } EXPECT_TRUE(matches("auto f = [] (int i) { return i; };", lambdaExpr())); } TEST_P(ASTMatchersTest, CXXForRangeStmt) { EXPECT_TRUE( notMatches("void f() { for (int i; i<5; ++i); }", cxxForRangeStmt())); } TEST_P(ASTMatchersTest, CXXForRangeStmt_CXX11) { if (!GetParam().isCXX11OrLater()) { return; } EXPECT_TRUE(matches("int as[] = { 1, 2, 3 };" "void f() { for (auto &a : as); }", cxxForRangeStmt())); } TEST_P(ASTMatchersTest, SubstNonTypeTemplateParmExpr) { if (!GetParam().isCXX()) { return; } EXPECT_FALSE(matches("template\n" "struct A { static const int n = 0; };\n" "struct B : public A<42> {};", traverse(TK_AsIs, substNonTypeTemplateParmExpr()))); EXPECT_TRUE(matches("template\n" "struct A { static const int n = N; };\n" "struct B : public A<42> {};", traverse(TK_AsIs, substNonTypeTemplateParmExpr()))); } TEST_P(ASTMatchersTest, NonTypeTemplateParmDecl) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(matches("template void f();", nonTypeTemplateParmDecl(hasName("N")))); EXPECT_TRUE( notMatches("template void f();", nonTypeTemplateParmDecl())); } TEST_P(ASTMatchersTest, TemplateTypeParmDecl) { if (!GetParam().isCXX()) { return; } EXPECT_TRUE(matches("template void f();", templateTypeParmDecl(hasName("T")))); EXPECT_TRUE(notMatches("template void f();", templateTypeParmDecl())); } TEST_P(ASTMatchersTest, TemplateTemplateParmDecl) { if (!GetParam().isCXX()) return; EXPECT_TRUE(matches("template