clang 24.0.0git
HTMLDiagnostics.cpp
Go to the documentation of this file.
1//===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===//
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// This file defines the HTMLDiagnostics object.
10//
11//===----------------------------------------------------------------------===//
12
13#include "HTMLDiagnostics.h"
14#include "PlistDiagnostics.h"
15#include "SarifDiagnostics.h"
16#include "clang/AST/Decl.h"
17#include "clang/AST/DeclBase.h"
18#include "clang/AST/Stmt.h"
22#include "clang/Basic/LLVM.h"
26#include "clang/Lex/Lexer.h"
28#include "clang/Lex/Token.h"
32#include "llvm/ADT/RewriteBuffer.h"
33#include "llvm/ADT/STLExtras.h"
34#include "llvm/ADT/Sequence.h"
35#include "llvm/ADT/SmallString.h"
36#include "llvm/ADT/StringRef.h"
37#include "llvm/ADT/iterator_range.h"
38#include "llvm/Support/Errc.h"
39#include "llvm/Support/ErrorHandling.h"
40#include "llvm/Support/FileSystem.h"
41#include "llvm/Support/IOSandbox.h"
42#include "llvm/Support/Path.h"
43#include "llvm/Support/raw_ostream.h"
44#include <cassert>
45#include <map>
46#include <memory>
47#include <set>
48#include <string>
49#include <system_error>
50#include <utility>
51#include <vector>
52
53using namespace clang;
54using namespace ento;
55using llvm::RewriteBuffer;
56
57//===----------------------------------------------------------------------===//
58// Boilerplate.
59//===----------------------------------------------------------------------===//
60
61namespace {
62
63class ArrowMap;
64
65class HTMLDiagnostics : public PathDiagnosticConsumer {
66 PathDiagnosticConsumerOptions DiagOpts;
67 std::string Directory;
68 bool createdDir = false;
69 bool noDir = false;
70 const Preprocessor &PP;
71 const bool SupportsCrossFileDiagnostics;
72 llvm::StringSet<> EmittedHashes;
73 html::RelexRewriteCacheRef RewriterCache =
75
76public:
77 HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,
78 const std::string &OutputDir, const Preprocessor &pp,
79 bool supportsMultipleFiles)
80 : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),
81 SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
82
83 ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
84
85 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
86 FilesMade *filesMade) override;
87
88 StringRef getName() const override { return HTML_DIAGNOSTICS_NAME; }
89
90 bool supportsCrossFileDiagnostics() const override {
91 return SupportsCrossFileDiagnostics;
92 }
93
94 unsigned ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece &P,
95 unsigned num);
96
97 unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID,
98 const PathDiagnosticControlFlowPiece &P,
99 unsigned Number);
100
101 void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P,
102 const std::vector<SourceRange> &PopUpRanges, unsigned num,
103 unsigned max);
104
105 void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range,
106 const char *HighlightStart = "<span class=\"mrange\">",
107 const char *HighlightEnd = "</span>");
108
109 void ReportDiag(const PathDiagnostic &D, FilesMade *filesMade);
110
111 // Generate the full HTML report
112 std::string GenerateHTML(const PathDiagnostic &D, Rewriter &R,
113 const SourceManager &SMgr, const PathPieces &path,
114 const char *declName);
115
116 // Add HTML header/footers to file specified by FID
117 void FinalizeHTML(const PathDiagnostic &D, Rewriter &R,
118 const SourceManager &SMgr, const PathPieces &path,
119 FileID FID, FileEntryRef Entry, const char *declName);
120
121 // Rewrite the file specified by FID with HTML formatting.
122 void RewriteFile(Rewriter &R, const PathPieces &path, FileID FID);
123
124 PathGenerationScheme getGenerationScheme() const override {
125 return Everything;
126 }
127
128private:
129 void addArrowSVGs(Rewriter &R, FileID BugFileID,
130 const ArrowMap &ArrowIndices);
131
132 /// \return Javascript for displaying shortcuts help;
133 StringRef showHelpJavascript();
134
135 /// \return Javascript for navigating the HTML report using j/k keys.
136 StringRef generateKeyboardNavigationJavascript();
137
138 /// \return Javascript for drawing control-flow arrows.
139 StringRef generateArrowDrawingJavascript();
140
141 /// \return JavaScript for an option to only show relevant lines.
142 std::string showRelevantLinesJavascript(const PathDiagnostic &D,
143 const PathPieces &path);
144
145 /// Write executed lines from \p D in JSON format into \p os.
146 void dumpCoverageData(const PathDiagnostic &D, const PathPieces &path,
147 llvm::raw_string_ostream &os);
148};
149
150bool isArrowPiece(const PathDiagnosticPiece &P) {
151 return isa<PathDiagnosticControlFlowPiece>(P) && P.getString().empty();
152}
153
154unsigned getPathSizeWithoutArrows(const PathPieces &Path) {
155 unsigned TotalPieces = Path.size();
156 unsigned TotalArrowPieces = llvm::count_if(
157 Path, [](const PathDiagnosticPieceRef &P) { return isArrowPiece(*P); });
158 return TotalPieces - TotalArrowPieces;
159}
160
161class ArrowMap : public std::vector<unsigned> {
162 using Base = std::vector<unsigned>;
163
164public:
165 ArrowMap(unsigned Size) : Base(Size, 0) {}
166 unsigned getTotalNumberOfArrows() const { return at(0); }
167};
168
169llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ArrowMap &Indices) {
170 OS << "[ ";
171 llvm::interleave(Indices, OS, ",");
172 return OS << " ]";
173}
174
175} // namespace
176
177/// Creates and registers an HTML diagnostic consumer, without any additional
178/// text consumer.
181 const std::string &OutputDir, const Preprocessor &PP,
182 bool SupportMultipleFiles) {
183
184 // TODO: Emit an error here.
185 if (OutputDir.empty())
186 return;
187
188 C.emplace_back(std::make_unique<HTMLDiagnostics>(
189 std::move(DiagOpts), OutputDir, PP, SupportMultipleFiles));
190}
191
192void ento::createHTMLDiagnosticConsumer(
194 const std::string &OutputDir, const Preprocessor &PP,
196 const MacroExpansionContext &MacroExpansions) {
197
198 // FIXME: HTML is currently our default output type, but if the output
199 // directory isn't specified, it acts like if it was in the minimal text
200 // output mode. This doesn't make much sense, we should have the minimal text
201 // as our default. In the case of backward compatibility concerns, this could
202 // be preserved with -analyzer-config-compatibility-mode=true.
203 createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
204 MacroExpansions);
205
206 createHTMLDiagnosticConsumerImpl(DiagOpts, C, OutputDir, PP,
207 /*SupportMultipleFiles=*/true);
208}
209
210void ento::createHTMLSingleFileDiagnosticConsumer(
212 const std::string &OutputDir, const Preprocessor &PP,
214 const clang::MacroExpansionContext &MacroExpansions) {
215 createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
216 MacroExpansions);
217
218 createHTMLDiagnosticConsumerImpl(DiagOpts, C, OutputDir, PP,
219 /*SupportMultipleFiles=*/false);
220}
221
222void ento::createPlistHTMLDiagnosticConsumer(
224 const std::string &prefix, const Preprocessor &PP,
226 const MacroExpansionContext &MacroExpansions) {
228 DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, true);
229 createPlistDiagnosticConsumerImpl(DiagOpts, C, prefix, PP, CTU,
230 MacroExpansions, true);
231 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP,
232 CTU, MacroExpansions);
233}
234
235void ento::createSarifHTMLDiagnosticConsumer(
237 const std::string &sarif_file, const Preprocessor &PP,
239 const MacroExpansionContext &MacroExpansions) {
241 DiagOpts, C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,
242 true);
243 createSarifDiagnosticConsumerImpl(DiagOpts, C, sarif_file, PP);
244
245 createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, sarif_file,
246 PP, CTU, MacroExpansions);
247}
248
249//===----------------------------------------------------------------------===//
250// Report processing.
251//===----------------------------------------------------------------------===//
252
253void HTMLDiagnostics::FlushDiagnosticsImpl(
254 std::vector<const PathDiagnostic *> &Diags,
255 FilesMade *filesMade) {
256 for (const auto Diag : Diags)
257 ReportDiag(*Diag, filesMade);
258}
259
260void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
261 FilesMade *filesMade) {
262 // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
263 auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
264
265 // Create the HTML directory if it is missing.
266 if (!createdDir) {
267 createdDir = true;
268 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
269 llvm::errs() << "warning: could not create directory '"
270 << Directory << "': " << ec.message() << '\n';
271 noDir = true;
272 return;
273 }
274 }
275
276 if (noDir)
277 return;
278
279 // First flatten out the entire path to make it easier to use.
280 PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
281
282 // The path as already been prechecked that the path is non-empty.
283 assert(!path.empty());
284 const SourceManager &SMgr = path.front()->getLocation().getManager();
285
286 // Create a new rewriter to generate HTML.
287 Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
288
289 // Get the function/method name
290 SmallString<128> declName("unknown");
291 int offsetDecl = 0;
292 if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
293 if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
294 declName = ND->getDeclName().getAsString();
295
296 if (const Stmt *Body = DeclWithIssue->getBody()) {
297 // Retrieve the relative position of the declaration which will be used
298 // for the file name
299 FullSourceLoc L(
300 SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),
301 SMgr);
302 FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);
303 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
304 }
305 }
306
307 SmallString<32> IssueHash =
309 auto [It, IsNew] = EmittedHashes.insert(IssueHash);
310 if (!IsNew) {
311 // We've already emitted a duplicate issue. It'll get overwritten anyway.
312 return;
313 }
314
315 std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
316 if (report.empty()) {
317 llvm::errs() << "warning: no diagnostics generated for main file.\n";
318 return;
319 }
320
321 // Create a path for the target HTML file.
322 int FD;
323
324 SmallString<128> FileNameStr;
325 llvm::raw_svector_ostream FileName(FileNameStr);
326 FileName << "report-";
327
328 // Historically, neither the stable report filename nor the unstable report
329 // filename were actually stable. That said, the stable report filename
330 // was more stable because it was mostly composed of information
331 // about the bug report instead of being completely random.
332 // Now both stable and unstable report filenames are in fact stable
333 // but the stable report filename is still more verbose.
335 // FIXME: This code relies on knowing what constitutes the issue hash.
336 // Otherwise deduplication won't work correctly.
337 FileID ReportFile =
338 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
339
340 OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(ReportFile);
341
342 FileName << llvm::sys::path::filename(Entry->getName()).str() << "-"
343 << declName.c_str() << "-" << offsetDecl << "-";
344 }
345
346 FileName << StringRef(IssueHash).substr(0, 6).str() << ".html";
347
348 SmallString<128> ResultPath;
349 llvm::sys::path::append(ResultPath, Directory, FileName.str());
350 if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) {
351 llvm::errs() << "warning: could not make '" << ResultPath
352 << "' absolute: " << EC.message() << '\n';
353 return;
354 }
355
356 if (std::error_code EC = llvm::sys::fs::openFileForReadWrite(
357 ResultPath, FD, llvm::sys::fs::CD_CreateNew,
358 llvm::sys::fs::OF_Text)) {
359 // Existence of the file corresponds to the situation where a different
360 // Clang instance has emitted a bug report with the same issue hash.
361 // This is an entirely normal situation that does not deserve a warning,
362 // as apart from hash collisions this can happen because the reports
363 // are in fact similar enough to be considered duplicates of each other.
364 if (EC != llvm::errc::file_exists) {
365 llvm::errs() << "warning: could not create file in '" << Directory
366 << "': " << EC.message() << '\n';
367 } else if (filesMade) {
368 // Record that we created the file so that it gets referenced in the
369 // plist and SARIF reports for every translation unit that found the
370 // issue.
371 filesMade->addDiagnostic(D, getName(),
372 llvm::sys::path::filename(ResultPath));
373 }
374 return;
375 }
376
377 llvm::raw_fd_ostream os(FD, true);
378
379 if (filesMade)
380 filesMade->addDiagnostic(D, getName(),
381 llvm::sys::path::filename(ResultPath));
382
383 // Emit the HTML to disk.
384 os << report;
385}
386
387std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
388 const SourceManager& SMgr, const PathPieces& path, const char *declName) {
389 // Rewrite source files as HTML for every new file the path crosses
390 std::vector<FileID> FileIDs;
391 for (auto I : path) {
392 FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
393 if (llvm::is_contained(FileIDs, FID))
394 continue;
395
396 FileIDs.push_back(FID);
397 RewriteFile(R, path, FID);
398 }
399
400 if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
401 // Prefix file names, anchor tags, and nav cursors to every file
402 for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
403 std::string s;
404 llvm::raw_string_ostream os(s);
405
406 if (I != FileIDs.begin())
407 os << "<hr class=divider>\n";
408
409 os << "<div id=File" << I->getHashValue() << ">\n";
410
411 // Left nav arrow
412 if (I != FileIDs.begin())
413 os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
414 << "\">&#x2190;</a></div>";
415
416 os << "<h4 class=FileName>" << SMgr.getFileEntryRefForID(*I)->getName()
417 << "</h4>\n";
418
419 // Right nav arrow
420 if (I + 1 != E)
421 os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
422 << "\">&#x2192;</a></div>";
423
424 os << "</div>\n";
425
426 R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str());
427 }
428
429 // Append files to the main report file in the order they appear in the path
430 for (auto I : llvm::drop_begin(FileIDs)) {
431 std::string s;
432 llvm::raw_string_ostream os(s);
433
434 const RewriteBuffer *Buf = R.getRewriteBufferFor(I);
435 for (auto BI : *Buf)
436 os << BI;
437
438 R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str());
439 }
440 }
441
442 const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]);
443 if (!Buf)
444 return {};
445
446 // Add CSS, header, and footer.
447 FileID FID =
448 path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
450 FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName);
451
452 std::string file;
453 llvm::raw_string_ostream os(file);
454 for (auto BI : *Buf)
455 os << BI;
456
457 return file;
458}
459
460void HTMLDiagnostics::dumpCoverageData(
461 const PathDiagnostic &D,
462 const PathPieces &path,
463 llvm::raw_string_ostream &os) {
464
465 const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines();
466
467 os << "var relevant_lines = {";
468 for (auto I = ExecutedLines.begin(),
469 E = ExecutedLines.end(); I != E; ++I) {
470 if (I != ExecutedLines.begin())
471 os << ", ";
472
473 os << "\"" << I->first.getHashValue() << "\": {";
474 for (unsigned LineNo : I->second) {
475 if (LineNo != *(I->second.begin()))
476 os << ", ";
477
478 os << "\"" << LineNo << "\": 1";
479 }
480 os << "}";
481 }
482
483 os << "};";
484}
485
486std::string HTMLDiagnostics::showRelevantLinesJavascript(
487 const PathDiagnostic &D, const PathPieces &path) {
488 std::string s;
489 llvm::raw_string_ostream os(s);
490 os << "<script type='text/javascript'>\n";
491 dumpCoverageData(D, path, os);
492 os << R"<<<(
493
494var filterCounterexample = function (hide) {
495 var tables = document.getElementsByClassName("code");
496 for (var t=0; t<tables.length; t++) {
497 var table = tables[t];
498 var file_id = table.getAttribute("data-fileid");
499 var lines_in_fid = relevant_lines[file_id];
500 if (!lines_in_fid) {
501 lines_in_fid = {};
502 }
503 var lines = table.getElementsByClassName("codeline");
504 for (var i=0; i<lines.length; i++) {
505 var el = lines[i];
506 var lineNo = el.getAttribute("data-linenumber");
507 if (!lines_in_fid[lineNo]) {
508 if (hide) {
509 el.setAttribute("hidden", "");
510 } else {
511 el.removeAttribute("hidden");
512 }
513 }
514 }
515 }
516}
517
518window.addEventListener("keydown", function (event) {
519 if (event.defaultPrevented) {
520 return;
521 }
522 // SHIFT + S
523 if (event.shiftKey && event.keyCode == 83) {
524 var checked = document.getElementsByName("showCounterexample")[0].checked;
525 filterCounterexample(!checked);
526 document.getElementsByName("showCounterexample")[0].click();
527 } else {
528 return;
529 }
530 event.preventDefault();
531}, true);
532
533document.addEventListener("DOMContentLoaded", function() {
534 document.querySelector('input[name="showCounterexample"]').onchange=
535 function (event) {
536 filterCounterexample(this.checked);
537 };
538});
539</script>
540
541<form>
542 <input type="checkbox" name="showCounterexample" id="showCounterexample" />
543 <label for="showCounterexample">
544 Show only relevant lines
545 </label>
546 <input type="checkbox" name="showArrows"
547 id="showArrows" style="margin-left: 10px" />
548 <label for="showArrows">
549 Show control flow arrows
550 </label>
551</form>
552)<<<";
553
554 return s;
555}
556
557void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic &D, Rewriter &R,
558 const SourceManager &SMgr,
559 const PathPieces &path, FileID FID,
560 FileEntryRef Entry, const char *declName) {
561 // This is a cludge; basically we want to append either the full
562 // working directory if we have no directory information. This is
563 // a work in progress.
564
565 llvm::SmallString<0> DirName;
566
567 if (llvm::sys::path::is_relative(Entry.getName())) {
568 llvm::sys::fs::current_path(DirName);
569 DirName += '/';
570 }
571
572 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
573 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
574
575 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript());
576
577 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
578 generateKeyboardNavigationJavascript());
579
580 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
581 generateArrowDrawingJavascript());
582
583 // Checkbox and javascript for filtering the output to the counterexample.
584 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
585 showRelevantLinesJavascript(D, path));
586
587 // Add the name of the file as an <h1> tag.
588 {
589 std::string s;
590 llvm::raw_string_ostream os(s);
591
592 os << "<!-- REPORTHEADER -->\n"
593 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
594 "<tr><td class=\"rowname\">File:</td><td>"
595 << html::EscapeText(DirName)
596 << html::EscapeText(Entry.getName())
597 << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
598 "<a href=\"#EndPath\">line "
599 << LineNumber
600 << ", column "
601 << ColumnNumber
602 << "</a><br />"
603 << D.getVerboseDescription() << "</td></tr>\n";
604
605 // The navigation across the extra notes pieces.
606 unsigned NumExtraPieces = 0;
607 for (const auto &Piece : path) {
608 if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
609 int LineNumber =
611 int ColumnNumber =
613 ++NumExtraPieces;
614 os << "<tr><td class=\"rowname\">Note:</td><td>"
615 << "<a href=\"#Note" << NumExtraPieces << "\">line "
616 << LineNumber << ", column " << ColumnNumber << "</a><br />"
617 << P->getString() << "</td></tr>";
618 }
619 }
620
621 // Output any other meta data.
622
623 for (const std::string &Metadata :
624 llvm::make_range(D.meta_begin(), D.meta_end())) {
625 os << "<tr><td></td><td>" << html::EscapeText(Metadata) << "</td></tr>\n";
626 }
627
628 os << R"<<<(
629</table>
630<!-- REPORTSUMMARYEXTRA -->
631<h3>Annotated Source Code</h3>
632<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
633 to see keyboard shortcuts</p>
634<input type="checkbox" class="spoilerhider" id="showinvocation" />
635<label for="showinvocation" >Show analyzer invocation</label>
636<div class="spoiler">clang -cc1 )<<<";
637 os << html::EscapeText(DiagOpts.ToolInvocation);
638 os << R"<<<(
639</div>
640<div id='tooltiphint' hidden="true">
641 <p>Keyboard shortcuts: </p>
642 <ul>
643 <li>Use 'j/k' keys for keyboard navigation</li>
644 <li>Use 'Shift+S' to show/hide relevant lines</li>
645 <li>Use '?' to toggle this window</li>
646 </ul>
647 <a href="#" onclick="toggleHelp(); return false;">Close</a>
648</div>
649)<<<";
650
651 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
652 }
653
654 // Embed meta-data tags.
655 {
656 std::string s;
657 llvm::raw_string_ostream os(s);
658
659 StringRef BugDesc = D.getVerboseDescription();
660 if (!BugDesc.empty())
661 os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
662
663 StringRef BugType = D.getBugType();
664 if (!BugType.empty())
665 os << "\n<!-- BUGTYPE " << BugType << " -->\n";
666
667 PathDiagnosticLocation UPDLoc = D.getUniqueingLoc();
668 FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()
669 ? UPDLoc.asLocation()
670 : D.getLocation().asLocation()),
671 SMgr);
672
673 StringRef BugCategory = D.getCategory();
674 if (!BugCategory.empty())
675 os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
676
677 os << "\n<!-- BUGFILE " << DirName << Entry.getName() << " -->\n";
678
679 os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry.getName()) << " -->\n";
680
681 os << "\n<!-- FUNCTIONNAME " << declName << " -->\n";
682
683 os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT "
684 << D.getIssueHash(PP.getSourceManager(), PP.getLangOpts()) << " -->\n";
685
686 os << "\n<!-- BUGLINE "
687 << LineNumber
688 << " -->\n";
689
690 os << "\n<!-- BUGCOLUMN "
691 << ColumnNumber
692 << " -->\n";
693
694 os << "\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) << " -->\n";
695
696 // Mark the end of the tags.
697 os << "\n<!-- BUGMETAEND -->\n";
698
699 // Insert the text.
700 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
701 }
702
704}
705
706StringRef HTMLDiagnostics::showHelpJavascript() {
707 return R"<<<(
708<script type='text/javascript'>
709
710var toggleHelp = function() {
711 var hint = document.querySelector("#tooltiphint");
712 var attributeName = "hidden";
713 if (hint.hasAttribute(attributeName)) {
714 hint.removeAttribute(attributeName);
715 } else {
716 hint.setAttribute("hidden", "true");
717 }
718};
719window.addEventListener("keydown", function (event) {
720 if (event.defaultPrevented) {
721 return;
722 }
723 if (event.key == "?") {
724 toggleHelp();
725 } else {
726 return;
727 }
728 event.preventDefault();
729});
730</script>
731)<<<";
732}
733
734static bool shouldDisplayPopUpRange(const SourceRange &Range) {
735 return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID());
736}
737
738static void
739HandlePopUpPieceStartTag(Rewriter &R,
740 const std::vector<SourceRange> &PopUpRanges) {
741 for (const auto &Range : PopUpRanges) {
742 if (!shouldDisplayPopUpRange(Range))
743 continue;
744
745 html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "",
746 "<table class='variable_popup'><tbody>",
747 /*IsTokenRange=*/true);
748 }
749}
750
751static void HandlePopUpPieceEndTag(Rewriter &R,
752 const PathDiagnosticPopUpPiece &Piece,
753 std::vector<SourceRange> &PopUpRanges,
754 unsigned int LastReportedPieceIndex,
755 unsigned int PopUpPieceIndex) {
756 SmallString<256> Buf;
757 llvm::raw_svector_ostream Out(Buf);
758
759 SourceRange Range(Piece.getLocation().asRange());
760 if (!shouldDisplayPopUpRange(Range))
761 return;
762
763 // Write out the path indices with a right arrow and the message as a row.
764 Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
765 << LastReportedPieceIndex;
766
767 // Also annotate the state transition with extra indices.
768 Out << '.' << PopUpPieceIndex;
769
770 Out << "</div></td><td>" << Piece.getString() << "</td></tr>";
771
772 // If no report made at this range mark the variable and add the end tags.
773 if (!llvm::is_contained(PopUpRanges, Range)) {
774 // Store that we create a report at this range.
775 PopUpRanges.push_back(Range);
776
777 Out << "</tbody></table></span>";
778 html::HighlightRange(R, Range.getBegin(), Range.getEnd(),
779 "<span class='variable'>", Buf.c_str(),
780 /*IsTokenRange=*/true);
781 } else {
782 // Otherwise inject just the new row at the end of the range.
783 html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(),
784 /*IsTokenRange=*/true);
785 }
786}
787
788void HTMLDiagnostics::RewriteFile(Rewriter &R, const PathPieces &path,
789 FileID FID) {
790 // Add line numbers first, so that tags inserted later at end-of-line
791 // offsets (e.g. pop-up closing tags) end up inside the row.
792 html::EscapeText(R, FID);
793 html::AddLineNumbers(R, FID);
794
795 // Process the path.
796 // Maintain the counts of extra note pieces separately.
797 unsigned TotalPieces = getPathSizeWithoutArrows(path);
798 unsigned TotalNotePieces =
799 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
801 });
802 unsigned PopUpPieceCount =
803 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
805 });
806
807 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
808 unsigned NumRegularPieces = TotalRegularPieces;
809 unsigned NumNotePieces = TotalNotePieces;
810 unsigned NumberOfArrows = 0;
811 // Stores the count of the regular piece indices.
812 std::map<int, int> IndexMap;
813 ArrowMap ArrowIndices(TotalRegularPieces + 1);
814
815 // Stores the different ranges where we have reported something.
816 std::vector<SourceRange> PopUpRanges;
817 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
818 const auto &Piece = *I.get();
819
821 ++IndexMap[NumRegularPieces];
822 } else if (isa<PathDiagnosticNotePiece>(Piece)) {
823 // This adds diagnostic bubbles, but not navigation.
824 // Navigation through note pieces would be added later,
825 // as a separate pass through the piece list.
826 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
827 --NumNotePieces;
828
829 } else if (isArrowPiece(Piece)) {
830 NumberOfArrows = ProcessControlFlowPiece(
831 R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows);
832 ArrowIndices[NumRegularPieces] = NumberOfArrows;
833
834 } else {
835 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
836 TotalRegularPieces);
837 --NumRegularPieces;
838 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
839 }
840 }
841 ArrowIndices[0] = NumberOfArrows;
842
843 // At this point ArrowIndices represent the following data structure:
844 // [a_0, a_1, ..., a_N]
845 // where N is the number of events in the path.
846 //
847 // Then for every event with index i \in [0, N - 1], we can say that
848 // arrows with indices \in [a_(i+1), a_i) correspond to that event.
849 // We can say that because arrows with these indices appeared in the
850 // path in between the i-th and the (i+1)-th events.
851 assert(ArrowIndices.back() == 0 &&
852 "No arrows should be after the last event");
853 // This assertion also guarantees that all indices in are <= NumberOfArrows.
854 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
855 "Incorrect arrow indices map");
856
857 // Secondary indexing if we are having multiple pop-ups between two notes.
858 // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...)
859 NumRegularPieces = TotalRegularPieces;
860 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
861 const auto &Piece = *I.get();
862
863 if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
864 int PopUpPieceIndex = IndexMap[NumRegularPieces];
865
866 // Pop-up pieces needs the index of the last reported piece and its count
867 // how many times we report to handle multiple reports on the same range.
868 // This marks the variable, adds the </table> end tag and the message
869 // (list element) as a row. The <table> start tag will be added after the
870 // rows has been written out. Note: It stores every different range.
871 HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces,
872 PopUpPieceIndex);
873
874 if (PopUpPieceIndex > 0)
875 --IndexMap[NumRegularPieces];
876
877 } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) {
878 --NumRegularPieces;
879 }
880 }
881
882 // Add the <table> start tag of pop-up pieces based on the stored ranges.
883 HandlePopUpPieceStartTag(R, PopUpRanges);
884
885 addArrowSVGs(R, FID, ArrowIndices);
886
887 // If we have a preprocessor, relex the file and syntax highlight.
888 // We might not have a preprocessor if we come from a deserialized AST file,
889 // for example.
890 html::SyntaxHighlight(R, FID, PP, RewriterCache);
891 html::HighlightMacros(R, FID, PP, RewriterCache);
892}
893
894void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
895 const PathDiagnosticPiece &P,
896 const std::vector<SourceRange> &PopUpRanges,
897 unsigned num, unsigned max) {
898 // For now, just draw a box above the line in question, and emit the
899 // warning.
900 FullSourceLoc Pos = P.getLocation().asLocation();
901
902 if (!Pos.isValid())
903 return;
904
905 SourceManager &SM = R.getSourceMgr();
906 assert(&Pos.getManager() == &SM && "SourceManagers are different!");
908
909 if (LPosInfo.first != BugFileID)
910 return;
911
912 llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first);
913 const char *FileStart = Buf.getBufferStart();
914
915 // Compute the column number. Rewind from the current position to the start
916 // of the line.
917 unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
918 const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData();
919 const char *LineStart = TokInstantiationPtr-ColNo;
920
921 // Compute LineEnd.
922 const char *LineEnd = TokInstantiationPtr;
923 const char *FileEnd = Buf.getBufferEnd();
924 while (*LineEnd != '\n' && LineEnd != FileEnd)
925 ++LineEnd;
926
927 // Compute the margin offset by counting tabs and non-tabs.
928 unsigned PosNo = 0;
929 for (const char* c = LineStart; c != TokInstantiationPtr; ++c)
930 PosNo += *c == '\t' ? 8 : 1;
931
932 // Create the html for the message.
933
934 const char *Kind = nullptr;
935 bool IsNote = false;
936 bool SuppressIndex = (max == 1);
937 switch (P.getKind()) {
938 case PathDiagnosticPiece::Event: Kind = "Event"; break;
939 case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break;
940 // Setting Kind to "Control" is intentional.
941 case PathDiagnosticPiece::Macro: Kind = "Control"; break;
943 Kind = "Note";
944 IsNote = true;
945 SuppressIndex = true;
946 break;
949 llvm_unreachable("Calls and extra notes should already be handled");
950 }
951
952 std::string sbuf;
953 llvm::raw_string_ostream os(sbuf);
954
955 os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
956
957 if (IsNote)
958 os << "Note" << num;
959 else if (num == max)
960 os << "EndPath";
961 else
962 os << "Path" << num;
963
964 os << "\" class=\"msg";
965 if (Kind)
966 os << " msg" << Kind;
967 os << "\" style=\"margin-left:" << PosNo << "ex";
968
969 // Output a maximum size.
971 // Get the string and determining its maximum substring.
972 const auto &Msg = P.getString();
973 unsigned max_token = 0;
974 unsigned cnt = 0;
975 unsigned len = Msg.size();
976
977 for (char C : Msg)
978 switch (C) {
979 default:
980 ++cnt;
981 continue;
982 case ' ':
983 case '\t':
984 case '\n':
985 if (cnt > max_token) max_token = cnt;
986 cnt = 0;
987 }
988
989 if (cnt > max_token)
990 max_token = cnt;
991
992 // Determine the approximate size of the message bubble in em.
993 unsigned em;
994 const unsigned max_line = 120;
995
996 if (max_token >= max_line)
997 em = max_token / 2;
998 else {
999 unsigned characters = max_line;
1000 unsigned lines = len / max_line;
1001
1002 if (lines > 0) {
1003 for (; characters > max_token; --characters)
1004 if (len / characters > lines) {
1005 ++characters;
1006 break;
1007 }
1008 }
1009
1010 em = characters / 2;
1011 }
1012
1013 if (em < max_line/2)
1014 os << "; max-width:" << em << "em";
1015 }
1016 else
1017 os << "; max-width:100em";
1019 os << "\">";
1020
1021 if (!SuppressIndex) {
1022 os << "<table class=\"msgT\"><tr><td valign=\"top\">";
1023 os << "<div class=\"PathIndex";
1024 if (Kind) os << " PathIndex" << Kind;
1025 os << "\">" << num << "</div>";
1026
1027 if (num > 1) {
1028 os << "</td><td><div class=\"PathNav\"><a href=\"#Path"
1029 << (num - 1)
1030 << "\" title=\"Previous event ("
1031 << (num - 1)
1032 << ")\">&#x2190;</a></div>";
1033 }
1034
1035 os << "</td><td>";
1036 }
1037
1038 if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
1039 os << "Within the expansion of the macro '";
1040
1041 // Get the name of the macro by relexing it.
1042 {
1043 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
1044 assert(L.isFileID());
1045 StringRef BufferInfo = L.getBufferData();
1046 FileIDAndOffset LocInfo = L.getDecomposedLoc();
1047 const char* MacroName = LocInfo.second + BufferInfo.data();
1048 Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(),
1049 BufferInfo.begin(), MacroName, BufferInfo.end());
1050
1051 Token TheTok;
1052 rawLexer.LexFromRawLexer(TheTok);
1053 for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i)
1054 os << MacroName[i];
1055 }
1056
1057 os << "':\n";
1058
1059 if (!SuppressIndex) {
1060 os << "</td>";
1061 if (num < max) {
1062 os << "<td><div class=\"PathNav\"><a href=\"#";
1063 if (num == max - 1)
1064 os << "EndPath";
1065 else
1066 os << "Path" << (num + 1);
1067 os << "\" title=\"Next event ("
1068 << (num + 1)
1069 << ")\">&#x2192;</a></div></td>";
1070 }
1072 os << "</tr></table>";
1073 }
1074
1075 // Within a macro piece. Write out each event.
1076 ProcessMacroPiece(os, *MP, 0);
1077 }
1078 else {
1080
1081 if (!SuppressIndex) {
1082 os << "</td>";
1083 if (num < max) {
1084 os << "<td><div class=\"PathNav\"><a href=\"#";
1085 if (num == max - 1)
1086 os << "EndPath";
1087 else
1088 os << "Path" << (num + 1);
1089 os << "\" title=\"Next event ("
1090 << (num + 1)
1091 << ")\">&#x2192;</a></div></td>";
1092 }
1093
1094 os << "</tr></table>";
1095 }
1096 }
1097
1098 os << "</div></td></tr>";
1099
1100 // Insert the new html after the newline, so that the bubble's row lands
1101 // between the current line's row and the next line's row.
1102 unsigned DisplayPos = LineEnd - FileStart;
1103 if (LineEnd != FileEnd)
1104 ++DisplayPos;
1105 SourceLocation Loc =
1106 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
1107
1108 R.InsertTextBefore(Loc, os.str());
1109
1110 // Now highlight the ranges.
1111 ArrayRef<SourceRange> Ranges = P.getRanges();
1112 for (const auto &Range : Ranges) {
1113 // If we have already highlighted the range as a pop-up there is no work.
1114 if (llvm::is_contained(PopUpRanges, Range))
1115 continue;
1116
1117 HighlightRange(R, LPosInfo.first, Range);
1118 }
1119}
1120
1121static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
1122 unsigned x = n % ('z' - 'a');
1123 n /= 'z' - 'a';
1124
1125 if (n > 0)
1126 EmitAlphaCounter(os, n);
1127
1128 os << char('a' + x);
1129}
1130
1131unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1132 const PathDiagnosticMacroPiece& P,
1133 unsigned num) {
1134 for (const auto &subPiece : P.subPieces) {
1135 if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1136 num = ProcessMacroPiece(os, *MP, num);
1137 continue;
1138 }
1139
1140 if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1141 os << "<div class=\"msg msgEvent\" style=\"width:94%; "
1142 "margin-left:5px\">"
1143 "<table class=\"msgT\"><tr>"
1144 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1145 EmitAlphaCounter(os, num++);
1146 os << "</div></td><td valign=\"top\">"
1147 << html::EscapeText(EP->getString())
1148 << "</td></tr></table></div>\n";
1149 }
1150 }
1151
1152 return num;
1153}
1154
1155void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1156 const ArrowMap &ArrowIndices) {
1157 std::string S;
1158 llvm::raw_string_ostream OS(S);
1159
1160 OS << R"<<<(
1161<style type="text/css">
1162 svg {
1163 position:absolute;
1164 top:0;
1165 left:0;
1166 height:100%;
1167 width:100%;
1168 pointer-events: none;
1169 overflow: visible
1170 }
1171 .arrow {
1172 stroke-opacity: 0.2;
1173 stroke-width: 1;
1174 marker-end: url(#arrowhead);
1175 }
1176
1177 .arrow.selected {
1178 stroke-opacity: 0.6;
1179 stroke-width: 2;
1180 marker-end: url(#arrowheadSelected);
1181 }
1182
1183 .arrowhead {
1184 orient: auto;
1185 stroke: none;
1186 opacity: 0.6;
1187 fill: blue;
1188 }
1189</style>
1190<svg xmlns="http://www.w3.org/2000/svg">
1191 <defs>
1192 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1193 viewBox="0 0 10 10" refX="3" refY="5"
1194 markerWidth="4" markerHeight="4">
1195 <path d="M 0 0 L 10 5 L 0 10 z" />
1196 </marker>
1197 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1198 viewBox="0 0 10 10" refX="3" refY="5"
1199 markerWidth="4" markerHeight="4">
1200 <path d="M 0 0 L 10 5 L 0 10 z" />
1201 </marker>
1202 </defs>
1203 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1204)<<<";
1205
1206 for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1207 OS << " <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n";
1208 }
1209
1210 OS << R"<<<(
1211 </g>
1212</svg>
1213<script type='text/javascript'>
1214const arrowIndices = )<<<";
1215
1216 OS << ArrowIndices << "\n</script>\n";
1217
1218 R.InsertTextBefore(R.getSourceMgr().getLocForStartOfFile(BugFileID),
1219 OS.str());
1220}
1221
1222static std::string getSpanBeginForControl(const char *ClassName,
1223 unsigned Index) {
1224 std::string Result;
1225 llvm::raw_string_ostream OS(Result);
1226 OS << "<span id=\"" << ClassName << Index << "\">";
1227 return Result;
1228}
1229
1230static std::string getSpanBeginForControlStart(unsigned Index) {
1231 return getSpanBeginForControl("start", Index);
1232}
1233
1234static std::string getSpanBeginForControlEnd(unsigned Index) {
1235 return getSpanBeginForControl("end", Index);
1236}
1237
1238unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1239 Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P,
1240 unsigned Number) {
1241 for (const PathDiagnosticLocationPair &LPair : P) {
1242 std::string Start = getSpanBeginForControlStart(Number),
1243 End = getSpanBeginForControlEnd(Number++);
1244
1245 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1246 Start.c_str());
1247 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1248 End.c_str());
1249 }
1250
1251 return Number;
1252}
1253
1254void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1255 SourceRange Range,
1256 const char *HighlightStart,
1257 const char *HighlightEnd) {
1258 SourceManager &SM = R.getSourceMgr();
1259 const LangOptions &LangOpts = R.getLangOpts();
1260
1261 std::optional<CharSourceRange> FileRange = getExpansionRangeInFile(
1262 CharSourceRange::getTokenRange(Range), BugFileID, SM);
1263 if (!FileRange)
1264 return;
1265
1266 CharSourceRange CharRange = Lexer::getAsCharRange(*FileRange, SM, LangOpts);
1267 html::HighlightRange(R, CharRange.getBegin(), CharRange.getEnd(),
1268 HighlightStart, HighlightEnd,
1269 /*IsTokenRange=*/false);
1270}
1271
1272StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1273 return R"<<<(
1274<script type='text/javascript'>
1275var digitMatcher = new RegExp("[0-9]+");
1276
1277var querySelectorAllArray = function(selector) {
1278 return Array.prototype.slice.call(
1279 document.querySelectorAll(selector));
1280}
1281
1282document.addEventListener("DOMContentLoaded", function() {
1283 querySelectorAllArray(".PathNav > a").forEach(
1284 function(currentValue, currentIndex) {
1285 var hrefValue = currentValue.getAttribute("href");
1286 currentValue.onclick = function() {
1287 scrollTo(document.querySelector(hrefValue));
1288 return false;
1289 };
1290 });
1291});
1292
1293var findNum = function() {
1294 var s = document.querySelector(".msg.selected");
1295 if (!s || s.id == "EndPath") {
1296 return 0;
1297 }
1298 var out = parseInt(digitMatcher.exec(s.id)[0]);
1299 return out;
1300};
1301
1302var classListAdd = function(el, theClass) {
1303 if(!el.className.baseVal)
1304 el.className += " " + theClass;
1305 else
1306 el.className.baseVal += " " + theClass;
1307};
1308
1309var classListRemove = function(el, theClass) {
1310 var className = (!el.className.baseVal) ?
1311 el.className : el.className.baseVal;
1312 className = className.replace(" " + theClass, "");
1313 if(!el.className.baseVal)
1314 el.className = className;
1315 else
1316 el.className.baseVal = className;
1317};
1318
1319var scrollTo = function(el) {
1320 querySelectorAllArray(".selected").forEach(function(s) {
1321 classListRemove(s, "selected");
1322 });
1323 classListAdd(el, "selected");
1324 window.scrollBy(0, el.getBoundingClientRect().top -
1325 (window.innerHeight / 2));
1326 highlightArrowsForSelectedEvent();
1327};
1328
1329var move = function(num, up, numItems) {
1330 if (num == 1 && up || num == numItems - 1 && !up) {
1331 return 0;
1332 } else if (num == 0 && up) {
1333 return numItems - 1;
1334 } else if (num == 0 && !up) {
1335 return 1 % numItems;
1336 }
1337 return up ? num - 1 : num + 1;
1338}
1339
1340var numToId = function(num) {
1341 if (num == 0) {
1342 return document.getElementById("EndPath")
1343 }
1344 return document.getElementById("Path" + num);
1345};
1346
1347var navigateTo = function(up) {
1348 var numItems = document.querySelectorAll(
1349 ".line > .msgEvent, .line > .msgControl").length;
1350 var currentSelected = findNum();
1351 var newSelected = move(currentSelected, up, numItems);
1352 var newEl = numToId(newSelected, numItems);
1353
1354 // Scroll element into center.
1355 scrollTo(newEl);
1356};
1357
1358window.addEventListener("keydown", function (event) {
1359 if (event.defaultPrevented) {
1360 return;
1361 }
1362 // key 'j'
1363 if (event.keyCode == 74) {
1364 navigateTo(/*up=*/false);
1365 // key 'k'
1366 } else if (event.keyCode == 75) {
1367 navigateTo(/*up=*/true);
1368 } else {
1369 return;
1370 }
1371 event.preventDefault();
1372}, true);
1373</script>
1374 )<<<";
1375}
1376
1377StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1378 return R"<<<(
1379<script type='text/javascript'>
1380// Return range of numbers from a range [lower, upper).
1381function range(lower, upper) {
1382 var array = [];
1383 for (var i = lower; i <= upper; ++i) {
1384 array.push(i);
1385 }
1386 return array;
1387}
1388
1389var getRelatedArrowIndices = function(pathId) {
1390 // HTML numeration of events is a bit different than it is in the path.
1391 // Everything is rotated one step to the right, so the last element
1392 // (error diagnostic) has index 0.
1393 if (pathId == 0) {
1394 // arrowIndices has at least 2 elements
1395 pathId = arrowIndices.length - 1;
1396 }
1397
1398 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1399}
1400
1401var highlightArrowsForSelectedEvent = function() {
1402 const selectedNum = findNum();
1403 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1404 arrowIndicesToHighlight.forEach((index) => {
1405 var arrow = document.querySelector("#arrow" + index);
1406 if(arrow) {
1407 classListAdd(arrow, "selected")
1408 }
1409 });
1410}
1411
1412var getAbsoluteBoundingRect = function(element) {
1413 const relative = element.getBoundingClientRect();
1414 return {
1415 left: relative.left + window.pageXOffset,
1416 right: relative.right + window.pageXOffset,
1417 top: relative.top + window.pageYOffset,
1418 bottom: relative.bottom + window.pageYOffset,
1419 height: relative.height,
1420 width: relative.width
1421 };
1422}
1423
1424var drawArrow = function(index) {
1425 // This function is based on the great answer from SO:
1426 // https://stackoverflow.com/a/39575674/11582326
1427 var start = document.querySelector("#start" + index);
1428 var end = document.querySelector("#end" + index);
1429 var arrow = document.querySelector("#arrow" + index);
1430
1431 var startRect = getAbsoluteBoundingRect(start);
1432 var endRect = getAbsoluteBoundingRect(end);
1433
1434 // It is an arrow from a token to itself, no need to visualize it.
1435 if (startRect.top == endRect.top &&
1436 startRect.left == endRect.left)
1437 return;
1438
1439 // Each arrow is a very simple Bézier curve, with two nodes and
1440 // two handles. So, we need to calculate four points in the window:
1441 // * start node
1442 var posStart = { x: 0, y: 0 };
1443 // * end node
1444 var posEnd = { x: 0, y: 0 };
1445 // * handle for the start node
1446 var startHandle = { x: 0, y: 0 };
1447 // * handle for the end node
1448 var endHandle = { x: 0, y: 0 };
1449 // One can visualize it as follows:
1450 //
1451 // start handle
1452 // /
1453 // X"""_.-""""X
1454 // .' \
1455 // / start node
1456 // |
1457 // |
1458 // | end node
1459 // \ /
1460 // `->X
1461 // X-'
1462 // \
1463 // end handle
1464 //
1465 // NOTE: (0, 0) is the top left corner of the window.
1466
1467 // We have 3 similar, but still different scenarios to cover:
1468 //
1469 // 1. Two tokens on different lines.
1470 // -xxx
1471 // /
1472 // \
1473 // -> xxx
1474 // In this situation, we draw arrow on the left curving to the left.
1475 // 2. Two tokens on the same line, and the destination is on the right.
1476 // ____
1477 // / \
1478 // / V
1479 // xxx xxx
1480 // In this situation, we draw arrow above curving upwards.
1481 // 3. Two tokens on the same line, and the destination is on the left.
1482 // xxx xxx
1483 // ^ /
1484 // \____/
1485 // In this situation, we draw arrow below curving downwards.
1486 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1487 startRect.top >= endRect.top + 5;
1488 const leftToRight = startRect.left < endRect.left;
1489
1490 // NOTE: various magic constants are chosen empirically for
1491 // better positioning and look
1492 if (onDifferentLines) {
1493 // Case #1
1494 const topToBottom = startRect.top < endRect.top;
1495 posStart.x = startRect.left - 1;
1496 // We don't want to start it at the top left corner of the token,
1497 // it doesn't feel like this is where the arrow comes from.
1498 // For this reason, we start it in the middle of the left side
1499 // of the token.
1500 posStart.y = startRect.top + startRect.height / 2;
1501
1502 // End node has arrow head and we give it a bit more space.
1503 posEnd.x = endRect.left - 4;
1504 posEnd.y = endRect.top;
1505
1506 // Utility object with x and y offsets for handles.
1507 var curvature = {
1508 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1509 // overlap much with top-to-bottom curves (much more frequent).
1510 x: topToBottom ? 15 : 25,
1511 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1512 }
1513
1514 // When destination is on the different line, we can make a
1515 // curvier arrow because we have space for it.
1516 // So, instead of using
1517 //
1518 // startHandle.x = posStart.x - curvature.x
1519 // endHandle.x = posEnd.x - curvature.x
1520 //
1521 // We use the leftmost of these two values for both handles.
1522 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1523 endHandle.x = startHandle.x;
1524
1525 // Curving downwards from the start node...
1526 startHandle.y = posStart.y + curvature.y;
1527 // ... and upwards from the end node.
1528 endHandle.y = posEnd.y - curvature.y;
1529
1530 } else if (leftToRight) {
1531 // Case #2
1532 // Starting from the top right corner...
1533 posStart.x = startRect.right - 1;
1534 posStart.y = startRect.top;
1535
1536 // ...and ending at the top left corner of the end token.
1537 posEnd.x = endRect.left + 1;
1538 posEnd.y = endRect.top - 1;
1539
1540 // Utility object with x and y offsets for handles.
1541 var curvature = {
1542 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1543 y: 5
1544 }
1545
1546 // Curving to the right...
1547 startHandle.x = posStart.x + curvature.x;
1548 // ... and upwards from the start node.
1549 startHandle.y = posStart.y - curvature.y;
1550
1551 // And to the left...
1552 endHandle.x = posEnd.x - curvature.x;
1553 // ... and upwards from the end node.
1554 endHandle.y = posEnd.y - curvature.y;
1555
1556 } else {
1557 // Case #3
1558 // Starting from the bottom right corner...
1559 posStart.x = startRect.right;
1560 posStart.y = startRect.bottom;
1561
1562 // ...and ending also at the bottom right corner, but of the end token.
1563 posEnd.x = endRect.right - 1;
1564 posEnd.y = endRect.bottom + 1;
1565
1566 // Utility object with x and y offsets for handles.
1567 var curvature = {
1568 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1569 y: 5
1570 }
1571
1572 // Curving to the left...
1573 startHandle.x = posStart.x - curvature.x;
1574 // ... and downwards from the start node.
1575 startHandle.y = posStart.y + curvature.y;
1576
1577 // And to the right...
1578 endHandle.x = posEnd.x + curvature.x;
1579 // ... and downwards from the end node.
1580 endHandle.y = posEnd.y + curvature.y;
1581 }
1582
1583 // Put it all together into a path.
1584 // More information on the format:
1585 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1586 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1587 "C" + startHandle.x + "," + startHandle.y + " " +
1588 endHandle.x + "," + endHandle.y + " " +
1589 posEnd.x + "," + posEnd.y;
1590
1591 arrow.setAttribute("d", pathStr);
1592};
1593
1594var drawArrows = function() {
1595 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1596 for (var i = 0; i < numOfArrows; ++i) {
1597 drawArrow(i);
1598 }
1599}
1600
1601var toggleArrows = function(event) {
1602 const arrows = document.querySelector("#arrows");
1603 if (event.target.checked) {
1604 arrows.setAttribute("visibility", "visible");
1605 } else {
1606 arrows.setAttribute("visibility", "hidden");
1607 }
1608}
1609
1610window.addEventListener("resize", drawArrows);
1611document.addEventListener("DOMContentLoaded", function() {
1612 // Whenever we show invocation, locations change, i.e. we
1613 // need to redraw arrows.
1614 document
1615 .querySelector('input[id="showinvocation"]')
1616 .addEventListener("click", drawArrows);
1617 // Hiding irrelevant lines also should cause arrow rerender.
1618 document
1619 .querySelector('input[name="showCounterexample"]')
1620 .addEventListener("change", drawArrows);
1621 document
1622 .querySelector('input[name="showArrows"]')
1623 .addEventListener("change", toggleArrows);
1624 drawArrows();
1625 // Default highlighting for the last event.
1626 highlightArrowsForSelectedEvent();
1627});
1628</script>
1629 )<<<";
1630}
static bool shouldDisplayPopUpRange(const SourceRange &Range)
static void EmitAlphaCounter(raw_ostream &os, unsigned n)
static std::string getSpanBeginForControl(const char *ClassName, unsigned Index)
static void createHTMLDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, bool SupportMultipleFiles)
Creates and registers an HTML diagnostic consumer, without any additional text consumer.
static void HandlePopUpPieceStartTag(Rewriter &R, const std::vector< SourceRange > &PopUpRanges)
static std::string getSpanBeginForControlEnd(unsigned Index)
static void HandlePopUpPieceEndTag(Rewriter &R, const PathDiagnosticPopUpPiece &Piece, std::vector< SourceRange > &PopUpRanges, unsigned int LastReportedPieceIndex, unsigned int PopUpPieceIndex)
static std::string getSpanBeginForControlStart(unsigned Index)
#define HTML_DIAGNOSTICS_NAME
Result
Implement __builtin_bit_cast and related operations.
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified.
static DiagnosticBuilder Diag(DiagnosticsEngine *Diags, const LangOptions &Features, FullSourceLoc TokLoc, const char *TokBegin, const char *TokRangeBegin, const char *TokRangeEnd, unsigned DiagID)
Produce a diagnostic highlighting some portion of a literal.
Defines the clang::Preprocessor interface.
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
__DEVICE__ int max(int __a, int __b)
static CharSourceRange getTokenRange(SourceRange R)
SourceLocation getEnd() const
SourceLocation getBegin() const
StringRef getName() const
The name of this FileEntry.
Definition FileEntry.h:61
FullSourceLoc getExpansionLoc() const
const char * getCharacterData(bool *Invalid=nullptr) const
unsigned getExpansionColumnNumber(bool *Invalid=nullptr) const
StringRef getBufferData(bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
FileIDAndOffset getDecomposedLoc() const
Decompose the specified location into a raw FileID + Offset pair.
const SourceManager & getManager() const
unsigned getExpansionLineNumber(bool *Invalid=nullptr) const
static CharSourceRange getAsCharRange(SourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Given a token range, produce a corresponding CharSourceRange that is not a token range.
Definition Lexer.h:438
MacroExpansionContext tracks the macro expansions processed by the Preprocessor.
Engages in a tight little dance with the lexer to efficiently preprocess tokens.
SourceManager & getSourceManager() const
const LangOptions & getLangOpts() const
bool isValid() const
Return true if this is a valid SourceLocation object.
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
FileIDAndOffset getDecomposedExpansionLoc(SourceLocation Loc) const
Decompose the specified location into a raw FileID + Offset pair.
unsigned getColumnNumber(FileID FID, unsigned FilePos, bool *Invalid=nullptr) const
Return the column # for the specified file position.
OptionalFileEntryRef getFileEntryRefForID(FileID FID) const
Returns the FileEntryRef for the provided FileID.
SourceLocation getLocForEndOfFile(FileID FID) const
Return the source location corresponding to the last byte of the specified file.
llvm::MemoryBufferRef getBufferOrFake(FileID FID, SourceLocation Loc=SourceLocation()) const
Return the buffer for the specified FileID.
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file.
SourceLocation getExpansionLoc(SourceLocation Loc) const
Given a SourceLocation object Loc, return the expansion location referenced by the ID.
unsigned getLength() const
Definition Token.h:145
This class is used for tools that requires cross translation unit capability.
PathDiagnosticRange asRange() const
ArrayRef< SourceRange > getRanges() const
Return the SourceRanges associated with this PathDiagnosticPiece.
virtual PathDiagnosticLocation getLocation() const =0
PathDiagnosticLocation getLocation() const override
meta_iterator meta_end() const
PathDiagnosticLocation getUniqueingLoc() const
Get the location on which the report should be uniqued.
StringRef getVerboseDescription() const
const Decl * getDeclWithIssue() const
Return the semantic context where an issue occurred.
const FilesToLineNumsMap & getExecutedLines() const
StringRef getCategory() const
meta_iterator meta_begin() const
SmallString< 32 > getIssueHash(const SourceManager &SrcMgr, const LangOptions &LangOpts) const
Get a hash that identifies the issue.
PathDiagnosticLocation getLocation() const
PathPieces flatten(bool ShouldFlattenMacros) const
std::vector< std::unique_ptr< PathDiagnosticConsumer > > PathDiagnosticConsumers
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
void createPlistDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, const MacroExpansionContext &MacroExpansions, bool SupportsMultipleFiles)
Creates and registers a Plist diagnostic consumer, without any additional text consumer.
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
std::map< FileID, std::set< unsigned > > FilesToLineNumsMap
File IDs mapped to sets of line numbers.
void createSarifDiagnosticConsumerImpl(PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP)
Creates and registers a SARIF diagnostic consumer, without any additional text consumer.
void AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID, StringRef title)
void HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E, const char *StartTag, const char *EndTag, bool IsTokenRange=true)
HighlightRange - Highlight a range in the source code with the specified start/end tags.
RelexRewriteCacheRef instantiateRelexRewriteCache()
If you need to rewrite the same file multiple times, you can instantiate a RelexRewriteCache and refe...
void AddLineNumbers(Rewriter &R, FileID FID)
void SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
SyntaxHighlight - Relex the specified FileID and annotate the HTML with information about keywords,...
void HighlightMacros(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
HighlightMacros - This uses the macro table state from the end of the file, to reexpand macros and in...
void EscapeText(Rewriter &R, FileID FID, bool EscapeSpaces=false, bool ReplaceTabs=false)
EscapeText - HTMLize a specified file so that special characters are are translated so that they are ...
std::shared_ptr< RelexRewriteCache > RelexRewriteCacheRef
Definition HTMLRewrite.h:31
StringRef getName(const HeaderType T)
Definition HeaderFile.h:38
@ Number
Just a number, nothing else.
Definition Primitives.h:26
Top level wrappers for InstallAPI frontend operations.
bool isa(CodeGen::Address addr)
Definition Address.h:330
CustomizableOptional< FileEntryRef > OptionalFileEntryRef
Definition FileEntry.h:196
std::pair< FileID, unsigned > FileIDAndOffset
const StreamingDiagnostic & operator<<(const StreamingDiagnostic &DB, const ConceptReference *C)
Insertion operator for diagnostics.
U cast(CodeGen::Address addr)
Definition Address.h:327
std::optional< CharSourceRange > getExpansionRangeInFile(CharSourceRange Range, FileID FID, const SourceManager &SM)
Maps both endpoints of Range to their macro expansion, so that the range can be shown to a user.
These options tweak the behavior of path diangostic consumers.
bool ShouldWriteVerboseReportFilename
If the consumer intends to produce multiple output files, should it use a pseudo-random file name or ...
std::string ToolInvocation
Run-line of the tool that produced the diagnostic.