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