diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h --- a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Record.h @@ -70,6 +70,9 @@ llvm::SmallVector getExporters(const FileEntry *File, FileManager &FM) const; + /// Returns true if the given file is a self-contained file. + bool isSelfContained(const FileEntry *File) const; + private: class RecordPragma; /// 1-based Line numbers for the #include directives of the main file that @@ -94,11 +97,13 @@ llvm::SmallVector> IWYUExportBy; + /// Contains all non self-contained files during the parsing. + llvm::DenseSet NonSelfContainedFiles; + /// Owns the strings. llvm::BumpPtrAllocator Arena; // FIXME: add support for clang use_instead pragma - // FIXME: add selfcontained file. }; /// Recorded main-file parser events relevant to include-cleaner. diff --git a/clang-tools-extra/include-cleaner/lib/CMakeLists.txt b/clang-tools-extra/include-cleaner/lib/CMakeLists.txt --- a/clang-tools-extra/include-cleaner/lib/CMakeLists.txt +++ b/clang-tools-extra/include-cleaner/lib/CMakeLists.txt @@ -13,6 +13,7 @@ clangAST clangBasic clangLex + clangToolingInclusions clangToolingInclusionsStdlib ) diff --git a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp --- a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp +++ b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp @@ -12,6 +12,21 @@ namespace clang::include_cleaner { +// Find a responsible header for the given file FID. If the file is non +// self-contained, we find its first includer that is self-contained by walking +// up the include stack. +static const FileEntry *getResponsibleHeader(FileID FID, + const SourceManager &SM, + const PragmaIncludes &PI) { + const FileEntry *FE = SM.getFileEntryForID(FID); + + while (FE && FID != SM.getMainFileID() && !PI.isSelfContained(FE)) { + FID = SM.getFileID(SM.getIncludeLoc(FID)); + FE = SM.getFileEntryForID(FID); + } + return FE; +} + llvm::SmallVector
findHeaders(const SymbolLocation &Loc, const SourceManager &SM, const PragmaIncludes &PI) { @@ -21,7 +36,7 @@ // FIXME: Handle macro locations. // FIXME: Handle non self-contained files. FileID FID = SM.getFileID(Loc.physical()); - const auto *FE = SM.getFileEntryForID(FID); + const auto *FE = getResponsibleHeader(FID, SM, PI); if (!FE) return {}; diff --git a/clang-tools-extra/include-cleaner/lib/Record.cpp b/clang-tools-extra/include-cleaner/lib/Record.cpp --- a/clang-tools-extra/include-cleaner/lib/Record.cpp +++ b/clang-tools-extra/include-cleaner/lib/Record.cpp @@ -16,6 +16,8 @@ #include "clang/Lex/MacroInfo.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/Inclusions/HeaderAnalysis.h" + namespace clang::include_cleaner { namespace { @@ -118,12 +120,25 @@ class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler { public: RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out) - : SM(CI.getSourceManager()), Out(Out), UniqueStrings(Arena) {} + : SM(CI.getSourceManager()), + HeaderInfo(CI.getPreprocessor().getHeaderSearchInfo()), Out(Out), + UniqueStrings(Arena) {} void FileChanged(SourceLocation Loc, FileChangeReason Reason, SrcMgr::CharacteristicKind FileType, FileID PrevFID) override { InMainFile = SM.isWrittenInMainFile(Loc); + + if (Reason == PPCallbacks::ExitFile) { + // At file exit time HeaderSearchInfo is valid and can be used to + // determine whether the file was a self-contained header or not. + if (const FileEntry *FE = SM.getFileEntryForID(PrevFID)) { + if (tooling::isSelfContainedHeader(FE, SM, HeaderInfo)) + Out->NonSelfContainedFiles.erase(FE->getUniqueID()); + else + Out->NonSelfContainedFiles.insert(FE->getUniqueID()); + } + } } void EndOfMainFile() override { @@ -238,6 +253,7 @@ bool InMainFile = false; const SourceManager &SM; + HeaderSearch &HeaderInfo; PragmaIncludes *Out; llvm::BumpPtrAllocator Arena; /// Intern table for strings. Contents are on the arena. @@ -287,6 +303,10 @@ return Results; } +bool PragmaIncludes::isSelfContained(const FileEntry *ID) const { + return !NonSelfContainedFiles.contains(ID->getUniqueID()); +} + std::unique_ptr RecordedAST::record() { class Recorder : public ASTConsumer { RecordedAST *Out; diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -28,6 +28,10 @@ using testing::Pair; using testing::UnorderedElementsAre; +std::string guard(const llvm::StringRef Code) { + return "#pragma once\n" + Code.str(); +} + TEST(WalkUsed, Basic) { // FIXME: Have a fixture for setting up tests. llvm::Annotations Code(R"cpp( @@ -40,14 +44,14 @@ } )cpp"); TestInputs Inputs(Code.code()); - Inputs.ExtraFiles["header.h"] = R"cpp( + Inputs.ExtraFiles["header.h"] = guard(R"cpp( void foo(); namespace std { class vector {}; } - )cpp"; - Inputs.ExtraFiles["private.h"] = R"cpp( + )cpp"); + Inputs.ExtraFiles["private.h"] = guard(R"cpp( // IWYU pragma: private, include "path/public.h" class Private {}; - )cpp"; + )cpp"); PragmaIncludes PI; Inputs.MakeAction = [&PI] { diff --git a/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp b/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp --- a/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp @@ -17,6 +17,10 @@ namespace { using testing::UnorderedElementsAre; +std::string guard(const llvm::StringRef Code) { + return "#pragma once\n" + Code.str(); +} + TEST(FindIncludeHeaders, IWYU) { TestInputs Inputs; PragmaIncludes PI; @@ -38,20 +42,24 @@ #include "header1.h" #include "header2.h" )cpp"; - Inputs.ExtraFiles["header1.h"] = R"cpp( + Inputs.ExtraFiles["header1.h"] = guard(R"cpp( // IWYU pragma: private, include "path/public.h" - )cpp"; - Inputs.ExtraFiles["header2.h"] = R"cpp( + )cpp"); + Inputs.ExtraFiles["header2.h"] = guard(R"cpp( #include "detail1.h" // IWYU pragma: export // IWYU pragma: begin_exports #include "detail2.h" // IWYU pragma: end_exports + #include "imp.inc" + #include "normal.h" - )cpp"; + + )cpp"); Inputs.ExtraFiles["normal.h"] = Inputs.ExtraFiles["detail1.h"] = - Inputs.ExtraFiles["detail2.h"] = ""; + Inputs.ExtraFiles["detail2.h"] = guard(""); + Inputs.ExtraFiles["imp.inc"] = ""; TestAST AST(Inputs); const auto &SM = AST.sourceManager(); auto &FM = SM.getFileManager(); @@ -70,6 +78,9 @@ EXPECT_THAT(findHeaders(SourceLocFromFile("detail2.h"), SM, PI), UnorderedElementsAre(Header(FM.getFile("header2.h").get()), Header(FM.getFile("detail2.h").get()))); + // header2.h is the first #includer for the non self-contained header imp.inc + EXPECT_THAT(findHeaders(SourceLocFromFile("imp.inc"), SM, PI), + UnorderedElementsAre(Header(FM.getFile("header2.h").get()))); EXPECT_THAT(findHeaders(SourceLocFromFile("normal.h"), SM, PI), UnorderedElementsAre(Header(FM.getFile("normal.h").get())));