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