//===--- WalkASTTest.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 "AnalysisInternal.h" #include "clang-include-cleaner/Types.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceLocation.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Testing/TestAST.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include #include #include namespace clang::include_cleaner { namespace { using testing::ElementsAre; // Specifies a test of which symbols are referenced by a piece of code. // Target should contain points annotated with the reference kind. // Example: // Target: int $explicit^foo(); // Referencing: int x = ^foo(); // There must be exactly one referencing location marked. // Returns target decls. std::vector testWalk(llvm::StringRef TargetCode, llvm::StringRef ReferencingCode) { llvm::Annotations Target(TargetCode); llvm::Annotations Referencing(ReferencingCode); TestInputs Inputs(Referencing.code()); Inputs.ExtraFiles["target.h"] = Target.code().str(); Inputs.ExtraArgs.push_back("-include"); Inputs.ExtraArgs.push_back("target.h"); Inputs.ExtraArgs.push_back("-std=c++20"); TestAST AST(Inputs); const auto &SM = AST.sourceManager(); // We're only going to record references from the nominated point, // to the target file. FileID ReferencingFile = SM.getMainFileID(); SourceLocation ReferencingLoc = SM.getComposedLoc(ReferencingFile, Referencing.point()); FileID TargetFile = SM.translateFile( llvm::cantFail(AST.fileManager().getFileRef("target.h"))); std::vector TargetDecls; // Perform the walk, and capture the offsets of the referenced targets. std::unordered_map> ReferencedOffsets; for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { if (ReferencingFile != SM.getDecomposedExpansionLoc(D->getLocation()).first) continue; walkAST(*D, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) { if (SM.getFileLoc(Loc) != ReferencingLoc) return; auto NDLoc = SM.getDecomposedLoc(SM.getFileLoc(ND.getLocation())); if (NDLoc.first != TargetFile) return; ReferencedOffsets[RT].push_back(NDLoc.second); TargetDecls.push_back(ND.getKind()); }); } for (auto &Entry : ReferencedOffsets) llvm::sort(Entry.second); // Compare results to the expected points. // For each difference, show the target point in context, like a diagnostic. std::string DiagBuf; llvm::raw_string_ostream DiagOS(DiagBuf); auto *DiagOpts = new DiagnosticOptions(); DiagOpts->ShowLevel = 0; DiagOpts->ShowNoteIncludeStack = 0; TextDiagnostic Diag(DiagOS, AST.context().getLangOpts(), DiagOpts); auto DiagnosePoint = [&](llvm::StringRef Message, unsigned Offset) { Diag.emitDiagnostic( FullSourceLoc(SM.getComposedLoc(TargetFile, Offset), SM), DiagnosticsEngine::Note, Message, {}, {}); }; for (auto RT : {RefType::Explicit, RefType::Implicit, RefType::Ambiguous}) { auto RTStr = llvm::to_string(RT); for (auto Expected : Target.points(RTStr)) if (!llvm::is_contained(ReferencedOffsets[RT], Expected)) DiagnosePoint("location not marked used with type " + RTStr, Expected); for (auto Actual : ReferencedOffsets[RT]) if (!llvm::is_contained(Target.points(RTStr), Actual)) DiagnosePoint("location unexpectedly used with type " + RTStr, Actual); } // If there were any differences, we print the entire referencing code once. if (!DiagBuf.empty()) ADD_FAILURE() << DiagBuf << "\nfrom code:\n" << ReferencingCode; return TargetDecls; } TEST(WalkAST, DeclRef) { testWalk("int $explicit^x;", "int y = ^x;"); testWalk("int $explicit^foo();", "int y = ^foo();"); testWalk("namespace ns { int $explicit^x; }", "int y = ns::^x;"); testWalk("struct S { static int x; };", "int y = S::^x;"); // Canonical declaration only. testWalk("extern int $explicit^x; int x;", "int y = ^x;"); // Return type of `foo` isn't used. testWalk("struct S{}; S $explicit^foo();", "auto bar() { return ^foo(); }"); } TEST(WalkAST, TagType) { testWalk("struct $explicit^S {};", "^S *y;"); testWalk("enum $explicit^E {};", "^E *y;"); testWalk("struct $explicit^S { static int x; };", "int y = ^S::x;"); // One explicit call from the TypeLoc in constructor spelling, another // implicit reference through the constructor call. testWalk("struct $explicit^$implicit^S { static int x; };", "auto y = ^S();"); } TEST(WalkAST, ClassTemplates) { // Explicit instantiation and (partial) specialization references primary // template. EXPECT_THAT(testWalk("template struct $explicit^Foo{};", "template struct ^Foo;"), ElementsAre(Decl::CXXRecord)); EXPECT_THAT(testWalk("template struct $explicit^Foo{};", "template<> struct ^Foo {};"), ElementsAre(Decl::CXXRecord)); EXPECT_THAT(testWalk("template struct $explicit^Foo{};", "template struct ^Foo {};"), ElementsAre(Decl::CXXRecord)); // Implicit instantiations references most relevant template. EXPECT_THAT( testWalk("template struct $explicit^Foo;", "^Foo x();"), ElementsAre(Decl::Kind::ClassTemplate)); EXPECT_THAT( testWalk("template struct $explicit^Foo {};", "^Foo x;"), ElementsAre(Decl::CXXRecord)); EXPECT_THAT(testWalk(R"cpp( template struct Foo {}; template<> struct $explicit^Foo {};)cpp", "^Foo x;"), ElementsAre(Decl::ClassTemplateSpecialization)); EXPECT_THAT(testWalk(R"cpp( template struct Foo {}; template struct $explicit^Foo {};)cpp", "^Foo x;"), ElementsAre(Decl::ClassTemplatePartialSpecialization)); // Incomplete instantiations don't have a specific specialization associated. EXPECT_THAT(testWalk(R"cpp( template struct $explicit^Foo; template struct Foo;)cpp", "^Foo x();"), ElementsAre(Decl::Kind::ClassTemplate)); EXPECT_THAT(testWalk(R"cpp( template struct $explicit^Foo {}; template struct Foo;)cpp", "^Foo x;"), ElementsAre(Decl::CXXRecord)); // FIXME: This is broken due to // https://github.com/llvm/llvm-project/issues/42259. EXPECT_THAT(testWalk(R"cpp( template struct $explicit^Foo { Foo(T); }; template<> struct Foo { Foo(int); };)cpp", "^Foo x(3);"), ElementsAre(Decl::ClassTemplate)); } TEST(WalkAST, VarTemplates) { // Explicit instantiation and (partial) specialization references primary // template. // FIXME: Explicit instantiations has wrong source location, they point at the // primary template location (hence we drop the reference). EXPECT_THAT( testWalk("template T Foo = 0;", "template int ^Foo;"), ElementsAre()); EXPECT_THAT(testWalk("template T $explicit^Foo = 0;", "template<> int ^Foo = 2;"), ElementsAre(Decl::Var)); EXPECT_THAT(testWalk("template T $explicit^Foo = 0;", "template T* ^Foo = 1;"), ElementsAre(Decl::Var)); // Implicit instantiations references most relevant template. // FIXME: This points at implicit specialization, instead we should point to // pattern. EXPECT_THAT(testWalk(R"cpp( template T $explicit^Foo = 0;)cpp", "int z = ^Foo;"), ElementsAre(Decl::VarTemplateSpecialization)); EXPECT_THAT(testWalk(R"cpp( template T Foo = 0; template<> int $explicit^Foo = 1;)cpp", "int x = ^Foo;"), ElementsAre(Decl::VarTemplateSpecialization)); // FIXME: This points at implicit specialization, instead we should point to // explicit partial specializaiton pattern. EXPECT_THAT(testWalk(R"cpp( template T Foo = 0; template T* $explicit^Foo = nullptr;)cpp", "int *x = ^Foo;"), ElementsAre(Decl::VarTemplateSpecialization)); EXPECT_THAT(testWalk(R"cpp( template T $explicit^Foo = 0; template int Foo;)cpp", "int x = ^Foo;"), ElementsAre(Decl::VarTemplateSpecialization)); } TEST(WalkAST, FunctionTemplates) { // Explicit instantiation and (partial) specialization references primary // template. // FIXME: Explicit instantiations has wrong source location, they point at the // primary template location (hence we drop the reference). EXPECT_THAT(testWalk("template void foo(T) {}", "template void ^foo(int);"), ElementsAre()); // FIXME: Report specialized template as used from explicit specializations. EXPECT_THAT(testWalk("template void foo(T);", "template<> void ^foo(int);"), ElementsAre()); EXPECT_THAT(testWalk("template void foo(T) {}", "template void ^foo(T*) {}"), ElementsAre()); // Implicit instantiations references most relevant template. EXPECT_THAT(testWalk(R"cpp( template void $explicit^foo() {})cpp", "auto x = []{ ^foo(); };"), ElementsAre(Decl::FunctionTemplate)); // FIXME: DeclRefExpr points at primary template, not the specialization. EXPECT_THAT(testWalk(R"cpp( template void $explicit^foo() {} template<> void foo(){})cpp", "auto x = []{ ^foo(); };"), ElementsAre(Decl::FunctionTemplate)); EXPECT_THAT(testWalk(R"cpp( template void $explicit^foo() {}; template void foo();)cpp", "auto x = [] { ^foo(); };"), ElementsAre(Decl::FunctionTemplate)); } TEST(WalkAST, TemplateSpecializationsFromUsingDecl) { // Class templates testWalk(R"cpp( namespace ns { template class $ambiguous^Z {}; // primary template template class $ambiguous^Z {}; // partial specialization template<> class $ambiguous^Z {}; // full specialization } )cpp", "using ns::^Z;"); // Var templates testWalk(R"cpp( namespace ns { template T $ambiguous^foo; // primary template template T $ambiguous^foo; // partial specialization template<> int* $ambiguous^foo; // full specialization } )cpp", "using ns::^foo;"); // Function templates, no partial template specializations. testWalk(R"cpp( namespace ns { template void $ambiguous^function(T); // primary template template<> void $ambiguous^function(int); // full specialization } )cpp", "using ns::^function;"); } TEST(WalkAST, Alias) { testWalk(R"cpp( namespace ns { int x; } using ns::$explicit^x; )cpp", "int y = ^x;"); testWalk("using $explicit^foo = int;", "^foo x;"); testWalk("struct S {}; using $explicit^foo = S;", "^foo x;"); testWalk(R"cpp( template struct Foo {}; template<> struct Foo {}; namespace ns { using ::$explicit^Foo; })cpp", "ns::^Foo x;"); testWalk(R"cpp( template struct Foo {}; namespace ns { using ::Foo; } template<> struct ns::$explicit^Foo {};)cpp", "^Foo x;"); // AST doesn't have enough information to figure out whether specialization // happened through an exported type or not. So err towards attributing use to // the using-decl, specializations on the exported type should be rare and // they're not permitted on type-aliases. testWalk(R"cpp( template struct Foo {}; namespace ns { using ::$explicit^Foo; } template<> struct ns::Foo {};)cpp", "ns::^Foo x;"); testWalk(R"cpp( namespace ns { enum class foo { bar }; } using ns::foo;)cpp", "auto x = foo::^bar;"); testWalk(R"cpp( namespace ns { enum foo { bar }; } using ns::foo::$explicit^bar;)cpp", "auto x = ^bar;"); } TEST(WalkAST, Using) { // We should report unused overloads as ambiguous. testWalk(R"cpp( namespace ns { void $explicit^x(); void $ambiguous^x(int); void $ambiguous^x(char); })cpp", "using ns::^x; void foo() { x(); }"); testWalk(R"cpp( namespace ns { void $ambiguous^x(); void $ambiguous^x(int); void $ambiguous^x(char); })cpp", "using ns::^x;"); testWalk("namespace ns { struct S; } using ns::$explicit^S;", "^S *s;"); testWalk(R"cpp( namespace ns { template class $ambiguous^Y {}; })cpp", "using ns::^Y;"); testWalk(R"cpp( namespace ns { template class Y {}; } using ns::$explicit^Y;)cpp", "^Y x;"); testWalk("namespace ns { enum E {A}; } using enum ns::$explicit^E;", "auto x = ^A;"); } TEST(WalkAST, Namespaces) { testWalk("namespace ns { void x(); }", "using namespace ^ns;"); } TEST(WalkAST, TemplateNames) { testWalk("template struct $explicit^S {};", "^S s;"); // FIXME: Template decl has the wrong primary location for type-alias template // decls. testWalk(R"cpp( template struct S {}; template $explicit^using foo = S;)cpp", "^foo x;"); testWalk(R"cpp( namespace ns {template struct S {}; } using ns::$explicit^S;)cpp", "^S x;"); testWalk(R"cpp( namespace ns { template struct S { S(T);}; template S(T t) -> S; } using ns::$explicit^S;)cpp", "^S x(123);"); testWalk("template struct $explicit^S {};", R"cpp( template