diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -2,6 +2,8 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include-cleaner/include) + add_subdirectory(support) # Configure the Features.inc file. @@ -162,6 +164,7 @@ clangDriver clangFormat clangFrontend + clangIncludeCleaner clangIndex clangLex clangSema diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -88,7 +88,7 @@ bool StandardLibrary = true; } Index; - enum UnusedIncludesPolicy { Strict, None }; + enum UnusedIncludesPolicy { Strict, None, Experiment }; /// Controls warnings and errors when parsing code. struct { bool SuppressAll = false; diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -431,11 +431,13 @@ }); if (F.UnusedIncludes) - if (auto Val = compileEnum( - "UnusedIncludes", **F.UnusedIncludes) - .map("Strict", Config::UnusedIncludesPolicy::Strict) - .map("None", Config::UnusedIncludesPolicy::None) - .value()) + if (auto Val = + compileEnum("UnusedIncludes", + **F.UnusedIncludes) + .map("Strict", Config::UnusedIncludesPolicy::Strict) + .map("Experiment", Config::UnusedIncludesPolicy::Experiment) + .map("None", Config::UnusedIncludesPolicy::None) + .value()) Out.Apply.push_back([Val](const Params &, Config &C) { C.Diagnostics.UnusedIncludes = *Val; }); diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -12,6 +12,8 @@ #include "ParsedAST.h" #include "Protocol.h" #include "SourceCode.h" +#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Types.h" #include "index/CanonicalIncludes.h" #include "support/Logger.h" #include "support/Trace.h" @@ -459,20 +461,71 @@ std::vector computeUnusedIncludes(ParsedAST &AST) { const auto &SM = AST.getSourceManager(); + const auto &Includes = AST.getIncludeStructure(); + const Config &Cfg = Config::current(); + if (Cfg.Diagnostics.UnusedIncludes == + Config::UnusedIncludesPolicy::Experiment) { + llvm::DenseSet Used; + + // FIXME: this map should probably be in IncludeStructure. + llvm::StringMap> BySpelling; + for (const auto &Inc : Includes.MainFileIncludes) { + if (Inc.HeaderID) + BySpelling.try_emplace(Inc.Written) + .first->second.push_back( + static_cast(*Inc.HeaderID)); + } + // FIXME: pass the macro references. + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), /*MacroRefs=*/{}, &AST.getPragmaIncludes(), + SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + for (const auto &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (auto HeaderID = Includes.getID(H.physical())) + Used.insert(*HeaderID); + break; + case include_cleaner::Header::Standard: + for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard())) + Used.insert(HeaderID); + break; + case include_cleaner::Header::Verbatim: + for (auto HeaderID : BySpelling.lookup(H.verbatim())) + Used.insert(HeaderID); + break; + } + } + }); + std::vector Unused; + for (const auto &I : Includes.MainFileIncludes) { + // non-existing headers may not have header ID! + if (!I.HeaderID) + continue; + auto HID = static_cast(*I.HeaderID); + // FIXME: use mayConsiderUnused to respect the AnalyzeStdlib and + // IgnoreHeader options, but it also does extra IWYU-related filterings + // which we don't need. + if (!Used.contains(HID) && + !AST.getPragmaIncludes().shouldKeep(I.HashLine + 1) && + mayConsiderUnused(I, AST, Cfg)) + Unused.push_back(&I); + } + return Unused; + } auto Refs = findReferencedLocations(AST); auto ReferencedFiles = - findReferencedFiles(Refs, AST.getIncludeStructure(), - AST.getCanonicalIncludes(), AST.getSourceManager()); - auto ReferencedHeaders = - translateToHeaderIDs(ReferencedFiles, AST.getIncludeStructure(), SM); + findReferencedFiles(Refs, Includes, AST.getCanonicalIncludes(), SM); + auto ReferencedHeaders = translateToHeaderIDs(ReferencedFiles, Includes, SM); return getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas); } std::vector issueUnusedIncludesDiagnostics(ParsedAST &AST, llvm::StringRef Code) { const Config &Cfg = Config::current(); - if (Cfg.Diagnostics.UnusedIncludes != Config::UnusedIncludesPolicy::Strict || + if (Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::None || Cfg.Diagnostics.SuppressAll || Cfg.Diagnostics.Suppress.contains("unused-includes")) return {}; diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -25,6 +25,7 @@ #include "Diagnostics.h" #include "Headers.h" #include "Preamble.h" +#include "clang-include-cleaner/Record.h" #include "index/CanonicalIncludes.h" #include "support/Path.h" #include "clang/Frontend/FrontendAction.h" @@ -106,6 +107,10 @@ /// (!) does not have tokens from the preamble. const syntax::TokenBuffer &getTokens() const { return Tokens; } + const include_cleaner::PragmaIncludes &getPragmaIncludes() const { + return Pragmas; + } + /// Returns the version of the ParseInputs this AST was built from. llvm::StringRef version() const { return Version; } @@ -128,7 +133,8 @@ MainFileMacros Macros, std::vector Marks, std::vector LocalTopLevelDecls, llvm::Optional> Diags, IncludeStructure Includes, - CanonicalIncludes CanonIncludes); + CanonicalIncludes CanonIncludes, + include_cleaner::PragmaIncludes Pragmas); Path TUPath; std::string Version; @@ -160,6 +166,7 @@ std::vector LocalTopLevelDecls; IncludeStructure Includes; CanonicalIncludes CanonIncludes; + include_cleaner::PragmaIncludes Pragmas; std::unique_ptr Resolver; }; diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -23,6 +23,7 @@ #include "Preamble.h" #include "SourceCode.h" #include "TidyProvider.h" +#include "clang-include-cleaner/Record.h" #include "index/CanonicalIncludes.h" #include "index/Index.h" #include "support/Logger.h" @@ -574,9 +575,11 @@ } IncludeStructure Includes; + include_cleaner::PragmaIncludes Pragmas; // If we are using a preamble, copy existing includes. if (Preamble) { Includes = Preamble->Includes; + Pragmas = Preamble->Pragmas; Includes.MainFileIncludes = Patch->preambleIncludes(); // Replay the preamble includes so that clang-tidy checks can see them. ReplayPreamble::attach(Patch->preambleIncludes(), *Clang, @@ -586,6 +589,9 @@ // Otherwise we would collect the replayed includes again... // (We can't *just* use the replayed includes, they don't have Resolved path). Includes.collect(*Clang); + if (Config::current().Diagnostics.UnusedIncludes == + Config::UnusedIncludesPolicy::Experiment) + Pragmas.record(*Clang); // Copy over the macros in the preamble region of the main file, and combine // with non-preamble macros below. MainFileMacros Macros; @@ -671,7 +677,7 @@ std::move(Clang), std::move(Action), std::move(Tokens), std::move(Macros), std::move(Marks), std::move(ParsedDecls), std::move(Diags), std::move(Includes), - std::move(CanonIncludes)); + std::move(CanonIncludes), std::move(Pragmas)); if (Result.Diags) { auto UnusedHeadersDiags = issueUnusedIncludesDiagnostics(Result, Inputs.Contents); @@ -770,13 +776,15 @@ std::vector Marks, std::vector LocalTopLevelDecls, llvm::Optional> Diags, - IncludeStructure Includes, CanonicalIncludes CanonIncludes) + IncludeStructure Includes, CanonicalIncludes CanonIncludes, + include_cleaner::PragmaIncludes Pragmas) : TUPath(TUPath), Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Tokens(std::move(Tokens)), Macros(std::move(Macros)), Marks(std::move(Marks)), Diags(std::move(Diags)), LocalTopLevelDecls(std::move(LocalTopLevelDecls)), - Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) { + Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)), + Pragmas(std::move(Pragmas)) { Resolver = std::make_unique(getASTContext()); assert(this->Clang); assert(this->Action); diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h --- a/clang-tools-extra/clangd/Preamble.h +++ b/clang-tools-extra/clangd/Preamble.h @@ -27,6 +27,7 @@ #include "Diagnostics.h" #include "FS.h" #include "Headers.h" +#include "clang-include-cleaner/Record.h" #include "index/CanonicalIncludes.h" #include "support/Path.h" #include "clang/Frontend/CompilerInvocation.h" @@ -57,6 +58,8 @@ // Processes like code completions and go-to-definitions will need #include // information, and their compile action skips preamble range. IncludeStructure Includes; + + include_cleaner::PragmaIncludes Pragmas; // Macros defined in the preamble section of the main file. // Users care about headers vs main-file, not preamble vs non-preamble. // These should be treated as main-file entities e.g. for code completion. diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "Headers.h" #include "SourceCode.h" +#include "clang-include-cleaner/Record.h" #include "support/Logger.h" #include "support/ThreadsafeFS.h" #include "support/Trace.h" @@ -77,6 +78,9 @@ std::vector takeMarks() { return std::move(Marks); } + include_cleaner::PragmaIncludes takePragmaIncludes() { + return std::move(Pragmas); + } CanonicalIncludes takeCanonicalIncludes() { return std::move(CanonIncludes); } bool isMainFileIncludeGuarded() const { return IsMainFileIncludeGuarded; } @@ -118,6 +122,9 @@ LangOpts = &CI.getLangOpts(); SourceMgr = &CI.getSourceManager(); Includes.collect(CI); + if (Config::current().Diagnostics.UnusedIncludes == + Config::UnusedIncludesPolicy::Experiment) + Pragmas.record(CI); if (BeforeExecuteCallback) BeforeExecuteCallback(CI); } @@ -187,6 +194,7 @@ PreambleParsedCallback ParsedCallback; IncludeStructure Includes; CanonicalIncludes CanonIncludes; + include_cleaner::PragmaIncludes Pragmas; MainFileMacros Macros; std::vector Marks; bool IsMainFileIncludeGuarded = false; @@ -560,6 +568,7 @@ Result->CompileCommand = Inputs.CompileCommand; Result->Diags = std::move(Diags); Result->Includes = CapturedInfo.takeIncludes(); + Result->Pragmas = CapturedInfo.takePragmaIncludes(); Result->Macros = CapturedInfo.takeMacros(); Result->Marks = CapturedInfo.takeMarks(); Result->CanonIncludes = CapturedInfo.takeCanonicalIncludes(); 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 @@ -48,6 +48,13 @@ /// defines the symbol. class PragmaIncludes { public: + PragmaIncludes() = default; + PragmaIncludes(PragmaIncludes &&) = default; + PragmaIncludes &operator=(PragmaIncludes &&) = default; + + PragmaIncludes(const PragmaIncludes &); + PragmaIncludes &operator=(const PragmaIncludes &); + /// Installs an analysing PPCallback and CommentHandler and populates results /// to the structure. void record(const CompilerInstance &CI); 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 @@ -148,7 +148,7 @@ RecordPragma(const CompilerInstance &CI, PragmaIncludes *Out) : SM(CI.getSourceManager()), HeaderInfo(CI.getPreprocessor().getHeaderSearchInfo()), Out(Out), - UniqueStrings(Arena) {} + UniqueStrings(Out->Arena) {} void FileChanged(SourceLocation Loc, FileChangeReason Reason, SrcMgr::CharacteristicKind FileType, @@ -174,7 +174,6 @@ std::unique(It.getSecond().begin(), It.getSecond().end()), It.getSecond().end()); } - Out->Arena = std::move(Arena); } void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, @@ -287,7 +286,6 @@ const SourceManager &SM; HeaderSearch &HeaderInfo; PragmaIncludes *Out; - llvm::BumpPtrAllocator Arena; /// Intern table for strings. Contents are on the arena. llvm::StringSaver UniqueStrings; @@ -316,6 +314,27 @@ std::vector KeepStack; }; +PragmaIncludes::PragmaIncludes(const PragmaIncludes & In) { + *this = In; +} + +PragmaIncludes &PragmaIncludes::operator=(const PragmaIncludes &In) { + ShouldKeep = In.ShouldKeep; + NonSelfContainedFiles = In.NonSelfContainedFiles; + Arena.Reset(); + llvm::UniqueStringSaver Strings(Arena); + IWYUPublic.clear(); + for (const auto& It : In.IWYUPublic) + IWYUPublic.try_emplace(It.first, Strings.save(It.second)); + IWYUExportBy.clear(); + for (const auto& UIDAndExporters : In.IWYUExportBy) { + const auto& [UID, Exporters] = UIDAndExporters; + for (StringRef ExporterHeader : Exporters) + IWYUExportBy.try_emplace(UID).first->second.push_back(ExporterHeader); + } + return *this; + } + void PragmaIncludes::record(const CompilerInstance &CI) { auto Record = std::make_unique(CI, this); CI.getPreprocessor().addCommentHandler(Record.get());