diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Hooks.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Hooks.h --- a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Hooks.h +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Hooks.h @@ -17,6 +17,8 @@ #ifndef CLANG_INCLUDE_CLEANER_HOOKS_H #define CLANG_INCLUDE_CLEANER_HOOKS_H +#include "clang/Basic/FileEntry.h" +#include "llvm/ADT/DenseSet.h" #include #include @@ -24,8 +26,40 @@ class ASTConsumer; class ASTContext; class Decl; +class CompilerInstance; + namespace include_cleaner { +// Captures #include mapping information. It analyses IWYU Pragma comments and +// other use_instead mechanisms (clang use_instead pragma) on included files. +// +// Used in the "Location => Header" analysis step to determine the final +// spelling header rather than the header directly defines the symbol. +class PragmaIncludes { +public: + // Installs an analysing PPCallback and CommentHandler and populates results + // to the structure. + void record(const CompilerInstance &CI); + + bool shouldKeep(FileEntryRef File) const; + // Returns the mapping include for the given physical header file. + // Returns "" if there is no mapping. + llvm::StringRef getMapping(FileEntryRef File) const; + + class RecordPragma; + +private: + // Main file headers that should be kept, decorated by "IWYU pragma: keep". + llvm::DenseSet ShouldKeep; // FIXME: implement + + // Header mapping by IWYU private pragma. + llvm::DenseMap + IWYUPrivate; + + // FIXME: add other IWYU supports (export etc) + // FIXME: add support for clang use_instead progma +}; + // Contains recorded parser events relevant to include-cleaner. struct RecordedAST { // The consumer (when installed into clang) tracks declarations in this. diff --git a/clang-tools-extra/include-cleaner/lib/Hooks.cpp b/clang-tools-extra/include-cleaner/lib/Hooks.cpp --- a/clang-tools-extra/include-cleaner/lib/Hooks.cpp +++ b/clang-tools-extra/include-cleaner/lib/Hooks.cpp @@ -11,9 +11,73 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/DeclGroup.h" #include "clang/Basic/SourceManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include +#include namespace clang::include_cleaner { +// FIXME: this is a mirror of clang::clangd::parseIWYUPragma, share the code? +static llvm::Optional parseIWYUPragma(const char *Text) { + constexpr llvm::StringLiteral IWYUPragma = "// IWYU pragma: "; + if (strncmp(Text, IWYUPragma.data(), IWYUPragma.size())) + return llvm::None; + Text += IWYUPragma.size(); + const char *End = Text; + while (*End != 0 && *End != '\n') + ++End; + return StringRef(Text, End - Text); +} + +class PragmaIncludes::RecordPragma : public PPCallbacks, public CommentHandler { +public: + RecordPragma(PragmaIncludes *Out) : Out(Out) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + bool Invalid = false; + auto Pragma = parseIWYUPragma( + PP.getSourceManager().getCharacterData(Range.getBegin(), &Invalid)); + if (!Pragma || Invalid) + return false; + + if (Pragma->consume_front("private, include ")) { + auto &SM = PP.getSourceManager(); + // We always insert using the spelling from the pragma. + if (auto *FE = SM.getFileEntryForID(SM.getFileID(Range.getBegin()))) + Out->IWYUPrivate.insert( + {FE->getLastRef().getUniqueID(), + Pragma->startswith("<") || Pragma->startswith("\"") + ? Pragma->str() + : ("\"" + *Pragma + "\"").str()}); + return false; + } + return false; + } + +private: + PragmaIncludes *Out; +}; + +void PragmaIncludes::record(const CompilerInstance &CI) { + auto Record = std::make_unique(this); + CI.getPreprocessor().addCommentHandler(Record.get()); + CI.getPreprocessor().addPPCallbacks(std::move(Record)); +} + +bool PragmaIncludes::shouldKeep(FileEntryRef F) const { + auto It = ShouldKeep.find(F.getUniqueID()); + return It != ShouldKeep.end(); +} + +llvm::StringRef PragmaIncludes::getMapping(FileEntryRef F) const { + auto It = IWYUPrivate.find(F.getUniqueID()); + if (It == IWYUPrivate.end()) + return ""; + return It->getSecond(); +} + std::unique_ptr RecordedAST::record() { class Recorder : public ASTConsumer { RecordedAST *Out;