//===- unittest/ASTMatchers/Dynamic/RegistryTest.cpp - Registry 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/ASTMatchers/Dynamic/Registry.h" #include "gtest/gtest.h" #include namespace clang { namespace ast_matchers { namespace dynamic { namespace { using ast_matchers::internal::Matcher; class RegistryTest : public ::testing::Test { public: std::vector Args() { return std::vector(); } std::vector Args(const VariantValue &Arg1) { std::vector Out(1); Out[0].Value = Arg1; return Out; } std::vector Args(const VariantValue &Arg1, const VariantValue &Arg2) { std::vector Out(2); Out[0].Value = Arg1; Out[1].Value = Arg2; return Out; } llvm::Optional lookupMatcherCtor(StringRef MatcherName) { return Registry::lookupMatcherCtor(MatcherName); } VariantMatcher constructMatcher(StringRef MatcherName, Diagnostics *Error = nullptr) { Diagnostics DummyError; if (!Error) Error = &DummyError; llvm::Optional Ctor = lookupMatcherCtor(MatcherName); VariantMatcher Out; if (Ctor) Out = Registry::constructMatcher(*Ctor, SourceRange(), Args(), Error); EXPECT_EQ("", DummyError.toStringFull()); return Out; } VariantMatcher constructMatcher(StringRef MatcherName, const VariantValue &Arg1, Diagnostics *Error = nullptr) { Diagnostics DummyError; if (!Error) Error = &DummyError; llvm::Optional Ctor = lookupMatcherCtor(MatcherName); VariantMatcher Out; if (Ctor) Out = Registry::constructMatcher(*Ctor, SourceRange(), Args(Arg1), Error); EXPECT_EQ("", DummyError.toStringFull()) << MatcherName; return Out; } VariantMatcher constructMatcher(StringRef MatcherName, const VariantValue &Arg1, const VariantValue &Arg2, Diagnostics *Error = nullptr) { Diagnostics DummyError; if (!Error) Error = &DummyError; llvm::Optional Ctor = lookupMatcherCtor(MatcherName); VariantMatcher Out; if (Ctor) Out = Registry::constructMatcher(*Ctor, SourceRange(), Args(Arg1, Arg2), Error); EXPECT_EQ("", DummyError.toStringFull()); return Out; } typedef std::vector CompVector; CompVector getCompletions() { std::vector > Context; return Registry::getMatcherCompletions( Registry::getAcceptedCompletionTypes(Context)); } CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1) { std::vector > Context; llvm::Optional Ctor = lookupMatcherCtor(MatcherName1); if (!Ctor) return CompVector(); Context.push_back(std::make_pair(*Ctor, ArgNo1)); return Registry::getMatcherCompletions( Registry::getAcceptedCompletionTypes(Context)); } CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1, StringRef MatcherName2, unsigned ArgNo2) { std::vector > Context; llvm::Optional Ctor = lookupMatcherCtor(MatcherName1); if (!Ctor) return CompVector(); Context.push_back(std::make_pair(*Ctor, ArgNo1)); Ctor = lookupMatcherCtor(MatcherName2); if (!Ctor) return CompVector(); Context.push_back(std::make_pair(*Ctor, ArgNo2)); return Registry::getMatcherCompletions( Registry::getAcceptedCompletionTypes(Context)); } bool hasCompletion(const CompVector &Comps, StringRef TypedText, StringRef MatcherDecl = StringRef()) { for (CompVector::const_iterator I = Comps.begin(), E = Comps.end(); I != E; ++I) { if (I->TypedText == TypedText && (MatcherDecl.empty() || I->MatcherDecl == MatcherDecl)) { return true; } } return false; } }; TEST_F(RegistryTest, CanConstructNoArgs) { Matcher IsArrowValue = constructMatcher( "memberExpr", constructMatcher("isArrow")).getTypedMatcher(); Matcher BoolValue = constructMatcher("cxxBoolLiteral").getTypedMatcher(); const std::string ClassSnippet = "struct Foo { int x; };\n" "Foo *foo = new Foo;\n" "int i = foo->x;\n"; const std::string BoolSnippet = "bool Foo = true;\n"; EXPECT_TRUE(matches(ClassSnippet, IsArrowValue)); EXPECT_TRUE(matches(BoolSnippet, BoolValue)); EXPECT_FALSE(matches(ClassSnippet, BoolValue)); EXPECT_FALSE(matches(BoolSnippet, IsArrowValue)); } TEST_F(RegistryTest, ConstructWithSimpleArgs) { Matcher Value = constructMatcher( "namedDecl", constructMatcher("hasName", StringRef("X"))) .getTypedMatcher(); EXPECT_TRUE(matches("class X {};", Value)); EXPECT_FALSE(matches("int x;", Value)); Value = functionDecl(constructMatcher("parameterCountIs", 2) .getTypedMatcher()); EXPECT_TRUE(matches("void foo(int,int);", Value)); EXPECT_FALSE(matches("void foo(int);", Value)); } TEST_F(RegistryTest, ConstructWithMatcherArgs) { Matcher HasInitializerSimple = constructMatcher( "varDecl", constructMatcher("hasInitializer", constructMatcher("stmt"))) .getTypedMatcher(); Matcher HasInitializerComplex = constructMatcher( "varDecl", constructMatcher("hasInitializer", constructMatcher("callExpr"))) .getTypedMatcher(); std::string code = "int i;"; EXPECT_FALSE(matches(code, HasInitializerSimple)); EXPECT_FALSE(matches(code, HasInitializerComplex)); code = "int i = 1;"; EXPECT_TRUE(matches(code, HasInitializerSimple)); EXPECT_FALSE(matches(code, HasInitializerComplex)); code = "int y(); int i = y();"; EXPECT_TRUE(matches(code, HasInitializerSimple)); EXPECT_TRUE(matches(code, HasInitializerComplex)); Matcher HasParameter = functionDecl(constructMatcher( "hasParameter", 1, constructMatcher("hasName", StringRef("x"))) .getTypedMatcher()); EXPECT_TRUE(matches("void f(int a, int x);", HasParameter)); EXPECT_FALSE(matches("void f(int x, int a);", HasParameter)); } TEST_F(RegistryTest, OverloadedMatchers) { Matcher CallExpr0 = constructMatcher( "callExpr", constructMatcher("callee", constructMatcher("memberExpr", constructMatcher("isArrow")))) .getTypedMatcher(); Matcher CallExpr1 = constructMatcher( "callExpr", constructMatcher( "callee", constructMatcher("cxxMethodDecl", constructMatcher("hasName", StringRef("x"))))) .getTypedMatcher(); std::string Code = "class Y { public: void x(); }; void z() { Y y; y.x(); }"; EXPECT_FALSE(matches(Code, CallExpr0)); EXPECT_TRUE(matches(Code, CallExpr1)); Code = "class Z { public: void z() { this->z(); } };"; EXPECT_TRUE(matches(Code, CallExpr0)); EXPECT_FALSE(matches(Code, CallExpr1)); Matcher DeclDecl = declaratorDecl(hasTypeLoc( constructMatcher( "loc", constructMatcher("asString", StringRef("const double *"))) .getTypedMatcher())); Matcher NNSL = constructMatcher( "loc", VariantMatcher::SingleMatcher(nestedNameSpecifier( specifiesType(hasDeclaration(recordDecl(hasName("A"))))))) .getTypedMatcher(); Code = "const double * x = 0;"; EXPECT_TRUE(matches(Code, DeclDecl)); EXPECT_FALSE(matches(Code, NNSL)); Code = "struct A { struct B {}; }; A::B a_b;"; EXPECT_FALSE(matches(Code, DeclDecl)); EXPECT_TRUE(matches(Code, NNSL)); } TEST_F(RegistryTest, PolymorphicMatchers) { const VariantMatcher IsDefinition = constructMatcher("isDefinition"); Matcher Var = constructMatcher("varDecl", IsDefinition).getTypedMatcher(); Matcher Class = constructMatcher("recordDecl", IsDefinition).getTypedMatcher(); Matcher Func = constructMatcher("functionDecl", IsDefinition).getTypedMatcher(); EXPECT_TRUE(matches("int a;", Var)); EXPECT_FALSE(matches("extern int a;", Var)); EXPECT_TRUE(matches("class A {};", Class)); EXPECT_FALSE(matches("class A;", Class)); EXPECT_TRUE(matches("void f(){};", Func)); EXPECT_FALSE(matches("void f();", Func)); Matcher Anything = constructMatcher("anything").getTypedMatcher(); Matcher RecordDecl = constructMatcher( "recordDecl", constructMatcher("hasName", StringRef("Foo")), VariantMatcher::SingleMatcher(Anything)).getTypedMatcher(); EXPECT_TRUE(matches("int Foo;", Anything)); EXPECT_TRUE(matches("class Foo {};", Anything)); EXPECT_TRUE(matches("void Foo(){};", Anything)); EXPECT_FALSE(matches("int Foo;", RecordDecl)); EXPECT_TRUE(matches("class Foo {};", RecordDecl)); EXPECT_FALSE(matches("void Foo(){};", RecordDecl)); Matcher ConstructExpr = constructMatcher( "cxxConstructExpr", constructMatcher( "hasDeclaration", constructMatcher( "cxxMethodDecl", constructMatcher( "ofClass", constructMatcher("hasName", StringRef("Foo")))))) .getTypedMatcher(); EXPECT_FALSE(matches("class Foo { public: Foo(); };", ConstructExpr)); EXPECT_TRUE( matches("class Foo { public: Foo(); }; Foo foo = Foo();", ConstructExpr)); } TEST_F(RegistryTest, TemplateArgument) { Matcher HasTemplateArgument = constructMatcher( "classTemplateSpecializationDecl", constructMatcher( "hasAnyTemplateArgument", constructMatcher("refersToType", constructMatcher("asString", StringRef("int"))))) .getTypedMatcher(); EXPECT_TRUE(matches("template class A {}; A a;", HasTemplateArgument)); EXPECT_FALSE(matches("template class A {}; A a;", HasTemplateArgument)); } TEST_F(RegistryTest, TypeTraversal) { Matcher M = constructMatcher( "pointerType", constructMatcher("pointee", constructMatcher("isConstQualified"), constructMatcher("isInteger"))).getTypedMatcher(); EXPECT_FALSE(matches("int *a;", M)); EXPECT_TRUE(matches("int const *b;", M)); M = constructMatcher( "arrayType", constructMatcher("hasElementType", constructMatcher("builtinType"))) .getTypedMatcher(); EXPECT_FALSE(matches("struct A{}; A a[7];;", M)); EXPECT_TRUE(matches("int b[7];", M)); } TEST_F(RegistryTest, CXXCtorInitializer) { Matcher CtorDecl = constructMatcher( "cxxConstructorDecl", constructMatcher( "hasAnyConstructorInitializer", constructMatcher("forField", constructMatcher("hasName", StringRef("foo"))))) .getTypedMatcher(); EXPECT_TRUE(matches("struct Foo { Foo() : foo(1) {} int foo; };", CtorDecl)); EXPECT_FALSE(matches("struct Foo { Foo() {} int foo; };", CtorDecl)); EXPECT_FALSE(matches("struct Foo { Foo() : bar(1) {} int bar; };", CtorDecl)); } TEST_F(RegistryTest, Adaptative) { Matcher D = constructMatcher( "recordDecl", constructMatcher( "has", constructMatcher("recordDecl", constructMatcher("hasName", StringRef("X"))))) .getTypedMatcher(); EXPECT_TRUE(matches("class X {};", D)); EXPECT_TRUE(matches("class Y { class X {}; };", D)); EXPECT_FALSE(matches("class Y { class Z {}; };", D)); Matcher S = constructMatcher( "forStmt", constructMatcher( "hasDescendant", constructMatcher("varDecl", constructMatcher("hasName", StringRef("X"))))) .getTypedMatcher(); EXPECT_TRUE(matches("void foo() { for(int X;;); }", S)); EXPECT_TRUE(matches("void foo() { for(;;) { int X; } }", S)); EXPECT_FALSE(matches("void foo() { for(;;); }", S)); EXPECT_FALSE(matches("void foo() { if (int X = 0){} }", S)); S = constructMatcher( "compoundStmt", constructMatcher("hasParent", constructMatcher("ifStmt"))) .getTypedMatcher(); EXPECT_TRUE(matches("void foo() { if (true) { int x = 42; } }", S)); EXPECT_FALSE(matches("void foo() { if (true) return; }", S)); } TEST_F(RegistryTest, VariadicOp) { Matcher D = constructMatcher( "anyOf", constructMatcher("recordDecl", constructMatcher("hasName", StringRef("Foo"))), constructMatcher("functionDecl", constructMatcher("hasName", StringRef("foo")))) .getTypedMatcher(); EXPECT_TRUE(matches("void foo(){}", D)); EXPECT_TRUE(matches("struct Foo{};", D)); EXPECT_FALSE(matches("int i = 0;", D)); D = constructMatcher( "allOf", constructMatcher("recordDecl"), constructMatcher( "namedDecl", constructMatcher("anyOf", constructMatcher("hasName", StringRef("Foo")), constructMatcher("hasName", StringRef("Bar"))))) .getTypedMatcher(); EXPECT_FALSE(matches("void foo(){}", D)); EXPECT_TRUE(matches("struct Foo{};", D)); EXPECT_FALSE(matches("int i = 0;", D)); EXPECT_TRUE(matches("class Bar{};", D)); EXPECT_FALSE(matches("class OtherBar{};", D)); D = recordDecl( has(fieldDecl(hasName("Foo"))), constructMatcher( "unless", constructMatcher("namedDecl", constructMatcher("hasName", StringRef("Bar")))) .getTypedMatcher()); EXPECT_FALSE(matches("class Bar{ int Foo; };", D)); EXPECT_TRUE(matches("class OtherBar{ int Foo; };", D)); D = constructMatcher( "namedDecl", constructMatcher("hasName", StringRef("Foo")), constructMatcher("unless", constructMatcher("recordDecl"))) .getTypedMatcher(); EXPECT_TRUE(matches("void Foo(){}", D)); EXPECT_TRUE(notMatches("struct Foo {};", D)); } TEST_F(RegistryTest, Errors) { // Incorrect argument count. std::unique_ptr Error(new Diagnostics()); EXPECT_TRUE(constructMatcher("hasInitializer", Error.get()).isNull()); EXPECT_EQ("Incorrect argument count. (Expected = 1) != (Actual = 0)", Error->toString()); Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher("isArrow", StringRef(), Error.get()).isNull()); EXPECT_EQ("Incorrect argument count. (Expected = 0) != (Actual = 1)", Error->toString()); Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher("anyOf", Error.get()).isNull()); EXPECT_EQ("Incorrect argument count. (Expected = (2, )) != (Actual = 0)", Error->toString()); Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher("unless", StringRef(), StringRef(), Error.get()).isNull()); EXPECT_EQ("Incorrect argument count. (Expected = (1, 1)) != (Actual = 2)", Error->toString()); // Bad argument type Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher("ofClass", StringRef(), Error.get()).isNull()); EXPECT_EQ("Incorrect type for arg 1. (Expected = Matcher) != " "(Actual = String)", Error->toString()); Error.reset(new Diagnostics()); EXPECT_TRUE( constructMatcher("cxxRecordDecl", constructMatcher("cxxRecordDecl"), constructMatcher("parameterCountIs", 3), Error.get()) .isNull()); EXPECT_EQ("Incorrect type for arg 2. (Expected = Matcher) != " "(Actual = Matcher)", Error->toString()); // Bad argument type with variadic. Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher("anyOf", StringRef(), StringRef(), Error.get()).isNull()); EXPECT_EQ( "Incorrect type for arg 1. (Expected = Matcher<>) != (Actual = String)", Error->toString()); Error.reset(new Diagnostics()); EXPECT_TRUE(constructMatcher( "cxxRecordDecl", constructMatcher("allOf", constructMatcher("isDerivedFrom", StringRef("FOO")), constructMatcher("isArrow")), Error.get()).isNull()); EXPECT_EQ("Incorrect type for arg 1. " "(Expected = Matcher) != " "(Actual = Matcher&Matcher" ")", Error->toString()); } TEST_F(RegistryTest, Completion) { CompVector Comps = getCompletions(); // Overloaded EXPECT_TRUE(hasCompletion( Comps, "hasParent(", "Matcher " "hasParent(Matcher)")); // Variadic. EXPECT_TRUE(hasCompletion(Comps, "whileStmt(", "Matcher whileStmt(Matcher...)")); // Polymorphic. EXPECT_TRUE(hasCompletion( Comps, "hasDescendant(", "Matcher " "hasDescendant(Matcher)")); CompVector WhileComps = getCompletions("whileStmt", 0); EXPECT_TRUE(hasCompletion(WhileComps, "hasBody(", "Matcher hasBody(Matcher)")); EXPECT_TRUE(hasCompletion( WhileComps, "hasParent(", "Matcher " "hasParent(Matcher)")); EXPECT_TRUE( hasCompletion(WhileComps, "allOf(", "Matcher allOf(Matcher...)")); EXPECT_FALSE(hasCompletion(WhileComps, "whileStmt(")); EXPECT_FALSE(hasCompletion(WhileComps, "ifStmt(")); CompVector AllOfWhileComps = getCompletions("allOf", 0, "whileStmt", 0); ASSERT_EQ(AllOfWhileComps.size(), WhileComps.size()); EXPECT_TRUE(std::equal(WhileComps.begin(), WhileComps.end(), AllOfWhileComps.begin())); CompVector DeclWhileComps = getCompletions("decl", 0, "whileStmt", 0); EXPECT_EQ(0u, DeclWhileComps.size()); CompVector NamedDeclComps = getCompletions("namedDecl", 0); EXPECT_TRUE( hasCompletion(NamedDeclComps, "isPublic()", "Matcher isPublic()")); EXPECT_TRUE(hasCompletion(NamedDeclComps, "hasName(\"", "Matcher hasName(string)")); // Heterogeneous overloads. Comps = getCompletions("classTemplateSpecializationDecl", 0); EXPECT_TRUE(hasCompletion( Comps, "isSameOrDerivedFrom(", "Matcher isSameOrDerivedFrom(string|Matcher)")); } TEST_F(RegistryTest, HasArgs) { Matcher Value = constructMatcher( "decl", constructMatcher("hasAttr", StringRef("attr::WarnUnused"))) .getTypedMatcher(); EXPECT_TRUE(matches("struct __attribute__((warn_unused)) X {};", Value)); EXPECT_FALSE(matches("struct X {};", Value)); } TEST_F(RegistryTest, ParenExpr) { Matcher Value = constructMatcher("parenExpr").getTypedMatcher(); EXPECT_TRUE(matches("int i = (1);", traverse(TK_AsIs, Value))); EXPECT_FALSE(matches("int i = 1;", traverse(TK_AsIs, Value))); } TEST_F(RegistryTest, EqualsMatcher) { Matcher BooleanStmt = constructMatcher( "cxxBoolLiteral", constructMatcher("equals", VariantValue(true))) .getTypedMatcher(); EXPECT_TRUE(matches("bool x = true;", BooleanStmt)); EXPECT_FALSE(matches("bool x = false;", BooleanStmt)); EXPECT_FALSE(matches("bool x = 0;", BooleanStmt)); BooleanStmt = constructMatcher( "cxxBoolLiteral", constructMatcher("equals", VariantValue(0))) .getTypedMatcher(); EXPECT_TRUE(matches("bool x = false;", BooleanStmt)); EXPECT_FALSE(matches("bool x = true;", BooleanStmt)); EXPECT_FALSE(matches("bool x = 0;", BooleanStmt)); Matcher DoubleStmt = constructMatcher( "floatLiteral", constructMatcher("equals", VariantValue(1.2))) .getTypedMatcher(); EXPECT_TRUE(matches("double x = 1.2;", DoubleStmt)); #if 0 // FIXME floatLiteral matching should work regardless of suffix. EXPECT_TRUE(matches("double x = 1.2f;", DoubleStmt)); EXPECT_TRUE(matches("double x = 1.2l;", DoubleStmt)); #endif EXPECT_TRUE(matches("double x = 12e-1;", DoubleStmt)); EXPECT_FALSE(matches("double x = 1.23;", DoubleStmt)); Matcher IntegerStmt = constructMatcher( "integerLiteral", constructMatcher("equals", VariantValue(42))) .getTypedMatcher(); EXPECT_TRUE(matches("int x = 42;", IntegerStmt)); EXPECT_FALSE(matches("int x = 1;", IntegerStmt)); Matcher CharStmt = constructMatcher( "characterLiteral", constructMatcher("equals", VariantValue('x'))) .getTypedMatcher(); EXPECT_TRUE(matches("int x = 'x';", CharStmt)); EXPECT_TRUE(matches("int x = L'x';", CharStmt)); EXPECT_TRUE(matches("int x = u'x';", CharStmt)); EXPECT_TRUE(matches("int x = U'x';", CharStmt)); EXPECT_FALSE(matches("int x = 120;", CharStmt)); } } // end anonymous namespace } // end namespace dynamic } // end namespace ast_matchers } // end namespace clang