//===--- FindHeadersTest.cpp ----------------------------------------------===// // // 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 "TypesInternal.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "clang/AST/Expr.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/FileEntry.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Testing/TestAST.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace clang::include_cleaner { namespace { using testing::ElementsAre; using testing::UnorderedElementsAre; std::string guard(llvm::StringRef Code) { return "#pragma once\n" + Code.str(); } class FindHeadersTest : public testing::Test { protected: TestInputs Inputs; PragmaIncludes PI; std::unique_ptr AST; FindHeadersTest() { Inputs.MakeAction = [this] { struct Hook : public SyntaxOnlyAction { public: Hook(PragmaIncludes *Out) : Out(Out) {} bool BeginSourceFileAction(clang::CompilerInstance &CI) override { Out->record(CI); return true; } PragmaIncludes *Out; }; return std::make_unique(&PI); }; } void buildAST() { AST = std::make_unique(Inputs); } llvm::SmallVector> findHeaders(llvm::StringRef FileName) { return include_cleaner::findHeaders( AST->sourceManager().translateFileLineCol( AST->fileManager().getFile(FileName).get(), /*Line=*/1, /*Col=*/1), AST->sourceManager(), &PI); } FileEntryRef physicalHeader(llvm::StringRef FileName) { return *AST->fileManager().getOptionalFileRef(FileName); }; }; TEST_F(FindHeadersTest, IWYUPrivateToPublic) { Inputs.Code = R"cpp( #include "private.h" )cpp"; Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private, include "path/public.h" )cpp"); buildAST(); EXPECT_THAT(findHeaders("private.h"), UnorderedElementsAre(physicalHeader("private.h"), Header("\"path/public.h\""))); } TEST_F(FindHeadersTest, IWYUExport) { Inputs.Code = R"cpp( #include "exporter.h" )cpp"; Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include "exported1.h" // IWYU pragma: export // IWYU pragma: begin_exports #include "exported2.h" // IWYU pragma: end_exports #include "normal.h" )cpp"); Inputs.ExtraFiles["exported1.h"] = guard(""); Inputs.ExtraFiles["exported2.h"] = guard(""); Inputs.ExtraFiles["normal.h"] = guard(""); buildAST(); EXPECT_THAT(findHeaders("exported1.h"), UnorderedElementsAre(physicalHeader("exported1.h"), physicalHeader("exporter.h"))); EXPECT_THAT(findHeaders("exported2.h"), UnorderedElementsAre(physicalHeader("exported2.h"), physicalHeader("exporter.h"))); EXPECT_THAT(findHeaders("normal.h"), UnorderedElementsAre(physicalHeader("normal.h"))); EXPECT_THAT(findHeaders("exporter.h"), UnorderedElementsAre(physicalHeader("exporter.h"))); } TEST_F(FindHeadersTest, IWYUExportForStandardHeaders) { Inputs.Code = R"cpp( #include "exporter.h" )cpp"; Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include // IWYU pragma: export )cpp"); Inputs.ExtraFiles["string"] = guard(""); Inputs.ExtraArgs.push_back("-isystem."); buildAST(); tooling::stdlib::Symbol StdString = *tooling::stdlib::Symbol::named("std::", "string"); EXPECT_THAT( include_cleaner::findHeaders(StdString, AST->sourceManager(), &PI), UnorderedElementsAre(physicalHeader("exporter.h"), StdString.header())); } TEST_F(FindHeadersTest, SelfContained) { Inputs.Code = R"cpp( #include "header.h" )cpp"; Inputs.ExtraFiles["header.h"] = guard(R"cpp( #include "fragment.inc" )cpp"); Inputs.ExtraFiles["fragment.inc"] = ""; buildAST(); EXPECT_THAT(findHeaders("fragment.inc"), UnorderedElementsAre(physicalHeader("fragment.inc"), physicalHeader("header.h"))); } TEST_F(FindHeadersTest, NonSelfContainedTraversePrivate) { Inputs.Code = R"cpp( #include "header.h" )cpp"; Inputs.ExtraFiles["header.h"] = guard(R"cpp( #include "fragment.inc" )cpp"); Inputs.ExtraFiles["fragment.inc"] = R"cpp( // IWYU pragma: private, include "public.h" )cpp"; buildAST(); // There is a IWYU private mapping in the non self-contained header, verify // that we don't emit its includer. EXPECT_THAT(findHeaders("fragment.inc"), UnorderedElementsAre(physicalHeader("fragment.inc"), Header("\"public.h\""))); } TEST_F(FindHeadersTest, NonSelfContainedTraverseExporter) { Inputs.Code = R"cpp( #include "exporter.h" )cpp"; Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include "exported.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["exported.h"] = guard(R"cpp( #include "fragment.inc" )cpp"); Inputs.ExtraFiles["fragment.inc"] = ""; buildAST(); // Verify that we emit exporters for each header on the path. EXPECT_THAT(findHeaders("fragment.inc"), UnorderedElementsAre(physicalHeader("fragment.inc"), physicalHeader("exported.h"), physicalHeader("exporter.h"))); } TEST_F(FindHeadersTest, TargetIsExpandedFromMacroInHeader) { struct CustomVisitor : RecursiveASTVisitor { const Decl *Out = nullptr; bool VisitNamedDecl(const NamedDecl *ND) { if (ND->getName() == "FLAG_foo" || ND->getName() == "Foo") { EXPECT_TRUE(Out == nullptr); Out = ND; } return true; } }; struct { llvm::StringRef MacroHeader; llvm::StringRef DeclareHeader; } TestCases[] = { {/*MacroHeader=*/R"cpp( #define DEFINE_CLASS(name) class name {}; )cpp", /*DeclareHeader=*/R"cpp( #include "macro.h" DEFINE_CLASS(Foo) )cpp"}, {/*MacroHeader=*/R"cpp( #define DEFINE_Foo class Foo {}; )cpp", /*DeclareHeader=*/R"cpp( #include "macro.h" DEFINE_Foo )cpp"}, {/*MacroHeader=*/R"cpp( #define DECLARE_FLAGS(name) extern int FLAG_##name )cpp", /*DeclareHeader=*/R"cpp( #include "macro.h" DECLARE_FLAGS(foo); )cpp"}, }; for (const auto &T : TestCases) { Inputs.Code = R"cpp(#include "declare.h")cpp"; Inputs.ExtraFiles["declare.h"] = guard(T.DeclareHeader); Inputs.ExtraFiles["macro.h"] = guard(T.MacroHeader); buildAST(); CustomVisitor Visitor; Visitor.TraverseDecl(AST->context().getTranslationUnitDecl()); auto Headers = clang::include_cleaner::findHeaders( Visitor.Out->getLocation(), AST->sourceManager(), /*PragmaIncludes=*/nullptr); EXPECT_THAT(Headers, UnorderedElementsAre(physicalHeader("declare.h"))); } } MATCHER_P2(HintedHeader, Header, Hint, "") { return std::tie(arg.Hint, arg) == std::tie(Hint, Header); } TEST_F(FindHeadersTest, PublicHeaderHint) { Inputs.Code = R"cpp( #include "public.h" )cpp"; Inputs.ExtraFiles["public.h"] = guard(R"cpp( #include "private.h" #include "private.inc" )cpp"); Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private )cpp"); Inputs.ExtraFiles["private.inc"] = ""; buildAST(); // Non self-contained files and headers marked with IWYU private pragma // shouldn't have PublicHeader hint. EXPECT_THAT( findHeaders("private.inc"), UnorderedElementsAre( HintedHeader(physicalHeader("private.inc"), Hints::OriginHeader), HintedHeader(physicalHeader("public.h"), Hints::PublicHeader))); EXPECT_THAT(findHeaders("private.h"), UnorderedElementsAre(HintedHeader(physicalHeader("private.h"), Hints::OriginHeader))); } TEST_F(FindHeadersTest, PreferredHeaderHint) { Inputs.Code = R"cpp( #include "private.h" )cpp"; Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private, include "public.h" )cpp"); buildAST(); // Headers explicitly marked should've preferred signal. EXPECT_THAT( findHeaders("private.h"), UnorderedElementsAre( HintedHeader(physicalHeader("private.h"), Hints::OriginHeader), HintedHeader(Header("\"public.h\""), Hints::PreferredHeader | Hints::PublicHeader))); } class HeadersForSymbolTest : public FindHeadersTest { protected: llvm::SmallVector
headersFor(llvm::StringRef Name) { struct Visitor : public RecursiveASTVisitor { const NamedDecl *Out = nullptr; llvm::StringRef Name; Visitor(llvm::StringRef Name) : Name(Name) {} bool VisitNamedDecl(const NamedDecl *ND) { if (auto *TD = ND->getDescribedTemplate()) ND = TD; if (ND->getName() == Name) { EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl()) << "Found multiple matches for " << Name << "."; Out = cast(ND->getCanonicalDecl()); } return true; } }; Visitor V(Name); V.TraverseDecl(AST->context().getTranslationUnitDecl()); if (!V.Out) ADD_FAILURE() << "Couldn't find any decls named " << Name << "."; assert(V.Out); return headersForSymbol(*V.Out, AST->sourceManager(), &PI); } llvm::SmallVector
headersForFoo() { return headersFor("foo"); } }; TEST_F(HeadersForSymbolTest, Deduplicates) { Inputs.Code = R"cpp( #include "foo.h" )cpp"; Inputs.ExtraFiles["foo.h"] = guard(R"cpp( // IWYU pragma: private, include "foo.h" void foo(); void foo(); )cpp"); buildAST(); EXPECT_THAT( headersForFoo(), UnorderedElementsAre(physicalHeader("foo.h"), // FIXME: de-duplicate across different kinds. Header("\"foo.h\""))); } TEST_F(HeadersForSymbolTest, RankByName) { Inputs.Code = R"cpp( #include "fox.h" #include "bar.h" )cpp"; Inputs.ExtraFiles["fox.h"] = guard(R"cpp( void foo(); )cpp"); Inputs.ExtraFiles["bar.h"] = guard(R"cpp( void foo(); )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("bar.h"), physicalHeader("fox.h"))); } TEST_F(HeadersForSymbolTest, Ranking) { // Sorting is done over (public, complete, canonical, origin)-tuple. Inputs.Code = R"cpp( #include "private.h" #include "public.h" #include "public_complete.h" #include "exporter.h" )cpp"; Inputs.ExtraFiles["public.h"] = guard(R"cpp( struct foo; )cpp"); Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private, include "canonical.h" struct foo; )cpp"); Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include "private.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["public_complete.h"] = guard("struct foo {};"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public_complete.h"), Header("\"canonical.h\""), physicalHeader("public.h"), physicalHeader("exporter.h"), physicalHeader("private.h"))); } TEST_F(HeadersForSymbolTest, PreferPublicOverComplete) { Inputs.Code = R"cpp( #include "complete_private.h" #include "public.h" )cpp"; Inputs.ExtraFiles["complete_private.h"] = guard(R"cpp( // IWYU pragma: private struct foo {}; )cpp"); Inputs.ExtraFiles["public.h"] = guard("struct foo;"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public.h"), physicalHeader("complete_private.h"))); } TEST_F(HeadersForSymbolTest, PreferNameMatch) { Inputs.Code = R"cpp( #include "public_complete.h" #include "test/foo.fwd.h" )cpp"; Inputs.ExtraFiles["public_complete.h"] = guard("struct foo {};"); Inputs.ExtraFiles["test/foo.fwd.h"] = guard("struct foo;"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("public_complete.h"), physicalHeader("test/foo.fwd.h"))); } TEST_F(HeadersForSymbolTest, MainFile) { Inputs.Code = R"cpp( #include "public_complete.h" struct foo; )cpp"; Inputs.ExtraFiles["public_complete.h"] = guard(R"cpp( struct foo {}; )cpp"); buildAST(); auto &SM = AST->sourceManager(); // FIXME: Symbols provided by main file should be treated specially. EXPECT_THAT( headersForFoo(), ElementsAre(physicalHeader("public_complete.h"), Header(*SM.getFileEntryRefForID(SM.getMainFileID())))); } TEST_F(HeadersForSymbolTest, PreferExporterOfPrivate) { Inputs.Code = R"cpp( #include "private.h" #include "exporter.h" )cpp"; Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private struct foo {}; )cpp"); Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include "private.h" // IWYU pragma: export )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h"), physicalHeader("private.h"))); } TEST_F(HeadersForSymbolTest, ExporterIsDownRanked) { Inputs.Code = R"cpp( #include "exporter.h" #include "zoo.h" )cpp"; // Deliberately named as zoo to make sure it doesn't get name-match boost and // also gets lexicographically bigger order than "exporter". Inputs.ExtraFiles["zoo.h"] = guard(R"cpp( struct foo {}; )cpp"); Inputs.ExtraFiles["exporter.h"] = guard(R"cpp( #include "zoo.h" // IWYU pragma: export )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h"), physicalHeader("exporter.h"))); } TEST_F(HeadersForSymbolTest, PreferPublicOverNameMatchOnPrivate) { Inputs.Code = R"cpp( #include "foo.h" )cpp"; Inputs.ExtraFiles["foo.h"] = guard(R"cpp( // IWYU pragma: private, include "public.h" struct foo {}; )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"")), physicalHeader("foo.h"))); } TEST_F(HeadersForSymbolTest, PublicOverPrivateWithoutUmbrella) { Inputs.Code = R"cpp( #include "bar.h" #include "foo.h" )cpp"; Inputs.ExtraFiles["bar.h"] = guard(R"cpp(#include "foo.h" // IWYU pragma: export)cpp"); Inputs.ExtraFiles["foo.h"] = guard(R"cpp( // IWYU pragma: private struct foo {}; )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("bar.h"), physicalHeader("foo.h"))); } TEST_F(HeadersForSymbolTest, IWYUTransitiveExport) { Inputs.Code = R"cpp( #include "export1.h" )cpp"; Inputs.ExtraFiles["export1.h"] = guard(R"cpp( #include "export2.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["export2.h"] = guard(R"cpp( #include "foo.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["foo.h"] = guard(R"cpp( struct foo {}; )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("foo.h"), physicalHeader("export1.h"), physicalHeader("export2.h"))); } TEST_F(HeadersForSymbolTest, IWYUTransitiveExportWithPrivate) { Inputs.Code = R"cpp( #include "export1.h" void bar() { foo();} )cpp"; Inputs.ExtraFiles["export1.h"] = guard(R"cpp( // IWYU pragma: private, include "public1.h" #include "export2.h" // IWYU pragma: export void foo(); )cpp"); Inputs.ExtraFiles["export2.h"] = guard(R"cpp( // IWYU pragma: private, include "public2.h" #include "export3.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["export3.h"] = guard(R"cpp( // IWYU pragma: private, include "public3.h" #include "foo.h" // IWYU pragma: export )cpp"); Inputs.ExtraFiles["foo.h"] = guard(R"cpp( void foo(); )cpp"); buildAST(); EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("foo.h"), Header(StringRef("\"public1.h\"")), physicalHeader("export1.h"), physicalHeader("export2.h"), physicalHeader("export3.h"))); } TEST_F(HeadersForSymbolTest, AmbiguousStdSymbols) { struct { llvm::StringRef Code; llvm::StringRef Name; llvm::StringRef ExpectedHeader; } TestCases[] = { { R"cpp( namespace std { template constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest); })cpp", "move", "", }, { R"cpp( namespace std { template ForwardIt2 move(ExecutionPolicy&& policy, ForwardIt1 first, ForwardIt1 last, ForwardIt2 d_first); })cpp", "move", "", }, { R"cpp( namespace std { template constexpr T move(T&& t) noexcept; })cpp", "move", "", }, { R"cpp( namespace std { template ForwardIt remove(ForwardIt first, ForwardIt last, const T& value); })cpp", "remove", "", }, { "namespace std { int remove(const char*); }", "remove", "", }, }; for (const auto &T : TestCases) { Inputs.Code = T.Code; buildAST(); EXPECT_THAT(headersFor(T.Name), UnorderedElementsAre( Header(*tooling::stdlib::Header::named(T.ExpectedHeader)))); } } TEST_F(HeadersForSymbolTest, AmbiguousStdSymbolsUsingShadow) { Inputs.Code = R"cpp( void remove(char*); namespace std { using ::remove; } void k() { std::remove("abc"); } )cpp"; buildAST(); // Find the DeclRefExpr in the std::remove("abc") function call. struct Visitor : public RecursiveASTVisitor { const DeclRefExpr *Out = nullptr; bool VisitDeclRefExpr(const DeclRefExpr *DRE) { EXPECT_TRUE(Out == nullptr) << "Found multiple DeclRefExpr!"; Out = DRE; return true; } }; Visitor V; V.TraverseDecl(AST->context().getTranslationUnitDecl()); ASSERT_TRUE(V.Out) << "Couldn't find a DeclRefExpr!"; EXPECT_THAT(headersForSymbol(*(V.Out->getFoundDecl()), AST->sourceManager(), &PI), UnorderedElementsAre( Header(*tooling::stdlib::Header::named("")))); } TEST_F(HeadersForSymbolTest, StandardHeaders) { Inputs.Code = "void assert();"; buildAST(); EXPECT_THAT( headersFor("assert"), // Respect the ordering from the stdlib mapping. UnorderedElementsAre(tooling::stdlib::Header::named(""), tooling::stdlib::Header::named(""))); } } // namespace } // namespace clang::include_cleaner