clang 22.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
577 generateKeyboardNavigationJavascript());
578
580 generateArrowDrawingJavascript());
581
582 // Checkbox and javascript for filtering the output to the counterexample.
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
790 // Process the path.
791 // Maintain the counts of extra note pieces separately.
792 unsigned TotalPieces = getPathSizeWithoutArrows(path);
793 unsigned TotalNotePieces =
794 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
796 });
797 unsigned PopUpPieceCount =
798 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
800 });
801
802 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
803 unsigned NumRegularPieces = TotalRegularPieces;
804 unsigned NumNotePieces = TotalNotePieces;
805 unsigned NumberOfArrows = 0;
806 // Stores the count of the regular piece indices.
807 std::map<int, int> IndexMap;
808 ArrowMap ArrowIndices(TotalRegularPieces + 1);
809
810 // Stores the different ranges where we have reported something.
811 std::vector<SourceRange> PopUpRanges;
812 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
813 const auto &Piece = *I.get();
814
816 ++IndexMap[NumRegularPieces];
817 } else if (isa<PathDiagnosticNotePiece>(Piece)) {
818 // This adds diagnostic bubbles, but not navigation.
819 // Navigation through note pieces would be added later,
820 // as a separate pass through the piece list.
821 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
822 --NumNotePieces;
823
824 } else if (isArrowPiece(Piece)) {
825 NumberOfArrows = ProcessControlFlowPiece(
826 R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows);
827 ArrowIndices[NumRegularPieces] = NumberOfArrows;
828
829 } else {
830 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
831 TotalRegularPieces);
832 --NumRegularPieces;
833 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
834 }
835 }
836 ArrowIndices[0] = NumberOfArrows;
837
838 // At this point ArrowIndices represent the following data structure:
839 // [a_0, a_1, ..., a_N]
840 // where N is the number of events in the path.
841 //
842 // Then for every event with index i \in [0, N - 1], we can say that
843 // arrows with indices \in [a_(i+1), a_i) correspond to that event.
844 // We can say that because arrows with these indices appeared in the
845 // path in between the i-th and the (i+1)-th events.
846 assert(ArrowIndices.back() == 0 &&
847 "No arrows should be after the last event");
848 // This assertion also guarantees that all indices in are <= NumberOfArrows.
849 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
850 "Incorrect arrow indices map");
851
852 // Secondary indexing if we are having multiple pop-ups between two notes.
853 // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...)
854 NumRegularPieces = TotalRegularPieces;
855 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
856 const auto &Piece = *I.get();
857
858 if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
859 int PopUpPieceIndex = IndexMap[NumRegularPieces];
860
861 // Pop-up pieces needs the index of the last reported piece and its count
862 // how many times we report to handle multiple reports on the same range.
863 // This marks the variable, adds the </table> end tag and the message
864 // (list element) as a row. The <table> start tag will be added after the
865 // rows has been written out. Note: It stores every different range.
866 HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces,
867 PopUpPieceIndex);
868
869 if (PopUpPieceIndex > 0)
870 --IndexMap[NumRegularPieces];
871
872 } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) {
873 --NumRegularPieces;
874 }
875 }
876
877 // Add the <table> start tag of pop-up pieces based on the stored ranges.
878 HandlePopUpPieceStartTag(R, PopUpRanges);
879
880 // Add line numbers, header, footer, etc.
881 html::EscapeText(R, FID);
882 html::AddLineNumbers(R, FID);
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";
1015 else
1016 os << "; max-width:100em";
1017
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 }
1070
1071 os << "</tr></table>";
1072 }
1073
1074 // Within a macro piece. Write out each event.
1075 ProcessMacroPiece(os, *MP, 0);
1076 }
1077 else {
1078 os << html::EscapeText(P.getString());
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.
1100 unsigned DisplayPos = LineEnd - FileStart;
1101 SourceLocation Loc =
1102 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
1103
1104 R.InsertTextBefore(Loc, os.str());
1105
1106 // Now highlight the ranges.
1107 ArrayRef<SourceRange> Ranges = P.getRanges();
1108 for (const auto &Range : Ranges) {
1109 // If we have already highlighted the range as a pop-up there is no work.
1110 if (llvm::is_contained(PopUpRanges, Range))
1111 continue;
1112
1113 HighlightRange(R, LPosInfo.first, Range);
1114 }
1115}
1116
1117static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
1118 unsigned x = n % ('z' - 'a');
1119 n /= 'z' - 'a';
1120
1121 if (n > 0)
1122 EmitAlphaCounter(os, n);
1123
1124 os << char('a' + x);
1125}
1126
1127unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1128 const PathDiagnosticMacroPiece& P,
1129 unsigned num) {
1130 for (const auto &subPiece : P.subPieces) {
1131 if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1132 num = ProcessMacroPiece(os, *MP, num);
1133 continue;
1134 }
1135
1136 if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1137 os << "<div class=\"msg msgEvent\" style=\"width:94%; "
1138 "margin-left:5px\">"
1139 "<table class=\"msgT\"><tr>"
1140 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1141 EmitAlphaCounter(os, num++);
1142 os << "</div></td><td valign=\"top\">"
1143 << html::EscapeText(EP->getString())
1144 << "</td></tr></table></div>\n";
1145 }
1146 }
1147
1148 return num;
1149}
1150
1151void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1152 const ArrowMap &ArrowIndices) {
1153 std::string S;
1154 llvm::raw_string_ostream OS(S);
1155
1156 OS << R"<<<(
1157<style type="text/css">
1158 svg {
1159 position:absolute;
1160 top:0;
1161 left:0;
1162 height:100%;
1163 width:100%;
1164 pointer-events: none;
1165 overflow: visible
1166 }
1167 .arrow {
1168 stroke-opacity: 0.2;
1169 stroke-width: 1;
1170 marker-end: url(#arrowhead);
1171 }
1172
1173 .arrow.selected {
1174 stroke-opacity: 0.6;
1175 stroke-width: 2;
1176 marker-end: url(#arrowheadSelected);
1177 }
1178
1179 .arrowhead {
1180 orient: auto;
1181 stroke: none;
1182 opacity: 0.6;
1183 fill: blue;
1184 }
1185</style>
1186<svg xmlns="http://www.w3.org/2000/svg">
1187 <defs>
1188 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1189 viewBox="0 0 10 10" refX="3" refY="5"
1190 markerWidth="4" markerHeight="4">
1191 <path d="M 0 0 L 10 5 L 0 10 z" />
1192 </marker>
1193 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1194 viewBox="0 0 10 10" refX="3" refY="5"
1195 markerWidth="4" markerHeight="4">
1196 <path d="M 0 0 L 10 5 L 0 10 z" />
1197 </marker>
1198 </defs>
1199 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1200)<<<";
1201
1202 for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1203 OS << " <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n";
1204 }
1205
1206 OS << R"<<<(
1207 </g>
1208</svg>
1209<script type='text/javascript'>
1210const arrowIndices = )<<<";
1211
1212 OS << ArrowIndices << "\n</script>\n";
1213
1215 OS.str());
1216}
1217
1218static std::string getSpanBeginForControl(const char *ClassName,
1219 unsigned Index) {
1220 std::string Result;
1221 llvm::raw_string_ostream OS(Result);
1222 OS << "<span id=\"" << ClassName << Index << "\">";
1223 return Result;
1224}
1225
1226static std::string getSpanBeginForControlStart(unsigned Index) {
1227 return getSpanBeginForControl("start", Index);
1228}
1229
1230static std::string getSpanBeginForControlEnd(unsigned Index) {
1231 return getSpanBeginForControl("end", Index);
1232}
1233
1234unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1235 Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P,
1236 unsigned Number) {
1237 for (const PathDiagnosticLocationPair &LPair : P) {
1238 std::string Start = getSpanBeginForControlStart(Number),
1239 End = getSpanBeginForControlEnd(Number++);
1240
1241 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1242 Start.c_str());
1243 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1244 End.c_str());
1245 }
1246
1247 return Number;
1248}
1249
1250void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1251 SourceRange Range,
1252 const char *HighlightStart,
1253 const char *HighlightEnd) {
1254 SourceManager &SM = R.getSourceMgr();
1255 const LangOptions &LangOpts = R.getLangOpts();
1256
1257 SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
1258 unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
1259
1260 SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
1261 unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
1262
1263 if (EndLineNo < StartLineNo)
1264 return;
1265
1266 if (SM.getFileID(InstantiationStart) != BugFileID ||
1267 SM.getFileID(InstantiationEnd) != BugFileID)
1268 return;
1269
1270 // Compute the column number of the end.
1271 unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
1272 unsigned OldEndColNo = EndColNo;
1273
1274 if (EndColNo) {
1275 // Add in the length of the token, so that we cover multi-char tokens.
1276 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
1277 }
1278
1279 // Highlight the range. Make the span tag the outermost tag for the
1280 // selected range.
1281
1282 SourceLocation E =
1283 InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
1284
1285 html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
1286}
1287
1288StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1289 return R"<<<(
1290<script type='text/javascript'>
1291var digitMatcher = new RegExp("[0-9]+");
1292
1293var querySelectorAllArray = function(selector) {
1294 return Array.prototype.slice.call(
1295 document.querySelectorAll(selector));
1296}
1297
1298document.addEventListener("DOMContentLoaded", function() {
1299 querySelectorAllArray(".PathNav > a").forEach(
1300 function(currentValue, currentIndex) {
1301 var hrefValue = currentValue.getAttribute("href");
1302 currentValue.onclick = function() {
1303 scrollTo(document.querySelector(hrefValue));
1304 return false;
1305 };
1306 });
1307});
1308
1309var findNum = function() {
1310 var s = document.querySelector(".msg.selected");
1311 if (!s || s.id == "EndPath") {
1312 return 0;
1313 }
1314 var out = parseInt(digitMatcher.exec(s.id)[0]);
1315 return out;
1316};
1317
1318var classListAdd = function(el, theClass) {
1319 if(!el.className.baseVal)
1320 el.className += " " + theClass;
1321 else
1322 el.className.baseVal += " " + theClass;
1323};
1324
1325var classListRemove = function(el, theClass) {
1326 var className = (!el.className.baseVal) ?
1327 el.className : el.className.baseVal;
1328 className = className.replace(" " + theClass, "");
1329 if(!el.className.baseVal)
1330 el.className = className;
1331 else
1332 el.className.baseVal = className;
1333};
1334
1335var scrollTo = function(el) {
1336 querySelectorAllArray(".selected").forEach(function(s) {
1337 classListRemove(s, "selected");
1338 });
1339 classListAdd(el, "selected");
1340 window.scrollBy(0, el.getBoundingClientRect().top -
1341 (window.innerHeight / 2));
1342 highlightArrowsForSelectedEvent();
1343};
1344
1345var move = function(num, up, numItems) {
1346 if (num == 1 && up || num == numItems - 1 && !up) {
1347 return 0;
1348 } else if (num == 0 && up) {
1349 return numItems - 1;
1350 } else if (num == 0 && !up) {
1351 return 1 % numItems;
1352 }
1353 return up ? num - 1 : num + 1;
1354}
1355
1356var numToId = function(num) {
1357 if (num == 0) {
1358 return document.getElementById("EndPath")
1359 }
1360 return document.getElementById("Path" + num);
1361};
1362
1363var navigateTo = function(up) {
1364 var numItems = document.querySelectorAll(
1365 ".line > .msgEvent, .line > .msgControl").length;
1366 var currentSelected = findNum();
1367 var newSelected = move(currentSelected, up, numItems);
1368 var newEl = numToId(newSelected, numItems);
1369
1370 // Scroll element into center.
1371 scrollTo(newEl);
1372};
1373
1374window.addEventListener("keydown", function (event) {
1375 if (event.defaultPrevented) {
1376 return;
1377 }
1378 // key 'j'
1379 if (event.keyCode == 74) {
1380 navigateTo(/*up=*/false);
1381 // key 'k'
1382 } else if (event.keyCode == 75) {
1383 navigateTo(/*up=*/true);
1384 } else {
1385 return;
1386 }
1387 event.preventDefault();
1388}, true);
1389</script>
1390 )<<<";
1391}
1392
1393StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1394 return R"<<<(
1395<script type='text/javascript'>
1396// Return range of numbers from a range [lower, upper).
1397function range(lower, upper) {
1398 var array = [];
1399 for (var i = lower; i <= upper; ++i) {
1400 array.push(i);
1401 }
1402 return array;
1403}
1404
1405var getRelatedArrowIndices = function(pathId) {
1406 // HTML numeration of events is a bit different than it is in the path.
1407 // Everything is rotated one step to the right, so the last element
1408 // (error diagnostic) has index 0.
1409 if (pathId == 0) {
1410 // arrowIndices has at least 2 elements
1411 pathId = arrowIndices.length - 1;
1412 }
1413
1414 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1415}
1416
1417var highlightArrowsForSelectedEvent = function() {
1418 const selectedNum = findNum();
1419 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1420 arrowIndicesToHighlight.forEach((index) => {
1421 var arrow = document.querySelector("#arrow" + index);
1422 if(arrow) {
1423 classListAdd(arrow, "selected")
1424 }
1425 });
1426}
1427
1428var getAbsoluteBoundingRect = function(element) {
1429 const relative = element.getBoundingClientRect();
1430 return {
1431 left: relative.left + window.pageXOffset,
1432 right: relative.right + window.pageXOffset,
1433 top: relative.top + window.pageYOffset,
1434 bottom: relative.bottom + window.pageYOffset,
1435 height: relative.height,
1436 width: relative.width
1437 };
1438}
1439
1440var drawArrow = function(index) {
1441 // This function is based on the great answer from SO:
1442 // https://stackoverflow.com/a/39575674/11582326
1443 var start = document.querySelector("#start" + index);
1444 var end = document.querySelector("#end" + index);
1445 var arrow = document.querySelector("#arrow" + index);
1446
1447 var startRect = getAbsoluteBoundingRect(start);
1448 var endRect = getAbsoluteBoundingRect(end);
1449
1450 // It is an arrow from a token to itself, no need to visualize it.
1451 if (startRect.top == endRect.top &&
1452 startRect.left == endRect.left)
1453 return;
1454
1455 // Each arrow is a very simple Bézier curve, with two nodes and
1456 // two handles. So, we need to calculate four points in the window:
1457 // * start node
1458 var posStart = { x: 0, y: 0 };
1459 // * end node
1460 var posEnd = { x: 0, y: 0 };
1461 // * handle for the start node
1462 var startHandle = { x: 0, y: 0 };
1463 // * handle for the end node
1464 var endHandle = { x: 0, y: 0 };
1465 // One can visualize it as follows:
1466 //
1467 // start handle
1468 // /
1469 // X"""_.-""""X
1470 // .' \
1471 // / start node
1472 // |
1473 // |
1474 // | end node
1475 // \ /
1476 // `->X
1477 // X-'
1478 // \
1479 // end handle
1480 //
1481 // NOTE: (0, 0) is the top left corner of the window.
1482
1483 // We have 3 similar, but still different scenarios to cover:
1484 //
1485 // 1. Two tokens on different lines.
1486 // -xxx
1487 // /
1488 // \
1489 // -> xxx
1490 // In this situation, we draw arrow on the left curving to the left.
1491 // 2. Two tokens on the same line, and the destination is on the right.
1492 // ____
1493 // / \
1494 // / V
1495 // xxx xxx
1496 // In this situation, we draw arrow above curving upwards.
1497 // 3. Two tokens on the same line, and the destination is on the left.
1498 // xxx xxx
1499 // ^ /
1500 // \____/
1501 // In this situation, we draw arrow below curving downwards.
1502 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1503 startRect.top >= endRect.top + 5;
1504 const leftToRight = startRect.left < endRect.left;
1505
1506 // NOTE: various magic constants are chosen empirically for
1507 // better positioning and look
1508 if (onDifferentLines) {
1509 // Case #1
1510 const topToBottom = startRect.top < endRect.top;
1511 posStart.x = startRect.left - 1;
1512 // We don't want to start it at the top left corner of the token,
1513 // it doesn't feel like this is where the arrow comes from.
1514 // For this reason, we start it in the middle of the left side
1515 // of the token.
1516 posStart.y = startRect.top + startRect.height / 2;
1517
1518 // End node has arrow head and we give it a bit more space.
1519 posEnd.x = endRect.left - 4;
1520 posEnd.y = endRect.top;
1521
1522 // Utility object with x and y offsets for handles.
1523 var curvature = {
1524 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1525 // overlap much with top-to-bottom curves (much more frequent).
1526 x: topToBottom ? 15 : 25,
1527 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1528 }
1529
1530 // When destination is on the different line, we can make a
1531 // curvier arrow because we have space for it.
1532 // So, instead of using
1533 //
1534 // startHandle.x = posStart.x - curvature.x
1535 // endHandle.x = posEnd.x - curvature.x
1536 //
1537 // We use the leftmost of these two values for both handles.
1538 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1539 endHandle.x = startHandle.x;
1540
1541 // Curving downwards from the start node...
1542 startHandle.y = posStart.y + curvature.y;
1543 // ... and upwards from the end node.
1544 endHandle.y = posEnd.y - curvature.y;
1545
1546 } else if (leftToRight) {
1547 // Case #2
1548 // Starting from the top right corner...
1549 posStart.x = startRect.right - 1;
1550 posStart.y = startRect.top;
1551
1552 // ...and ending at the top left corner of the end token.
1553 posEnd.x = endRect.left + 1;
1554 posEnd.y = endRect.top - 1;
1555
1556 // Utility object with x and y offsets for handles.
1557 var curvature = {
1558 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1559 y: 5
1560 }
1561
1562 // Curving to the right...
1563 startHandle.x = posStart.x + curvature.x;
1564 // ... and upwards from the start node.
1565 startHandle.y = posStart.y - curvature.y;
1566
1567 // And to the left...
1568 endHandle.x = posEnd.x - curvature.x;
1569 // ... and upwards from the end node.
1570 endHandle.y = posEnd.y - curvature.y;
1571
1572 } else {
1573 // Case #3
1574 // Starting from the bottom right corner...
1575 posStart.x = startRect.right;
1576 posStart.y = startRect.bottom;
1577
1578 // ...and ending also at the bottom right corner, but of the end token.
1579 posEnd.x = endRect.right - 1;
1580 posEnd.y = endRect.bottom + 1;
1581
1582 // Utility object with x and y offsets for handles.
1583 var curvature = {
1584 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1585 y: 5
1586 }
1587
1588 // Curving to the left...
1589 startHandle.x = posStart.x - curvature.x;
1590 // ... and downwards from the start node.
1591 startHandle.y = posStart.y + curvature.y;
1592
1593 // And to the right...
1594 endHandle.x = posEnd.x + curvature.x;
1595 // ... and downwards from the end node.
1596 endHandle.y = posEnd.y + curvature.y;
1597 }
1598
1599 // Put it all together into a path.
1600 // More information on the format:
1601 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1602 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1603 "C" + startHandle.x + "," + startHandle.y + " " +
1604 endHandle.x + "," + endHandle.y + " " +
1605 posEnd.x + "," + posEnd.y;
1606
1607 arrow.setAttribute("d", pathStr);
1608};
1609
1610var drawArrows = function() {
1611 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1612 for (var i = 0; i < numOfArrows; ++i) {
1613 drawArrow(i);
1614 }
1615}
1616
1617var toggleArrows = function(event) {
1618 const arrows = document.querySelector("#arrows");
1619 if (event.target.checked) {
1620 arrows.setAttribute("visibility", "visible");
1621 } else {
1622 arrows.setAttribute("visibility", "hidden");
1623 }
1624}
1625
1626window.addEventListener("resize", drawArrows);
1627document.addEventListener("DOMContentLoaded", function() {
1628 // Whenever we show invocation, locations change, i.e. we
1629 // need to redraw arrows.
1630 document
1631 .querySelector('input[id="showinvocation"]')
1632 .addEventListener("click", drawArrows);
1633 // Hiding irrelevant lines also should cause arrow rerender.
1634 document
1635 .querySelector('input[name="showCounterexample"]')
1636 .addEventListener("change", drawArrows);
1637 document
1638 .querySelector('input[name="showArrows"]')
1639 .addEventListener("change", toggleArrows);
1640 drawArrows();
1641 // Default highlighting for the last event.
1642 highlightArrowsForSelectedEvent();
1643});
1644</script>
1645 )<<<";
1646}
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
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)
__device__ __2f16 float __ockl_bool s
__device__ __2f16 float c
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:498
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 InsertTextBefore(SourceLocation Loc, StringRef Str)
InsertText - Insert the specified string at the specified location in the original buffer.
Definition Rewriter.h:137
SourceManager & getSourceMgr() const
Definition Rewriter.h:78
const LangOptions & getLangOpts() const
Definition Rewriter.h:79
const llvm::RewriteBuffer * getRewriteBufferFor(FileID FID) const
getRewriteBufferFor - Return the rewrite buffer for the specified FileID.
Definition Rewriter.h:199
bool InsertTextAfter(SourceLocation Loc, StringRef Str)
InsertTextAfter - Insert the specified string at the specified location in the original buffer.
Definition Rewriter.h:124
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:143
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
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:208
std::pair< FileID, unsigned > FileIDAndOffset
@ Result
The result type of a method or function.
Definition TypeBase.h:905
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.