diff --git a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h --- a/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h +++ b/clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h @@ -45,7 +45,7 @@ /// the headers for any referenced symbol void walkUsed(llvm::ArrayRef ASTRoots, llvm::ArrayRef MacroRefs, - const PragmaIncludes &PI, const SourceManager &, UsedSymbolCB CB); + const PragmaIncludes *PI, const SourceManager &, UsedSymbolCB CB); } // namespace include_cleaner } // namespace clang diff --git a/clang-tools-extra/include-cleaner/lib/Analysis.cpp b/clang-tools-extra/include-cleaner/lib/Analysis.cpp --- a/clang-tools-extra/include-cleaner/lib/Analysis.cpp +++ b/clang-tools-extra/include-cleaner/lib/Analysis.cpp @@ -19,8 +19,9 @@ void walkUsed(llvm::ArrayRef ASTRoots, llvm::ArrayRef MacroRefs, - const PragmaIncludes &PI, const SourceManager &SM, + const PragmaIncludes *PI, const SourceManager &SM, UsedSymbolCB CB) { + // This is duplicated in writeHTMLReport, changes should be mirrored there. tooling::stdlib::Recognizer Recognizer; for (auto *Root : ASTRoots) { auto &SM = Root->getASTContext().getSourceManager(); diff --git a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h --- a/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h +++ b/clang-tools-extra/include-cleaner/lib/AnalysisInternal.h @@ -75,18 +75,18 @@ // Order must match Kind enum! std::variant Storage; }; -llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Header &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); /// Finds the headers that provide the symbol location. // FIXME: expose signals llvm::SmallVector
findHeaders(const SymbolLocation &Loc, const SourceManager &SM, - const PragmaIncludes &PI); + 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 Roots, ASTContext &Ctx, - llvm::raw_ostream &OS); +void writeHTMLReport(FileID File, llvm::ArrayRef Roots, + llvm::ArrayRef MacroRefs, ASTContext &Ctx, + PragmaIncludes *PI, llvm::raw_ostream &OS); } // namespace include_cleaner } // namespace clang 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 @@ -14,7 +14,7 @@ llvm::SmallVector
findHeaders(const SymbolLocation &Loc, const SourceManager &SM, - const PragmaIncludes &PI) { + const PragmaIncludes *PI) { llvm::SmallVector
Results; switch (Loc.kind()) { case SymbolLocation::Physical: { @@ -25,18 +25,21 @@ if (!FE) return {}; - // We treat the spelling header in the IWYU pragma as the final public - // header. - // FIXME: look for exporters if the public header is exported by another - // header. - llvm::StringRef VerbatimSpelling = PI.getPublic(FE); - if (!VerbatimSpelling.empty()) - return {Header(VerbatimSpelling)}; - Results = {Header(FE)}; - // FIXME: compute transitive exporter headers. - for (const auto *Export : PI.getExporters(FE, SM.getFileManager())) - Results.push_back(Export); + if (PI) { + // We treat the spelling header in the IWYU pragma as the final public + // header. + // FIXME: look for exporters if the public header is exported by another + // header. + llvm::StringRef VerbatimSpelling = PI->getPublic(FE); + if (!VerbatimSpelling.empty()) + return {Header(VerbatimSpelling)}; + + // FIXME: compute transitive exporter headers. + for (const auto *Export : PI->getExporters(FE, SM.getFileManager())) + Results.push_back(Export); + } + return Results; } case SymbolLocation::Standard: { diff --git a/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp b/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp --- a/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp +++ b/clang-tools-extra/include-cleaner/lib/HTMLReport.cpp @@ -14,12 +14,13 @@ //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" -#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Types.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" #include "clang/AST/PrettyPrinter.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "clang/Tooling/Inclusions/StandardLibrary.h" +#include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" namespace clang::include_cleaner { @@ -38,16 +39,24 @@ } .ref { text-decoration: underline; color: #008; } .sel { position: relative; cursor: pointer; } + .ref.implicit { background-color: #ff8; } #hover { - background-color: #aaccff; border: 1px solid black; + color: black; + background-color: #aaccff; border: 1px solid #444; z-index: 1; position: absolute; top: 100%; left: 0; font-family: sans-serif; padding: 0.5em; } #hover p, #hover pre { margin: 0; } - #hover section header { font-weight: bold; } - #hover section:not(:first-child) { margin-top: 1em; } + #hover .target.implicit { background-color: #bbb; } + #hover .target.ambiguous { background-color: #caf; } + #hover th { color: #008; text-align: right; padding-right: 0.5em; } + #hover .target:not(:first-child) { + margin-top: 1em; + padding-top: 1em; + border-top: 1px solid #444; + } )css"; constexpr llvm::StringLiteral JS = R"js( @@ -74,49 +83,95 @@ } )js"; -// Print the declaration tersely, but enough to identify e.g. which overload. -std::string printDecl(const NamedDecl &ND) { - std::string S; - llvm::raw_string_ostream OS(S); - PrintingPolicy PP = ND.getASTContext().getPrintingPolicy(); - PP.FullyQualifiedName = true; - PP.TerseOutput = true; - PP.SuppressInitializers = true; - ND.print(OS, PP); - llvm::erase_value(S, '\n'); - return S; +// Categorize the symbol, like FunctionDecl or Macro +llvm::StringRef describeSymbol(const Symbol &Sym) { + switch (Sym.kind()) { + case Symbol::Declaration: + return Sym.declaration().getDeclKindName(); + case Symbol::Macro: + return "Macro"; + } + llvm_unreachable("unhandled symbol kind"); +} + +llvm::StringRef refType(RefType T) { + switch (T) { + case RefType::Explicit: + return "explicit"; + case RefType::Implicit: + return "implicit"; + case RefType::Ambiguous: + return "ambiguous"; + } } class Reporter { llvm::raw_ostream &OS; const ASTContext &Ctx; const SourceManager &SM; + const PragmaIncludes *PI; FileID File; - // Symbols that are referenced from the main file. + // References to symbols from the main file. + // FIXME: should we deduplicate these? struct Target { - const NamedDecl *D; + Symbol Sym; + RefType Type; + SmallVector Locations; + SmallVector
Headers; }; std::vector Targets; // Points within the main file that reference a Target. - std::vector> Refs; + // Implicit refs will be marked with a symbol just before the token. + struct Ref { + unsigned Offset; + bool Implicit; + size_t TargetIndex; + bool operator<(const Ref &Other) const { + return std::forward_as_tuple(Offset, !Implicit, TargetIndex) < + std::forward_as_tuple(Other.Offset, !Other.Implicit, TargetIndex); + } + }; + std::vector Refs; + + Target makeTarget(const SymbolReference &SR) { + Target T{SR.Target, SR.RT, {}, {}}; + + // Duplicates logic from walkUsed(), which doesn't expose SymbolLocations. + // FIXME: use locateDecl and friends once implemented. + // This doesn't use stdlib::Recognizer, but locateDecl will soon do that. + switch (SR.Target.kind()) { + case Symbol::Declaration: + T.Locations.push_back(SR.Target.declaration().getLocation()); + break; + case Symbol::Macro: + T.Locations.push_back(SR.Target.macro().Definition); + break; + } + + for (const auto &Loc : T.Locations) + T.Headers = findHeaders(Loc, SM, PI); + + return T; + } public: - Reporter(llvm::raw_ostream &OS, ASTContext &Ctx, FileID File) - : OS(OS), Ctx(Ctx), SM(Ctx.getSourceManager()), File(File) {} + Reporter(llvm::raw_ostream &OS, ASTContext &Ctx, const PragmaIncludes *PI, + FileID File) + : OS(OS), Ctx(Ctx), SM(Ctx.getSourceManager()), PI(PI), File(File) {} - void addRef(SourceLocation Loc, const NamedDecl &D) { - auto [File, Offset] = SM.getDecomposedLoc(SM.getFileLoc(Loc)); + void addRef(const SymbolReference &SR) { + auto [File, Offset] = SM.getDecomposedLoc(SM.getFileLoc(SR.RefLocation)); if (File != this->File) { // Can get here e.g. if there's an #include inside a root Decl. // FIXME: do something more useful than this. - llvm::errs() << "Ref location outside file! " - << D.getQualifiedNameAsString() << " at " - << Loc.printToString(SM) << "\n"; + llvm::errs() << "Ref location outside file! " << SR.Target << " at " + << SR.RefLocation.printToString(SM) << "\n"; return; } - Targets.push_back({&D}); - Refs.push_back({Offset, Targets.size() - 1}); + + Refs.push_back({Offset, SR.RT == RefType::Implicit, Targets.size()}); + Targets.push_back(makeTarget(SR)); } void write() { @@ -126,9 +181,9 @@ OS << "\n"; OS << "\n"; for (unsigned I = 0; I < Targets.size(); ++I) { - OS << "\n"; } OS << "\n"; OS << "\n"; @@ -156,16 +211,84 @@ escapeChar(C); } + // Abbreviate a path ('path/to/Foo.h') to just the filename ('Foo.h'). + // The full path is available on hover. + void printFilename(llvm::StringRef Path) { + llvm::StringRef File = llvm::sys::path::filename(Path); + if (File == Path) + return escapeString(Path); + OS << ""; + escapeString(File); + OS << ""; + } + + // Print a source location in compact style. + void printSourceLocation(SourceLocation Loc) { + if (Loc.isInvalid()) + return escapeString(""); + if (!Loc.isMacroID()) + return printFilename(Loc.printToString(SM)); + + // Replicating printToString() is a bit simpler than parsing/reformatting. + printFilename(SM.getExpansionLoc(Loc).printToString(SM)); + OS << " <Spelling="; + printFilename(SM.getSpellingLoc(Loc).printToString(SM)); + OS << ">"; + } + void writeTarget(const Target &T) { - OS << "
" << T.D->getDeclKindName() << " "; - escapeString(T.D->getQualifiedNameAsString()); - OS << "
"; + OS << ""; + + OS << "\n"; + + if (T.Sym.kind() == Symbol::Declaration) { + // Print the declaration of the symbol, e.g. to disambiguate overloads. + const auto &D = T.Sym.declaration(); + PrintingPolicy PP = D.getASTContext().getPrintingPolicy(); + PP.FullyQualifiedName = true; + PP.TerseOutput = true; + PP.SuppressInitializers = true; + std::string S; + llvm::raw_string_ostream SS(S); + D.print(SS, PP); + + OS << "\n"; + } + + for (const auto &Loc : T.Locations) { + OS << "\n"; + } + + for (const auto &H : T.Headers) { + OS << "\n"; + } - OS << "

declared at "; - escapeString(SM.getFileLoc(T.D->getLocation()).printToString(SM)); - OS << "

";
-    escapeString(printDecl(*T.D));
-    OS << "
"; + OS << "
Symbol"; + OS << describeSymbol(T.Sym) << " "; + escapeString(llvm::to_string(T.Sym)); + OS << "
"; + escapeString(S); + OS << "
Location"; + if (Loc.kind() == SymbolLocation::Physical) // needs SM to print properly. + printSourceLocation(Loc.physical()); + else + escapeString(llvm::to_string(Loc)); + OS << "
Header"; + switch (H.kind()) { + case Header::Physical: + printFilename(H.physical()->getName()); + break; + case Header::Standard: + OS << "stdlib " << H.standard().name(); + break; + case Header::Verbatim: + OS << "verbatim "; + escapeString(H.verbatim()); + break; + } + OS << "
"; } void writeCode() { @@ -182,14 +305,23 @@ OS << ""; End = 0; } + // Handle implicit refs, which are rendered *before* the token. + while (!Rest.empty() && Rest.front().Offset == I && + Rest.front().Implicit) { + const Ref &R = Rest.front(); + OS << ""; + Rest = Rest.drop_front(); + }; + // Accumulate all explicit refs that appear on the same token. std::string TargetList; - Rest = Rest.drop_while([&](auto &R) { - if (R.first != I) + Rest = Rest.drop_while([&](const Ref &R) { + if (R.Offset != I) return false; if (!TargetList.empty()) TargetList.push_back(','); TargetList.push_back('t'); - TargetList.append(std::to_string(R.second)); + TargetList.append(std::to_string(R.TargetIndex)); return true; }); if (!TargetList.empty()) { @@ -209,13 +341,16 @@ } // namespace -void writeHTMLReport(FileID File, llvm::ArrayRef Roots, ASTContext &Ctx, - llvm::raw_ostream &OS) { - Reporter R(OS, Ctx, File); +void writeHTMLReport(FileID File, llvm::ArrayRef Roots, + llvm::ArrayRef MacroRefs, ASTContext &Ctx, + PragmaIncludes *PI, llvm::raw_ostream &OS) { + Reporter R(OS, Ctx, PI, File); for (Decl *Root : Roots) - walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType) { - R.addRef(Loc, D); + walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType T) { + R.addRef(SymbolReference{Loc, D, T}); }); + for (const SymbolReference &Ref : MacroRefs) + R.addRef(Ref); R.write(); } diff --git a/clang-tools-extra/include-cleaner/lib/Types.cpp b/clang-tools-extra/include-cleaner/lib/Types.cpp --- a/clang-tools-extra/include-cleaner/lib/Types.cpp +++ b/clang-tools-extra/include-cleaner/lib/Types.cpp @@ -18,10 +18,10 @@ switch (S.kind()) { case Symbol::Declaration: if (const auto *ND = llvm::dyn_cast(&S.declaration())) - return OS << ND->getNameAsString(); + return OS << ND->getQualifiedNameAsString(); return OS << S.declaration().getDeclKindName(); case Symbol::Macro: - return OS << S.macro().Name; + return OS << S.macro().Name->getName(); } llvm_unreachable("Unhandled Symbol kind"); } diff --git a/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp b/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp --- a/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp +++ b/clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp @@ -47,6 +47,15 @@ class HTMLReportAction : public clang::ASTFrontendAction { RecordedAST AST; + RecordedPP PP; + PragmaIncludes PI; + + void ExecuteAction() override { + auto &P = getCompilerInstance().getPreprocessor(); + P.addPPCallbacks(PP.record(P)); + PI.record(getCompilerInstance()); + ASTFrontendAction::ExecuteAction(); + } std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef File) override { @@ -62,7 +71,7 @@ exit(1); } writeHTMLReport(AST.Ctx->getSourceManager().getMainFileID(), AST.Roots, - *AST.Ctx, OS); + PP.MacroReferences, *AST.Ctx, &PI, OS); } }; 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 @@ -72,7 +72,7 @@ auto &SM = AST.sourceManager(); llvm::DenseMap> OffsetToProviders; - walkUsed(TopLevelDecls, /*MacroRefs=*/{}, PI, SM, + walkUsed(TopLevelDecls, /*MacroRefs=*/{}, &PI, SM, [&](const SymbolReference &Ref, llvm::ArrayRef
Providers) { auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation); EXPECT_EQ(FID, SM.getMainFileID()); @@ -113,11 +113,10 @@ Symbol Answer = Macro{&Idents.get("ANSWER"), SM.getComposedLoc(HdrID, Hdr.point())}; llvm::DenseMap> OffsetToProviders; - PragmaIncludes PI; walkUsed(/*ASTRoots=*/{}, /*MacroRefs=*/ {SymbolReference{SM.getComposedLoc(SM.getMainFileID(), Main.point()), Answer, RefType::Explicit}}, - PI, SM, + /*PI=*/nullptr, SM, [&](const SymbolReference &Ref, llvm::ArrayRef
Providers) { auto [FID, Offset] = SM.getDecomposedLoc(Ref.RefLocation); EXPECT_EQ(FID, SM.getMainFileID()); 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 @@ -61,19 +61,19 @@ /*Line=*/1, /*Col=*/1); }; - EXPECT_THAT(findHeaders(SourceLocFromFile("header1.h"), SM, PI), + EXPECT_THAT(findHeaders(SourceLocFromFile("header1.h"), SM, &PI), UnorderedElementsAre(Header("\"path/public.h\""))); - EXPECT_THAT(findHeaders(SourceLocFromFile("detail1.h"), SM, PI), + EXPECT_THAT(findHeaders(SourceLocFromFile("detail1.h"), SM, &PI), UnorderedElementsAre(Header(FM.getFile("header2.h").get()), Header(FM.getFile("detail1.h").get()))); - EXPECT_THAT(findHeaders(SourceLocFromFile("detail2.h"), SM, PI), + EXPECT_THAT(findHeaders(SourceLocFromFile("detail2.h"), SM, &PI), UnorderedElementsAre(Header(FM.getFile("header2.h").get()), Header(FM.getFile("detail2.h").get()))); - EXPECT_THAT(findHeaders(SourceLocFromFile("normal.h"), SM, PI), + EXPECT_THAT(findHeaders(SourceLocFromFile("normal.h"), SM, &PI), UnorderedElementsAre(Header(FM.getFile("normal.h").get()))); } } // namespace } // namespace clang::include_cleaner \ No newline at end of file