hokein created this revision. hokein added reviewers: kadircet, sammccall. Herald added a project: All. hokein requested review of this revision. Herald added a project: clang-tools-extra.
This patch implements the initial version of "Location => Header" step: - define the interface; - integrate into the existing workflow, and use the PragmaIncludes; Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D137320 Files: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h clang-tools-extra/include-cleaner/lib/Analysis.cpp clang-tools-extra/include-cleaner/lib/AnalysisInternal.h clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
Index: clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp =================================================================== --- clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -7,11 +7,13 @@ //===----------------------------------------------------------------------===// #include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" #include "clang/Testing/TestAST.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/ArrayRef.h" @@ -26,41 +28,67 @@ using testing::Pair; using testing::UnorderedElementsAre; -TEST(WalkUsed, Basic) { - // FIXME: Have a fixture for setting up tests. - llvm::Annotations HeaderCode(R"cpp( - void foo(); - namespace std { class vector {}; })cpp"); +class WalkUsedTest : public ::testing::Test { +protected: + TestInputs Inputs; + PragmaIncludes PI; + + WalkUsedTest() { + 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<Hook>(&PI); + }; + } + + llvm::DenseMap</*offset*/ size_t, std::vector<Header>> walk(TestAST &AST) { + llvm::SmallVector<Decl *> TopLevelDecls; + for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) + TopLevelDecls.emplace_back(D); + auto &SM = AST.sourceManager(); + + llvm::DenseMap<size_t, std::vector<Header>> OffsetToProviders; + walkUsed( + TopLevelDecls, PI, + [&](SourceLocation RefLoc, Symbol S, llvm::ArrayRef<Header> Providers) { + auto [FID, Offset] = SM.getDecomposedLoc(RefLoc); + EXPECT_EQ(FID, SM.getMainFileID()); + OffsetToProviders.try_emplace(Offset, Providers.vec()); + }); + return OffsetToProviders; + } +}; + +TEST_F(WalkUsedTest, Basic) { llvm::Annotations Code(R"cpp( void $bar^bar() { $foo^foo(); std::$vector^vector $vconstructor^v; } )cpp"); - TestInputs Inputs(Code.code()); - Inputs.ExtraFiles["header.h"] = HeaderCode.code().str(); + Inputs.Code = Code.code(); + Inputs.ExtraFiles["header.h"] = R"cpp( + void foo(); + namespace std { class vector {}; } + )cpp"; Inputs.ExtraArgs.push_back("-include"); Inputs.ExtraArgs.push_back("header.h"); TestAST AST(Inputs); - llvm::SmallVector<Decl *> TopLevelDecls; - for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) { - TopLevelDecls.emplace_back(D); - } - - auto &SM = AST.sourceManager(); - llvm::DenseMap<size_t, std::vector<Header>> OffsetToProviders; - walkUsed(TopLevelDecls, [&](SourceLocation RefLoc, Symbol S, - llvm::ArrayRef<Header> Providers) { - auto [FID, Offset] = SM.getDecomposedLoc(RefLoc); - EXPECT_EQ(FID, SM.getMainFileID()); - OffsetToProviders.try_emplace(Offset, Providers.vec()); - }); auto HeaderFile = Header(AST.fileManager().getFile("header.h").get()); - auto MainFile = Header(SM.getFileEntryForID(SM.getMainFileID())); + auto MainFile = Header(AST.sourceManager().getFileEntryForID( + AST.sourceManager().getMainFileID())); auto VectorSTL = Header(tooling::stdlib::Header::named("<vector>").value()); EXPECT_THAT( - OffsetToProviders, + walk(AST), UnorderedElementsAre( Pair(Code.point("bar"), UnorderedElementsAre(MainFile)), Pair(Code.point("foo"), UnorderedElementsAre(HeaderFile)), @@ -68,5 +96,56 @@ Pair(Code.point("vconstructor"), UnorderedElementsAre(VectorSTL)))); } +TEST_F(WalkUsedTest, IWYUPrivate) { + llvm::Annotations Code(R"cpp( + #include "private.h" + void $bar^bar($private^Private) { + } + )cpp"); + Inputs.Code = Code.code(); + Inputs.ExtraFiles["private.h"] = R"cpp( + // IWYU pragma: private, include "path/public.h" + class Private {}; + )cpp"; + TestAST AST(Inputs); + + auto PublicH = Header("\"path/public.h\""); + auto MainFile = Header(AST.sourceManager().getFileEntryForID( + AST.sourceManager().getMainFileID())); + + EXPECT_THAT(walk(AST), + UnorderedElementsAre( + Pair(Code.point("bar"), UnorderedElementsAre(MainFile)), + Pair(Code.point("private"), UnorderedElementsAre(PublicH)))); +} + +TEST_F(WalkUsedTest, IWYUExport) { + llvm::Annotations Code(R"cpp( + #include "header1.h" + void $bar^bar($private^Private) { + } + )cpp"); + Inputs.Code = Code.code(); + Inputs.ExtraFiles["header1.h"] = R"cpp( + #include "header2.h" + )cpp"; + Inputs.ExtraFiles["header2.h"] = R"cpp( + #include "private.h" // IWYU pragma: export + )cpp"; + Inputs.ExtraFiles["private.h"] = R"cpp(class Private {};)cpp"; + TestAST AST(Inputs); + + auto Header2File = Header(AST.fileManager().getFile("header2.h").get()); + auto PrivateFile = Header(AST.fileManager().getFile("private.h").get()); + auto MainFile = Header(AST.sourceManager().getFileEntryForID( + AST.sourceManager().getMainFileID())); + + EXPECT_THAT(walk(AST), + UnorderedElementsAre( + Pair(Code.point("bar"), UnorderedElementsAre(MainFile)), + Pair(Code.point("private"), + UnorderedElementsAre(PrivateFile, Header2File)))); +} + } // namespace } // namespace clang::include_cleaner Index: clang-tools-extra/include-cleaner/lib/AnalysisInternal.h =================================================================== --- clang-tools-extra/include-cleaner/lib/AnalysisInternal.h +++ clang-tools-extra/include-cleaner/lib/AnalysisInternal.h @@ -21,6 +21,8 @@ #ifndef CLANG_INCLUDE_CLEANER_ANALYSISINTERNAL_H #define CLANG_INCLUDE_CLEANER_ANALYSISINTERNAL_H +#include "clang-include-cleaner/Record.h" +#include "clang-include-cleaner/Types.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/STLFunctionalExtras.h" @@ -42,6 +44,12 @@ /// being analyzed, in order to find all references within it. void walkAST(Decl &Root, llvm::function_ref<void(SourceLocation, NamedDecl &)>); +/// Finds the headers that provide the symbol location. +// FIXME: expose signals +llvm::SmallVector<Header> findIncludeHeaders(const SymbolLocation &Loc, + const SourceManager &SM, + const PragmaIncludes &PI); + /// Write an HTML summary of the analysis to the given stream. /// FIXME: Once analysis has a public API, this should be public too. void writeHTMLReport(FileID File, llvm::ArrayRef<Decl *> Roots, ASTContext &Ctx, Index: clang-tools-extra/include-cleaner/lib/Analysis.cpp =================================================================== --- clang-tools-extra/include-cleaner/lib/Analysis.cpp +++ clang-tools-extra/include-cleaner/lib/Analysis.cpp @@ -7,28 +7,19 @@ //===----------------------------------------------------------------------===// #include "clang-include-cleaner/Analysis.h" -#include "clang-include-cleaner/Types.h" #include "AnalysisInternal.h" +#include "clang-include-cleaner/Record.h" +#include "clang-include-cleaner/Types.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/SourceManager.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" namespace clang::include_cleaner { -namespace { -llvm::SmallVector<Header> -toHeader(llvm::ArrayRef<tooling::stdlib::Header> Headers) { - llvm::SmallVector<Header> Result; - llvm::for_each(Headers, [&](tooling::stdlib::Header H) { - Result.emplace_back(Header(H)); - }); - return Result; -} -} // namespace -void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, UsedSymbolCB CB) { +void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, const PragmaIncludes &PI, + UsedSymbolCB CB) { tooling::stdlib::Recognizer Recognizer; for (auto *Root : ASTRoots) { auto &SM = Root->getASTContext().getSourceManager(); @@ -36,16 +27,45 @@ if (auto SS = Recognizer(&ND)) { // FIXME: Also report forward decls from main-file, so that the caller // can decide to insert/ignore a header. - return CB(Loc, Symbol(*SS), toHeader(SS->headers())); + return CB(Loc, Symbol(*SS), findIncludeHeaders({*SS}, SM, PI)); } // FIXME: Extract locations from redecls. - // FIXME: Handle IWYU pragmas, non self-contained files. // FIXME: Handle macro locations. - if (auto *FE = SM.getFileEntryForID(SM.getFileID(ND.getLocation()))) - return CB(Loc, Symbol(ND), {Header(FE)}); + return CB(Loc, Symbol(ND), + findIncludeHeaders({ND.getLocation()}, SM, PI)); }); } // FIXME: Handle references of macros. } +llvm::SmallVector<Header> findIncludeHeaders(const SymbolLocation &SLoc, + const SourceManager &SM, + const PragmaIncludes &PI) { + llvm::SmallVector<Header> Results; + if (SLoc.index() == 0) { + // FIXME: Handle non self-contained files. + SourceLocation Loc = std::get<SourceLocation>(SLoc); + FileID FID = SM.getFileID(Loc); + const auto *FE = SM.getFileEntryForID(FID); + if (!FE) + return {}; + + llvm::StringRef VerbatimSpelling = PI.getPublic(FE); + if (!VerbatimSpelling.empty()) + return {{VerbatimSpelling}}; + + Results = {{FE}}; + // FIXME: compute a closure of exporter headers. + for (const auto *Export : PI.getExporters(FE, SM.getFileManager())) + Results.push_back(Export); + return Results; + } + if (SLoc.index() == 1) { + for (const auto &H : std::get<tooling::stdlib::Symbol>(SLoc).headers()) + Results.push_back(H); + return Results; + } + return Results; +} + } // namespace clang::include_cleaner Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h =================================================================== --- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h +++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Types.h @@ -22,7 +22,10 @@ #ifndef CLANG_INCLUDE_CLEANER_TYPES_H #define CLANG_INCLUDE_CLEANER_TYPES_H +#include "clang/Basic/FileEntry.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/ADT/StringRef.h" #include <memory> #include <vector> @@ -48,14 +51,17 @@ Header(const FileEntry *FE) : Storage(FE) {} /// A logical file representing a stdlib header. Header(tooling::stdlib::Header H) : Storage(H) {} + Header(llvm::StringRef VerbatimSpelling) : Storage(VerbatimSpelling.str()) {} bool operator==(const Header &RHS) const { return Storage == RHS.Storage; } private: - // FIXME: Handle verbatim spellings. - std::variant<const FileEntry *, tooling::stdlib::Header> Storage; + std::variant<const FileEntry *, tooling::stdlib::Header, std::string> Storage; }; +// FIXME: use a real Class! +using SymbolLocation = std::variant<SourceLocation, tooling::stdlib::Symbol>; + } // namespace include_cleaner } // namespace clang Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h =================================================================== --- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h +++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h @@ -11,6 +11,7 @@ #ifndef CLANG_INCLUDE_CLEANER_ANALYSIS_H #define CLANG_INCLUDE_CLEANER_ANALYSIS_H +#include "clang-include-cleaner/Record.h" #include "clang-include-cleaner/Types.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLFunctionalExtras.h" @@ -42,9 +43,8 @@ /// headers which don't match any #include in the main file /// - to diagnose unused includes: an #include in the main file does not match /// the headers for any referenced symbol -/// FIXME: Take in an include structure to improve location to header mappings -/// (e.g. IWYU pragmas). -void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, UsedSymbolCB CB); +void walkUsed(llvm::ArrayRef<Decl *> ASTRoots, const PragmaIncludes &PI, + UsedSymbolCB CB); } // namespace include_cleaner } // namespace clang
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits