sammccall created this revision. sammccall added a reviewer: hokein. Herald added a project: All. sammccall requested review of this revision. Herald added a project: clang-tools-extra. Herald added a subscriber: cfe-commits.
Demo: https://gistpreview.github.io/?fec9b77c726cfb3cc7c424b197e3f68c Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D137677 Files: clang-tools-extra/include-cleaner/lib/AnalysisInternal.h clang-tools-extra/include-cleaner/lib/HTMLReport.cpp clang-tools-extra/include-cleaner/lib/Types.cpp clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
Index: clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp =================================================================== --- clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp +++ clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp @@ -47,6 +47,13 @@ class HTMLReportAction : public clang::ASTFrontendAction { RecordedAST AST; + RecordedPP PP; + + void ExecuteAction() override { + auto &P = getCompilerInstance().getPreprocessor(); + P.addPPCallbacks(PP.record(P)); + ASTFrontendAction::ExecuteAction(); + } std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef File) override { @@ -62,7 +69,7 @@ exit(1); } writeHTMLReport(AST.Ctx->getSourceManager().getMainFileID(), AST.Roots, - *AST.Ctx, OS); + PP.MacroReferences, *AST.Ctx, OS); } }; Index: clang-tools-extra/include-cleaner/lib/Types.cpp =================================================================== --- clang-tools-extra/include-cleaner/lib/Types.cpp +++ clang-tools-extra/include-cleaner/lib/Types.cpp @@ -21,7 +21,7 @@ return OS << ND->getNameAsString(); return OS << S.declaration().getDeclKindName(); case Symbol::Macro: - return OS << S.macro().Name; + return OS << S.macro().Name->getName(); case Symbol::Standard: return OS << S.standard().scope() << S.standard().name(); } Index: clang-tools-extra/include-cleaner/lib/HTMLReport.cpp =================================================================== --- clang-tools-extra/include-cleaner/lib/HTMLReport.cpp +++ clang-tools-extra/include-cleaner/lib/HTMLReport.cpp @@ -15,11 +15,12 @@ #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 "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" namespace clang::include_cleaner { @@ -38,6 +39,7 @@ } .ref { text-decoration: underline; color: #008; } .sel { position: relative; cursor: pointer; } + .ref.implicit { background-color: #ff8; } #hover { background-color: #aaccff; border: 1px solid black; z-index: 1; @@ -47,6 +49,8 @@ } #hover p, #hover pre { margin: 0; } #hover section header { font-weight: bold; } + #hover section.implicit { background-color: #bbb; } + #hover section.ambiguous { background-color: #caf; } #hover section:not(:first-child) { margin-top: 1em; } )css"; @@ -75,18 +79,67 @@ )js"; // Print the declaration tersely, but enough to identify e.g. which overload. -std::string printDecl(const NamedDecl &ND) { +std::string printSymbol(const Symbol &Sym) { 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); + switch (Sym.kind()) { + case Symbol::Declaration: { + const auto &D = Sym.declaration(); + PrintingPolicy PP = D.getASTContext().getPrintingPolicy(); + PP.FullyQualifiedName = true; + PP.TerseOutput = true; + PP.SuppressInitializers = true; + D.print(OS, PP); + break; + } + case Symbol::Macro: + OS << "#define " << Sym.macro().Name->getName() << " ..."; + break; + case Symbol::Standard: + OS << "(defined in stdlib)"; + break; + } 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"; + case Symbol::Standard: + return "Standard symbol"; + } + llvm_unreachable("unhandled symbol kind"); +} + +// Categorize the symbol, like FunctionDecl or Macro +std::string locateSymbol(const Symbol &Sym, const SourceManager &SM) { + switch (Sym.kind()) { + case Symbol::Declaration: + return Sym.declaration().getLocation().printToString(SM); + case Symbol::Macro: + return Sym.macro().Definition.printToString(SM); + case Symbol::Standard: + return ("stdlib header " + Sym.standard().header().name()).str(); + } + llvm_unreachable("unhandled symbol kind"); +} + +llvm::StringRef refType(RefType T) { + switch (T) { + case RefType::Explicit: + return ""; + case RefType::Implicit: + return "implicit"; + case RefType::Ambiguous: + return "ambiguous"; + } +} + class Reporter { llvm::raw_ostream &OS; const ASTContext &Ctx; @@ -94,29 +147,41 @@ FileID File; // Symbols that are referenced from the main file. + // FIXME: should we deduplicate these? struct Target { - const NamedDecl *D; + Symbol Sym; + RefType Type; + SmallVector<Header> Providers; }; std::vector<Target> Targets; // Points within the main file that reference a Target. - std::vector<std::pair</*Offset*/ unsigned, /*TargetIndex*/ unsigned>> 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<Ref> Refs; public: Reporter(llvm::raw_ostream &OS, ASTContext &Ctx, FileID File) : OS(OS), Ctx(Ctx), SM(Ctx.getSourceManager()), File(File) {} - void addRef(SourceLocation Loc, const NamedDecl &D) { - auto [File, Offset] = SM.getDecomposedLoc(SM.getFileLoc(Loc)); + void addRef(const SymbolReference &SR, llvm::ArrayRef<Header> Headers) { + 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}); + Targets.push_back({SR.Target, SR.RT, {Headers.begin(), Headers.end()}}); + Refs.push_back({Offset, SR.RT == RefType::Implicit, Targets.size() - 1}); } void write() { @@ -126,7 +191,8 @@ OS << "<style>" << CSS << "</style>\n"; OS << "<script>" << JS << "</script>\n"; for (unsigned I = 0; I < Targets.size(); ++I) { - OS << "<template id='t" << I << "'><section>"; + OS << "<template id='t" << I << "'><section class='" + << refType(Targets[I].Type) << "'>"; writeTarget(Targets[I]); OS << "</section></template>\n"; } @@ -157,15 +223,27 @@ } void writeTarget(const Target &T) { - OS << "<header>" << T.D->getDeclKindName() << " "; - escapeString(T.D->getQualifiedNameAsString()); - OS << "</header>"; + OS << "<header>"; + if (T.Type != RefType::Explicit) + OS << refType(T.Type) << " "; + OS << describeSymbol(T.Sym) << " "; + escapeString(llvm::to_string(T.Sym)); + OS << "</header>\n"; OS << "<p>declared at "; - escapeString(SM.getFileLoc(T.D->getLocation()).printToString(SM)); - OS << "</p><pre>"; - escapeString(printDecl(*T.D)); - OS << "</pre>"; + escapeString(locateSymbol(T.Sym, SM)); + OS << "</p>\n"; + + OS << "<pre>"; + escapeString(printSymbol(T.Sym)); + OS << "</pre>\n"; + + OS << "<ul>"; + for (Header H : T.Providers) { + OS << "Candidate: "; + escapeString(llvm::to_string(H)); + } + OS << "</ul>\n"; } void writeCode() { @@ -182,14 +260,23 @@ OS << "</span>"; 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 << "<span class='ref sel implicit' data-hover='t" << R.TargetIndex + << "'>◊</span>"; + 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 +296,14 @@ } // namespace -void writeHTMLReport(FileID File, llvm::ArrayRef<Decl *> Roots, ASTContext &Ctx, +void writeHTMLReport(FileID File, llvm::ArrayRef<Decl *> Roots, + llvm::ArrayRef<SymbolReference> MacroRefs, ASTContext &Ctx, llvm::raw_ostream &OS) { Reporter R(OS, Ctx, File); - for (Decl *Root : Roots) - walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType) { - R.addRef(Loc, D); - }); + walkUsed(Roots, MacroRefs, Ctx.getSourceManager(), + [&](const SymbolReference &Ref, llvm::ArrayRef<Header> Headers) { + R.addRef(Ref, Headers); + }); R.write(); } 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 @@ -48,7 +48,8 @@ /// 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, +void writeHTMLReport(FileID File, llvm::ArrayRef<Decl *> Roots, + llvm::ArrayRef<SymbolReference> MacroRefs, ASTContext &Ctx, llvm::raw_ostream &OS); } // namespace include_cleaner
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits