brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.9 KiB · b0a6c95 Raw
521 lines · cpp
1//===--- HTMLReport.cpp - Explain the analysis for humans -----------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// If we're debugging this tool or trying to explain its conclusions, we need to10// be able to identify specific facts about the code and the inferences made.11//12// This library prints an annotated version of the code13//14//===----------------------------------------------------------------------===//15 16#include "AnalysisInternal.h"17#include "clang-include-cleaner/IncludeSpeller.h"18#include "clang-include-cleaner/Types.h"19#include "clang/AST/ASTContext.h"20#include "clang/AST/PrettyPrinter.h"21#include "clang/Basic/SourceManager.h"22#include "clang/Lex/HeaderSearch.h"23#include "clang/Lex/Lexer.h"24#include "clang/Lex/Preprocessor.h"25#include "clang/Tooling/Inclusions/StandardLibrary.h"26#include "llvm/Support/ScopedPrinter.h"27#include "llvm/Support/raw_ostream.h"28#include <numeric>29 30namespace clang::include_cleaner {31namespace {32 33constexpr llvm::StringLiteral CSS = R"css(34  body { margin: 0; }35  pre { line-height: 1.5em; counter-reset: line; margin: 0; }36  pre .line:not(.added) { counter-increment: line; }37  pre .line::before {38    content: counter(line);39    display: inline-block;40    background-color: #eee; border-right: 1px solid #ccc;41    text-align: right;42    width: 3em; padding-right: 0.5em; margin-right: 0.5em;43  }44  pre .line.added::before { content: '+' }45  .ref, .inc { text-decoration: underline; color: #008; }46  .sel { position: relative; cursor: pointer; }47  .ref.implicit { background-color: #ff8; }48  #hover {49    color: black;50    background-color: #aaccff; border: 1px solid #444;51    z-index: 1;52    position: absolute; top: 100%; left: 0;53    font-family: sans-serif;54    padding: 0.5em;55  }56  #hover p, #hover pre { margin: 0; }57  #hover .target.implicit, .provides .implicit { background-color: #bbb; }58  #hover .target.ambiguous, .provides .ambiguous { background-color: #caf; }59  .missing, .unused { background-color: #faa !important; }60  .inserted { background-color: #bea !important; }61  .semiused { background-color: #888 !important; }62  #hover th { color: #008; text-align: right; padding-right: 0.5em; }63  #hover .target:not(:first-child) {64    margin-top: 1em;65    padding-top: 1em;66    border-top: 1px solid #444;67  }68  .ref.missing #hover .insert { background-color: #bea; }69  .ref:not(.missing) #hover .insert { font-style: italic; }70)css";71 72constexpr llvm::StringLiteral JS = R"js(73  // Recreate the #hover div inside whichever target .sel element was clicked.74  function select(event) {75    var target = event.target.closest('.sel');76    var hover = document.getElementById('hover');77    if (hover) {78      if (hover.parentElement == target) return;79      hover.parentNode.removeChild(hover);80    }81    if (target == null) return;82    hover = document.createElement('div');83    hover.id = 'hover';84    fillHover(hover, target);85    target.appendChild(hover);86  }87  // Fill the #hover div with the templates named by data-hover in the target.88  function fillHover(hover, target) {89    target.dataset.hover?.split(',').forEach(function(id) {90      for (c of document.getElementById(id).content.childNodes)91        hover.appendChild(c.cloneNode(true));92    })93  }94)js";95 96// Categorize the symbol, like FunctionDecl or Macro97llvm::StringRef describeSymbol(const Symbol &Sym) {98  switch (Sym.kind()) {99  case Symbol::Declaration:100    return Sym.declaration().getDeclKindName();101  case Symbol::Macro:102    return "Macro";103  }104  llvm_unreachable("unhandled symbol kind");105}106 107// Return detailed symbol description (declaration), if we have any.108std::string printDetails(const Symbol &Sym) {109  std::string S;110  if (Sym.kind() == Symbol::Declaration) {111    // Print the declaration of the symbol, e.g. to disambiguate overloads.112    const auto &D = Sym.declaration();113    PrintingPolicy PP = D.getASTContext().getPrintingPolicy();114    PP.FullyQualifiedName = true;115    PP.TerseOutput = true;116    PP.SuppressInitializers = true;117    llvm::raw_string_ostream SS(S);118    D.print(SS, PP);119  }120  return S;121}122 123llvm::StringRef refType(RefType T) {124  switch (T) {125  case RefType::Explicit:126    return "explicit";127  case RefType::Implicit:128    return "implicit";129  case RefType::Ambiguous:130    return "ambiguous";131  }132  llvm_unreachable("unhandled RefType enum");133}134 135class Reporter {136  llvm::raw_ostream &OS;137  const ASTContext &Ctx;138  const SourceManager &SM;139  const Preprocessor &PP;140  const include_cleaner::Includes &Includes;141  const PragmaIncludes *PI;142  FileID MainFile;143  const FileEntry *MainFE;144 145  // Points within the main file that reference a Symbol.146  // Implicit refs will be marked with a symbol just before the token.147  struct Ref {148    unsigned Offset;149    RefType Type;150    Symbol Sym;151    SmallVector<SymbolLocation> Locations = {};152    SmallVector<Header> Headers = {};153    SmallVector<const Include *> Includes = {};154    bool Satisfied = false;  // Is the include present?155    std::string Insert = {}; // If we had no includes, what would we insert?156  };157  std::vector<Ref> Refs;158  llvm::DenseMap<const Include *, std::vector<unsigned>> IncludeRefs;159  llvm::StringMap<std::vector</*RefIndex*/ unsigned>> Insertion;160 161  llvm::StringRef includeType(const Include *I) {162    auto &List = IncludeRefs[I];163    if (List.empty())164      return "unused";165    if (llvm::any_of(List, [&](unsigned I) {166          return Refs[I].Type == RefType::Explicit;167        }))168      return "used";169    return "semiused";170  }171 172  void fillTarget(Ref &R) {173    // Duplicates logic from walkUsed(), which doesn't expose SymbolLocations.174    for (auto &Loc : locateSymbol(R.Sym, Ctx.getLangOpts()))175      R.Locations.push_back(Loc);176    R.Headers = headersForSymbol(R.Sym, PP, PI);177 178    for (const auto &H : R.Headers) {179      R.Includes.append(Includes.match(H));180      // FIXME: library should signal main-file refs somehow.181      // Non-physical refs to the main-file should be possible.182      if (H.kind() == Header::Physical && H.physical() == MainFE)183        R.Satisfied = true;184    }185    if (!R.Includes.empty())186      R.Satisfied = true;187    // Include pointers are meaningfully ordered as they are backed by a vector.188    llvm::sort(R.Includes);189    R.Includes.erase(llvm::unique(R.Includes), R.Includes.end());190 191    if (!R.Headers.empty())192      R.Insert =193          spellHeader({R.Headers.front(), PP.getHeaderSearchInfo(), MainFE});194  }195 196public:197  Reporter(llvm::raw_ostream &OS, ASTContext &Ctx, const Preprocessor &PP,198           const include_cleaner::Includes &Includes, const PragmaIncludes *PI,199           FileID MainFile)200      : OS(OS), Ctx(Ctx), SM(Ctx.getSourceManager()), PP(PP),201        Includes(Includes), PI(PI), MainFile(MainFile),202        MainFE(SM.getFileEntryForID(MainFile)) {}203 204  void addRef(const SymbolReference &SR) {205    auto [File, Offset] = SM.getDecomposedLoc(SM.getFileLoc(SR.RefLocation));206    if (File != this->MainFile) {207      // Can get here e.g. if there's an #include inside a root Decl.208      // FIXME: do something more useful than this.209      llvm::errs() << "Ref location outside file! " << SR.Target << " at "210                   << SR.RefLocation.printToString(SM) << "\n";211      return;212    }213 214    int RefIndex = Refs.size();215    Refs.emplace_back(Ref{Offset, SR.RT, SR.Target});216    Ref &R = Refs.back();217    fillTarget(R);218    for (const auto *I : R.Includes)219      IncludeRefs[I].push_back(RefIndex);220    if (R.Type == RefType::Explicit && !R.Satisfied && !R.Insert.empty())221      Insertion[R.Insert].push_back(RefIndex);222  }223 224  void write() {225    OS << "<!doctype html>\n";226    OS << "<html>\n";227    OS << "<head>\n";228    OS << "<style>" << CSS << "</style>\n";229    OS << "<script>" << JS << "</script>\n";230    for (const auto &Ins : Insertion) {231      OS << "<template id='i";232      escapeString(Ins.first());233      OS << "'>";234      writeInsertion(Ins.first(), Ins.second);235      OS << "</template>\n";236    }237    for (auto &Inc : Includes.all()) {238      OS << "<template id='i" << Inc.Line << "'>";239      writeInclude(Inc);240      OS << "</template>\n";241    }242    for (unsigned I = 0; I < Refs.size(); ++I) {243      OS << "<template id='t" << I << "'>";244      writeTarget(Refs[I]);245      OS << "</template>\n";246    }247    OS << "</head>\n";248    OS << "<body>\n";249    writeCode();250    OS << "</body>\n";251    OS << "</html>\n";252  }253 254private:255  void escapeChar(char C) {256    switch (C) {257    case '<':258      OS << "&lt;";259      break;260    case '&':261      OS << "&amp;";262      break;263    default:264      OS << C;265    }266  }267 268  void escapeString(llvm::StringRef S) {269    for (char C : S)270      escapeChar(C);271  }272 273  // Abbreviate a path ('path/to/Foo.h') to just the filename ('Foo.h').274  // The full path is available on hover.275  void printFilename(llvm::StringRef Path) {276    llvm::StringRef File = llvm::sys::path::filename(Path);277    if (File == Path)278      return escapeString(Path);279    OS << "<span title='";280    escapeString(Path);281    OS << "'>";282    escapeString(File);283    OS << "</span>";284  }285 286  // Print a source location in compact style.287  void printSourceLocation(SourceLocation Loc) {288    if (Loc.isInvalid())289      return escapeString("<invalid>");290    if (!Loc.isMacroID())291      return printFilename(Loc.printToString(SM));292 293    // Replicating printToString() is a bit simpler than parsing/reformatting.294    printFilename(SM.getExpansionLoc(Loc).printToString(SM));295    OS << " &lt;Spelling=";296    printFilename(SM.getSpellingLoc(Loc).printToString(SM));297    OS << ">";298  }299 300  // Write "Provides: " rows of an include or include-insertion table.301  // These describe the symbols the header provides, referenced by RefIndices.302  void writeProvides(llvm::ArrayRef<unsigned> RefIndices) {303    // We show one ref for each symbol: first by (RefType != Explicit, Sequence)304    llvm::DenseMap<Symbol, /*RefIndex*/ unsigned> FirstRef;305    for (unsigned RefIndex : RefIndices) {306      const Ref &R = Refs[RefIndex];307      auto I = FirstRef.try_emplace(R.Sym, RefIndex);308      if (!I.second && R.Type == RefType::Explicit &&309          Refs[I.first->second].Type != RefType::Explicit)310        I.first->second = RefIndex;311    }312    std::vector<std::pair<Symbol, unsigned>> Sorted = {FirstRef.begin(),313                                                       FirstRef.end()};314    llvm::stable_sort(Sorted, llvm::less_second{});315    for (auto &[S, RefIndex] : Sorted) {316      auto &R = Refs[RefIndex];317      OS << "<tr class='provides'><th>Provides</td><td>";318      std::string Details = printDetails(S);319      if (!Details.empty()) {320        OS << "<span class='" << refType(R.Type) << "' title='";321        escapeString(Details);322        OS << "'>";323      }324      escapeString(llvm::to_string(S));325      if (!Details.empty())326        OS << "</span>";327 328      unsigned Line = SM.getLineNumber(MainFile, R.Offset);329      OS << ", <a href='#line" << Line << "'>line " << Line << "</a>";330      OS << "</td></tr>";331    }332  }333 334  void writeInclude(const Include &Inc) {335    OS << "<table class='include'>";336    if (Inc.Resolved) {337      OS << "<tr><th>Resolved</td><td>";338      escapeString(Inc.Resolved->getName());339      OS << "</td></tr>\n";340      writeProvides(IncludeRefs[&Inc]);341    }342    OS << "</table>";343  }344 345  void writeInsertion(llvm::StringRef Text, llvm::ArrayRef<unsigned> Refs) {346    OS << "<table class='insertion'>";347    writeProvides(Refs);348    OS << "</table>";349  }350 351  void writeTarget(const Ref &R) {352    OS << "<table class='target " << refType(R.Type) << "'>";353 354    OS << "<tr><th>Symbol</th><td>";355    OS << describeSymbol(R.Sym) << " <code>";356    escapeString(llvm::to_string(R.Sym));357    OS << "</code></td></tr>\n";358 359    std::string Details = printDetails(R.Sym);360    if (!Details.empty()) {361      OS << "<tr><td></td><td><code>";362      escapeString(Details);363      OS << "</code></td></tr>\n";364    }365 366    for (const auto &Loc : R.Locations) {367      OS << "<tr><th>Location</th><td>";368      if (Loc.kind() == SymbolLocation::Physical) // needs SM to print properly.369        printSourceLocation(Loc.physical());370      else371        escapeString(llvm::to_string(Loc));372      OS << "</td></tr>\n";373    }374 375    for (const auto &H : R.Headers) {376      OS << "<tr><th>Header</th><td>";377      switch (H.kind()) {378      case Header::Physical:379        printFilename(H.physical().getName());380        break;381      case Header::Standard:382        OS << "stdlib " << H.standard().name();383        break;384      case Header::Verbatim:385        OS << "verbatim ";386        escapeString(H.verbatim());387        break;388      }389      OS << "</td></tr>\n";390    }391 392    for (const auto *I : R.Includes) {393      OS << "<tr><th>Included</th><td>";394      escapeString(I->quote());395      OS << ", <a href='#line" << I->Line << "'>line " << I->Line << "</a>";396      OS << "</td></tr>";397    }398 399    if (!R.Insert.empty()) {400      OS << "<tr><th>Insert</th><td class='insert'>";401      escapeString(R.Insert);402      OS << "</td></tr>";403    }404 405    OS << "</table>";406  }407 408  void writeCode() {409    llvm::StringRef Code = SM.getBufferData(MainFile);410 411    OS << "<pre onclick='select(event)' class='code'>";412 413    std::vector<llvm::StringRef> Insertions{Insertion.keys().begin(),414                                            Insertion.keys().end()};415    llvm::sort(Insertions);416    for (llvm::StringRef Insertion : Insertions) {417      OS << "<code class='line added'>"418         << "<span class='inc sel inserted' data-hover='i";419      escapeString(Insertion);420      OS << "'>#include ";421      escapeString(Insertion);422      OS << "</span></code>\n";423    }424 425    const Include *Inc = nullptr;426    unsigned LineNum = 0;427    // Lines are <code>, include lines have an inner <span>.428    auto StartLine = [&] {429      ++LineNum;430      OS << "<code class='line' id='line" << LineNum << "'>";431      if ((Inc = Includes.atLine(LineNum)))432        OS << "<span class='inc sel " << includeType(Inc) << "' data-hover='i"433           << Inc->Line << "'>";434    };435    auto EndLine = [&] {436      if (Inc)437        OS << "</span>";438      OS << "</code>\n";439    };440 441    std::vector<unsigned> RefOrder(Refs.size());442    std::iota(RefOrder.begin(), RefOrder.end(), 0);443    llvm::stable_sort(RefOrder, [&](unsigned A, unsigned B) {444      return std::make_pair(Refs[A].Offset, Refs[A].Type != RefType::Implicit) <445             std::make_pair(Refs[B].Offset, Refs[B].Type != RefType::Implicit);446    });447    auto Rest = llvm::ArrayRef(RefOrder);448    unsigned End = 0;449    StartLine();450    for (unsigned I = 0; I < Code.size(); ++I) {451      // Finish refs early at EOL to avoid dealing with splitting the span.452      if (End && (End == I || Code[I] == '\n')) {453        OS << "</span>";454        End = 0;455      }456      // Handle implicit refs, which are rendered *before* the token.457      while (!Rest.empty() && Refs[Rest.front()].Offset == I &&458             Refs[Rest.front()].Type == RefType::Implicit) {459        const Ref &R = Refs[Rest.front()];460        OS << "<span class='ref sel implicit "461           << (R.Satisfied ? "satisfied" : "missing") << "' data-hover='t"462           << Rest.front() << "'>&loz;</span>";463        Rest = Rest.drop_front();464      };465      // Accumulate all explicit refs that appear on the same token.466      std::string TargetList;467      bool Unsatisfied = false;468      Rest = Rest.drop_while([&](unsigned RefIndex) {469        const Ref &R = Refs[RefIndex];470        if (R.Offset != I)471          return false;472        if (!TargetList.empty())473          TargetList.push_back(',');474        TargetList.push_back('t');475        TargetList.append(std::to_string(RefIndex));476        Unsatisfied = Unsatisfied || !R.Satisfied;477        return true;478      });479      if (!TargetList.empty()) {480        assert(End == 0 && "Overlapping tokens!");481        OS << "<span class='ref sel" << (Unsatisfied ? " missing" : "")482           << "' data-hover='" << TargetList << "'>";483        End = I + Lexer::MeasureTokenLength(SM.getComposedLoc(MainFile, I), SM,484                                            Ctx.getLangOpts());485      }486      if (Code[I] == '\n') {487        EndLine();488        StartLine();489      } else490        escapeChar(Code[I]);491    }492    EndLine();493    OS << "</pre>\n";494  }495};496 497} // namespace498 499void writeHTMLReport(FileID File, const include_cleaner::Includes &Includes,500                     llvm::ArrayRef<Decl *> Roots,501                     llvm::ArrayRef<SymbolReference> MacroRefs, ASTContext &Ctx,502                     const Preprocessor &PP, PragmaIncludes *PI,503                     llvm::raw_ostream &OS) {504  Reporter R(OS, Ctx, PP, Includes, PI, File);505  const auto& SM = Ctx.getSourceManager();506  for (Decl *Root : Roots)507    walkAST(*Root, [&](SourceLocation Loc, const NamedDecl &D, RefType T) {508      if(!SM.isWrittenInMainFile(SM.getSpellingLoc(Loc)))509        return;510      R.addRef(SymbolReference{D, Loc, T});511    });512  for (const SymbolReference &Ref : MacroRefs) {513    if (!SM.isWrittenInMainFile(SM.getSpellingLoc(Ref.RefLocation)))514      continue;515    R.addRef(Ref);516  }517  R.write();518}519 520} // namespace clang::include_cleaner521