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/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 Macro
97llvm::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(Out&: SS, Policy: 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(Range&: List, P: [&](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(S: R.Sym, LO: Ctx.getLangOpts()))
175 R.Locations.push_back(Elt: Loc);
176 R.Headers = headersForSymbol(S: R.Sym, PP, PI);
177
178 for (const auto &H : R.Headers) {
179 R.Includes.append(RHS: 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(C&: R.Includes);
189 R.Includes.erase(CS: llvm::unique(R&: R.Includes), CE: R.Includes.end());
190
191 if (!R.Headers.empty())
192 R.Insert =
193 spellHeader(Input: {.H: R.Headers.front(), .HS: PP.getHeaderSearchInfo(), .Main: 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(FID: MainFile)) {}
203
204 void addRef(const SymbolReference &SR) {
205 auto [File, Offset] = SM.getDecomposedLoc(Loc: SM.getFileLoc(Loc: 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(args: Ref{.Offset: Offset, .Type: SR.RT, .Sym: SR.Target});
216 Ref &R = Refs.back();
217 fillTarget(R);
218 for (const auto *I : R.Includes)
219 IncludeRefs[I].push_back(x: RefIndex);
220 if (R.Type == RefType::Explicit && !R.Satisfied && !R.Insert.empty())
221 Insertion[R.Insert].push_back(x: 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(S: Ins.first());
233 OS << "'>";
234 writeInsertion(Text: Ins.first(), Refs: 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(R: 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: Path);
277 if (File == Path)
278 return escapeString(S: Path);
279 OS << "<span title='";
280 escapeString(S: Path);
281 OS << "'>";
282 escapeString(S: 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(S: "<invalid>");
290 if (!Loc.isMacroID())
291 return printFilename(Path: Loc.printToString(SM));
292
293 // Replicating printToString() is a bit simpler than parsing/reformatting.
294 printFilename(Path: SM.getExpansionLoc(Loc).printToString(SM));
295 OS << " &lt;Spelling=";
296 printFilename(Path: 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(Key: R.Sym, Args&: 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(Range&: Sorted, C: 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(Sym: S);
319 if (!Details.empty()) {
320 OS << "<span class='" << refType(T: R.Type) << "' title='";
321 escapeString(S: Details);
322 OS << "'>";
323 }
324 escapeString(S: llvm::to_string(Value: S));
325 if (!Details.empty())
326 OS << "</span>";
327
328 unsigned Line = SM.getLineNumber(FID: MainFile, FilePos: 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(S: Inc.Resolved->getName());
339 OS << "</td></tr>\n";
340 writeProvides(RefIndices: 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(RefIndices: Refs);
348 OS << "</table>";
349 }
350
351 void writeTarget(const Ref &R) {
352 OS << "<table class='target " << refType(T: R.Type) << "'>";
353
354 OS << "<tr><th>Symbol</th><td>";
355 OS << describeSymbol(Sym: R.Sym) << " <code>";
356 escapeString(S: llvm::to_string(Value: R.Sym));
357 OS << "</code></td></tr>\n";
358
359 std::string Details = printDetails(Sym: R.Sym);
360 if (!Details.empty()) {
361 OS << "<tr><td></td><td><code>";
362 escapeString(S: 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: Loc.physical());
370 else
371 escapeString(S: llvm::to_string(Value: 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(Path: 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(S: 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(S: 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(S: R.Insert);
402 OS << "</td></tr>";
403 }
404
405 OS << "</table>";
406 }
407
408 void writeCode() {
409 llvm::StringRef Code = SM.getBufferData(FID: 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(C&: Insertions);
416 for (llvm::StringRef Insertion : Insertions) {
417 OS << "<code class='line added'>"
418 << "<span class='inc sel inserted' data-hover='i";
419 escapeString(S: Insertion);
420 OS << "'>#include ";
421 escapeString(S: 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(OneBasedIndex: LineNum)))
432 OS << "<span class='inc sel " << includeType(I: 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(first: RefOrder.begin(), last: RefOrder.end(), value: 0);
443 llvm::stable_sort(Range&: RefOrder, C: [&](unsigned A, unsigned B) {
444 return std::make_pair(x&: Refs[A].Offset, y: Refs[A].Type != RefType::Implicit) <
445 std::make_pair(x&: Refs[B].Offset, y: 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(Pred: [&](unsigned RefIndex) {
469 const Ref &R = Refs[RefIndex];
470 if (R.Offset != I)
471 return false;
472 if (!TargetList.empty())
473 TargetList.push_back(c: ',');
474 TargetList.push_back(c: 't');
475 TargetList.append(str: std::to_string(val: 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(Loc: SM.getComposedLoc(FID: MainFile, Offset: I), SM,
484 LangOpts: Ctx.getLangOpts());
485 }
486 if (Code[I] == '\n') {
487 EndLine();
488 StartLine();
489 } else
490 escapeChar(C: Code[I]);
491 }
492 EndLine();
493 OS << "</pre>\n";
494 }
495};
496
497} // namespace
498
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&: *Root, [&](SourceLocation Loc, const NamedDecl &D, RefType T) {
508 if(!SM.isWrittenInMainFile(Loc: SM.getSpellingLoc(Loc)))
509 return;
510 R.addRef(SR: SymbolReference{D, .RefLocation: Loc, .RT: T});
511 });
512 for (const SymbolReference &Ref : MacroRefs) {
513 if (!SM.isWrittenInMainFile(Loc: SM.getSpellingLoc(Loc: Ref.RefLocation)))
514 continue;
515 R.addRef(SR: Ref);
516 }
517 R.write();
518}
519
520} // namespace clang::include_cleaner
521

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/include-cleaner/lib/HTMLReport.cpp