//===-- HeuristicResolverTests.cpp --------------------------*- C++ -*-----===// // // 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 "clang/Sema/HeuristicResolver.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace clang::ast_matchers; using testing::ElementsAre; namespace clang { namespace { // Helper for matching a sequence of elements with a variadic list of matchers. // Usage: `ElementsAre(matchAdapter(Vs, MatchFunction)...)`, where `Vs...` is // a variadic list of matchers. // For each `V` in `Vs`, this will match the corresponding element `E` if // `MatchFunction(V, E)` is true. MATCHER_P2(matchAdapter, MatcherForElement, MatchFunction, "matchAdapter") { return MatchFunction(MatcherForElement, arg); } template using ResolveFnT = std::function( const HeuristicResolver *, const InputNode *)>; // Test heuristic resolution on `Code` using the resolution procedure // `ResolveFn`, which takes a `HeuristicResolver` and an input AST node of type // `InputNode` and returns a `std::vector`. // `InputMatcher` should be an AST matcher that matches a single node to pass as // input to `ResolveFn`, bound to the ID "input". `OutputMatchers` should be AST // matchers that each match a single node, bound to the ID "output". template void expectResolution(llvm::StringRef Code, ResolveFnT ResolveFn, const InputMatcher &IM, const OutputMatchers &...OMS) { auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"}); auto &Ctx = TU->getASTContext(); auto InputMatches = match(IM, Ctx); ASSERT_EQ(1u, InputMatches.size()); const auto *Input = InputMatches[0].template getNodeAs("input"); ASSERT_TRUE(Input); auto OutputNodeMatches = [&](auto &OutputMatcher, auto &Actual) { auto OutputMatches = match(OutputMatcher, Ctx); if (OutputMatches.size() != 1u) return false; const auto *ExpectedOutput = OutputMatches[0].template getNodeAs("output"); if (!ExpectedOutput) return false; return ExpectedOutput == Actual; }; HeuristicResolver H(Ctx); auto Results = ResolveFn(&H, Input); EXPECT_THAT(Results, ElementsAre(matchAdapter(OMS, OutputNodeMatches)...)); } // Wrapper for the above that accepts a HeuristicResolver member function // pointer directly. template void expectResolution(llvm::StringRef Code, std::vector ( HeuristicResolver::*ResolveFn)(const InputNode *) const, const InputMatcher &IM, const OutputMatchers &...OMS) { expectResolution(Code, ResolveFnT(std::mem_fn(ResolveFn)), IM, OMS...); } TEST(HeuristicResolver, MemberExpr) { std::string Code = R"cpp( template struct S { void bar() {} }; template void foo(S arg) { arg.bar(); } )cpp"; // Test resolution of "bar" in "arg.bar()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("bar")).bind("input"), cxxMethodDecl(hasName("bar")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Overloads) { std::string Code = R"cpp( template struct S { void bar(int); void bar(float); }; template void foo(S arg, U u) { arg.bar(u); } )cpp"; // Test resolution of "bar" in "arg.bar(u)". Both overloads should be found. expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("bar")).bind("input"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("int")))) .bind("output"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("float")))) .bind("output")); } TEST(HeuristicResolver, MemberExpr_SmartPointer) { std::string Code = R"cpp( template struct S { void foo() {} }; template struct unique_ptr { T* operator->(); }; template void test(unique_ptr>& v) { v->foo(); } )cpp"; // Test resolution of "foo" in "v->foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_SmartPointer_Qualified) { std::string Code = R"cpp( template struct Waldo { void find(); void find() const; }; template struct unique_ptr { T* operator->(); }; template void test(unique_ptr>& w) { w->find(); } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find"), isConst()).bind("output")); } TEST(HeuristicResolver, MemberExpr_Static_Qualified) { std::string Code = R"cpp( template struct Waldo { static void find(); }; template void foo(const Waldo& t) { t.find(); } )cpp"; // Test resolution of "find" in "t.find()". // The object being `const` should have no bearing on a call to a static // method. expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_AutoTypeDeduction1) { std::string Code = R"cpp( template struct A { int waldo; }; template void foo(A a) { auto copy = a; copy.waldo; } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_AutoTypeDeduction2) { std::string Code = R"cpp( struct B { int waldo; }; template struct A { B b; }; template void foo(A a) { auto b = a.b; b.waldo; } )cpp"; expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Chained) { std::string Code = R"cpp( struct A { void foo() {} }; template struct B { A func(int); void bar() { func(1).foo(); } }; )cpp"; // Test resolution of "foo" in "func(1).foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_ReferenceType) { std::string Code = R"cpp( struct B { int waldo; }; template struct A { B &b; }; template void foo(A &a) { a.b.waldo; } )cpp"; // Test resolution of "waldo" in "a.b.waldo". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_PointerType) { std::string Code = R"cpp( struct B { int waldo; }; template struct A { B *b; }; template void foo(A &a) { a.b->waldo; } )cpp"; // Test resolution of "waldo" in "a.b->waldo". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("waldo")).bind("input"), fieldDecl(hasName("waldo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_TemplateArgs) { std::string Code = R"cpp( struct Foo { static Foo k(int); template T convert(); }; template void test() { Foo::k(T()).template convert(); } )cpp"; // Test resolution of "convert" in "Foo::k(T()).template convert()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("convert")).bind("input"), functionTemplateDecl(hasName("convert")).bind("output")); } TEST(HeuristicResolver, MemberExpr_TypeAlias) { std::string Code = R"cpp( template struct Waldo { void find(); }; template using Wally = Waldo; template void foo(Wally w) { w.find(); } )cpp"; // Test resolution of "find" in "w.find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_BaseClass_TypeAlias) { std::string Code = R"cpp( template struct Waldo { void find(); }; template using Wally = Waldo; template struct S : Wally { void foo() { this->find(); } }; )cpp"; // Test resolution of "find" in "this->find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Metafunction) { std::string Code = R"cpp( template struct Waldo { void find(); }; template struct MetaWaldo { using Type = Waldo; }; template void foo(typename MetaWaldo::Type w) { w.find(); } )cpp"; // Test resolution of "find" in "w.find()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"), cxxMethodDecl(hasName("find")).bind("output")); } TEST(HeuristicResolver, MemberExpr_Metafunction_Enumerator) { std::string Code = R"cpp( enum class State { Hidden }; template struct Meta { using Type = State; }; template void foo(typename Meta::Type t) { t.Hidden; } )cpp"; // Test resolution of "Hidden" in "t.Hidden". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("Hidden")).bind("input"), enumConstantDecl(hasName("Hidden")).bind("output")); } TEST(HeuristicResolver, MemberExpr_DeducedNonTypeTemplateParameter) { std::string Code = R"cpp( template struct Waldo { const int found = N; }; template int foo() { return W.found; } )cpp"; // Test resolution of "found" in "W.found". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("found")).bind("input"), fieldDecl(hasName("found")).bind("output")); } TEST(HeuristicResolver, MemberExpr_HangIssue126536) { std::string Code = R"cpp( template void foo() { T bar; auto baz = (bar, bar); baz.foo(); } )cpp"; // Test resolution of "foo" in "baz.foo()". // Here, we are testing that we do not get into an infinite loop. expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input")); } TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument) { std::string Code = R"cpp( struct Default { void foo(); }; template void bar(T t) { t.foo(); } )cpp"; // Test resolution of "foo" in "t.foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, MemberExpr_DefaultTemplateArgument_Recursive) { std::string Code = R"cpp( struct Default { void foo(); }; template void bar(T t) { t.foo(); } )cpp"; // Test resolution of "foo" in "t.foo()". expectResolution( Code, &HeuristicResolver::resolveMemberExpr, cxxDependentScopeMemberExpr(hasMemberName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_StaticMethod) { std::string Code = R"cpp( template struct S { static void bar() {} }; template void foo() { S::bar(); } )cpp"; // Test resolution of "bar" in "S::bar()". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("bar")).bind("input"), cxxMethodDecl(hasName("bar")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_DefaultTemplateArgument) { std::string Code = R"cpp( struct Default { static void foo(); }; template void bar() { T::foo(); } )cpp"; // Test resolution of "foo" in "T::foo()". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("foo")).bind("input"), cxxMethodDecl(hasName("foo")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_StaticOverloads) { std::string Code = R"cpp( template struct S { static void bar(int); static void bar(float); }; template void foo(U u) { S::bar(u); } )cpp"; // Test resolution of "bar" in "S::bar(u)". Both overloads should be found. expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("bar")).bind("input"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("int")))) .bind("output"), cxxMethodDecl(hasName("bar"), hasParameter(0, hasType(asString("float")))) .bind("output")); } TEST(HeuristicResolver, DeclRefExpr_Enumerator) { std::string Code = R"cpp( template struct Foo { enum class E { A, B }; E e = E::A; }; )cpp"; // Test resolution of "A" in "E::A". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("A")).bind("input"), enumConstantDecl(hasName("A")).bind("output")); } TEST(HeuristicResolver, DeclRefExpr_RespectScope) { std::string Code = R"cpp( template struct PointerIntPair { void *getPointer() const { return Info::getPointer(); } }; )cpp"; // Test resolution of "getPointer" in "Info::getPointer()". // Here, we are testing that we do not incorrectly get the enclosing // getPointer() function as a result. expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("getPointer")).bind("input")); } TEST(HeuristicResolver, DeclRefExpr_Nested) { std::string Code = R"cpp( struct S { static int Waldo; }; template struct Meta { using Type = S; }; template void foo() { Meta::Type::Waldo; } )cpp"; // Test resolution of "Waldo" in "Meta::Type::Waldo". expectResolution( Code, &HeuristicResolver::resolveDeclRefExpr, dependentScopeDeclRefExpr(hasDependentName("Waldo")).bind("input"), varDecl(hasName("Waldo")).bind("output")); } TEST(HeuristicResolver, DependentNameType) { std::string Code = R"cpp( template struct A { struct B {}; }; template void foo(typename A::B); )cpp"; // Tests resolution of "B" in "A::B". expectResolution( Code, &HeuristicResolver::resolveDependentNameType, functionDecl(hasParameter(0, hasType(dependentNameType().bind("input")))), classTemplateDecl( has(cxxRecordDecl(has(cxxRecordDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, DependentNameType_Nested) { std::string Code = R"cpp( template struct A { struct B { struct C {}; }; }; template void foo(typename A::B::C); )cpp"; // Tests resolution of "C" in "A::B::C". expectResolution( Code, &HeuristicResolver::resolveDependentNameType, functionDecl(hasParameter(0, hasType(dependentNameType().bind("input")))), classTemplateDecl(has(cxxRecordDecl(has( cxxRecordDecl(has(cxxRecordDecl(hasName("C")).bind("output")))))))); } TEST(HeuristicResolver, DependentNameType_Recursion) { std::string Code = R"cpp( template struct Waldo { using Type = typename Waldo::Type::Next; }; )cpp"; // Test resolution of "Next" in "typename Waldo::Type::Next". // Here, we are testing that we do not get into an infinite recursion. expectResolution(Code, &HeuristicResolver::resolveDependentNameType, typeAliasDecl(hasType(dependentNameType().bind("input")))); } TEST(HeuristicResolver, DependentNameType_MutualRecursion) { std::string Code = R"cpp( template struct Odd; template struct Even { using Type = typename Odd::Type::Next; }; template struct Odd { using Type = typename Even::Type::Next; }; )cpp"; // Test resolution of "Next" in "typename Even::Type::Next". // Similar to the above but we have two mutually recursive templates. expectResolution( Code, &HeuristicResolver::resolveDependentNameType, classTemplateDecl(hasName("Odd"), has(cxxRecordDecl(has(typeAliasDecl( hasType(dependentNameType().bind("input")))))))); } TEST(HeuristicResolver, NestedNameSpecifier) { // Test resolution of "B" in "A::B::C". // Unlike the "C", the "B" does not get its own DependentNameTypeLoc node, // so the resolution uses the NestedNameSpecifier as input. std::string Code = R"cpp( template struct A { struct B { struct C {}; }; }; template void foo(typename A::B::C); )cpp"; // Adapt the call to resolveNestedNameSpecifierToType() to the interface // expected by expectResolution() (returning a vector of decls). ResolveFnT ResolveFn = [](const HeuristicResolver *H, const NestedNameSpecifier *NNS) -> std::vector { return {H->resolveNestedNameSpecifierToType(NNS)->getAsCXXRecordDecl()}; }; expectResolution(Code, ResolveFn, nestedNameSpecifier(hasPrefix(specifiesType(hasDeclaration( classTemplateDecl(hasName("A")))))) .bind("input"), classTemplateDecl(has(cxxRecordDecl( has(cxxRecordDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, TemplateSpecializationType) { std::string Code = R"cpp( template struct A { template struct B {}; }; template void foo(typename A::template B); )cpp"; // Test resolution of "B" in "A::template B". expectResolution(Code, &HeuristicResolver::resolveTemplateSpecializationType, functionDecl(hasParameter(0, hasType(type().bind("input")))), classTemplateDecl(has(cxxRecordDecl( has(classTemplateDecl(hasName("B")).bind("output")))))); } TEST(HeuristicResolver, DependentCall_NonMember) { std::string Code = R"cpp( template void nonmember(T); template void bar(T t) { nonmember(t); } )cpp"; // Test resolution of "nonmember" in "nonmember(t)". expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(unresolvedLookupExpr(hasAnyDeclaration( functionTemplateDecl(hasName("nonmember")))))) .bind("input"), functionTemplateDecl(hasName("nonmember")).bind("output")); } TEST(HeuristicResolver, DependentCall_Member) { std::string Code = R"cpp( template struct A { void member(T); }; template void bar(A a, T t) { a.member(t); } )cpp"; // Test resolution of "member" in "a.member(t)". expectResolution( Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(cxxDependentScopeMemberExpr(hasMemberName("member")))) .bind("input"), cxxMethodDecl(hasName("member")).bind("output")); } TEST(HeuristicResolver, DependentCall_StaticMember) { std::string Code = R"cpp( template struct A { static void static_member(T); }; template void bar(T t) { A::static_member(t); } )cpp"; // Test resolution of "static_member" in "A::static_member(t)". expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(dependentScopeDeclRefExpr( hasDependentName("static_member")))) .bind("input"), cxxMethodDecl(hasName("static_member")).bind("output")); } TEST(HeuristicResolver, DependentCall_Overload) { std::string Code = R"cpp( void overload(int); void overload(double); template void bar(T t) { overload(t); } )cpp"; // Test resolution of "overload" in "overload(t)". Both overload should be // found. expectResolution(Code, &HeuristicResolver::resolveCalleeOfCallExpr, callExpr(callee(unresolvedLookupExpr(hasAnyDeclaration( functionDecl(hasName("overload")))))) .bind("input"), functionDecl(hasName("overload"), hasParameter(0, hasType(asString("double")))) .bind("output"), functionDecl(hasName("overload"), hasParameter(0, hasType(asString("int")))) .bind("output")); } TEST(HeuristicResolver, UsingValueDecl) { std::string Code = R"cpp( template struct Base { void waldo(); }; template struct Derived : Base { using Base::waldo; }; )cpp"; // Test resolution of "waldo" in "Base::waldo". expectResolution(Code, &HeuristicResolver::resolveUsingValueDecl, unresolvedUsingValueDecl(hasName("waldo")).bind("input"), cxxMethodDecl(hasName("waldo")).bind("output")); } // `arg` is a ParamVarDecl*, `Expected` is a string MATCHER_P(ParamNameMatcher, Expected, "paramNameMatcher") { EXPECT_TRUE(arg); if (IdentifierInfo *Ident = arg->getDeclName().getAsIdentifierInfo()) { return Ident->getName() == Expected; } return false; } // Helper function for testing HeuristicResolver::getProtoTypeLoc. // Takes a matcher that selects a callee expression bound to the ID "input", // calls getProtoTypeLoc() on it, and checks that the call found a // FunctionProtoTypeLoc encoding the given parameter names. template void expectParameterNames(ASTContext &Ctx, const InputMatcher &IM, ParameterNames... ExpectedParameterNames) { auto InputMatches = match(IM, Ctx); ASSERT_EQ(1u, InputMatches.size()); const auto *Input = InputMatches[0].template getNodeAs("input"); ASSERT_TRUE(Input); HeuristicResolver H(Ctx); auto Loc = H.getFunctionProtoTypeLoc(Input); ASSERT_TRUE(Loc); EXPECT_THAT(Loc.getParams(), ElementsAre(ParamNameMatcher(ExpectedParameterNames)...)); } TEST(HeuristicResolver, ProtoTypeLoc) { std::string Code = R"cpp( void (*f1)(int param1); void (__stdcall *f2)(int param2); using f3_t = void(*)(int param3); f3_t f3; using f4_t = void(__stdcall *)(int param4); f4_t f4; struct S { void (*f5)(int param5); using f6_t = void(*)(int param6); f6_t f6; }; void bar() { f1(42); f2(42); f3(42); f4(42); S s; s.f5(42); s.f6(42); } )cpp"; auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"}); auto &Ctx = TU->getASTContext(); auto checkFreeFunction = [&](llvm::StringRef FunctionName, llvm::StringRef ParamName) { expectParameterNames( Ctx, callExpr( callee(implicitCastExpr(hasSourceExpression(declRefExpr( to(namedDecl(hasName(FunctionName)))))) .bind("input"))), ParamName); }; checkFreeFunction("f1", "param1"); checkFreeFunction("f2", "param2"); checkFreeFunction("f3", "param3"); checkFreeFunction("f4", "param4"); auto checkMemberFunction = [&](llvm::StringRef MemberName, llvm::StringRef ParamName) { expectParameterNames( Ctx, callExpr(callee(implicitCastExpr(hasSourceExpression(memberExpr( member(hasName(MemberName))))) .bind("input"))), ParamName); }; checkMemberFunction("f5", "param5"); checkMemberFunction("f6", "param6"); } } // namespace } // namespace clang