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