clang 17.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, const FileEntry *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 const FileEntry *Entry = SMgr.getFileEntryForID(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.getFileEntryForID(*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();
432 const FileEntry* Entry = SMgr.getFileEntryForID(FID);
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, const PathPieces& path, FileID FID,
542 const FileEntry *Entry, const char *declName) {
543 // This is a cludge; basically we want to append either the full
544 // working directory if we have no directory information. This is
545 // a work in progress.
546
547 llvm::SmallString<0> DirName;
548
549 if (llvm::sys::path::is_relative(Entry->getName())) {
550 llvm::sys::fs::current_path(DirName);
551 DirName += '/';
552 }
553
554 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
555 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
556
557 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript());
558
560 generateKeyboardNavigationJavascript());
561
563 generateArrowDrawingJavascript());
564
565 // Checkbox and javascript for filtering the output to the counterexample.
567 showRelevantLinesJavascript(D, path));
568
569 // Add the name of the file as an <h1> tag.
570 {
571 std::string s;
572 llvm::raw_string_ostream os(s);
573
574 os << "<!-- REPORTHEADER -->\n"
575 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
576 "<tr><td class=\"rowname\">File:</td><td>"
577 << html::EscapeText(DirName)
578 << html::EscapeText(Entry->getName())
579 << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
580 "<a href=\"#EndPath\">line "
581 << LineNumber
582 << ", column "
583 << ColumnNumber
584 << "</a><br />"
585 << D.getVerboseDescription() << "</td></tr>\n";
586
587 // The navigation across the extra notes pieces.
588 unsigned NumExtraPieces = 0;
589 for (const auto &Piece : path) {
590 if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
591 int LineNumber =
592 P->getLocation().asLocation().getExpansionLineNumber();
593 int ColumnNumber =
594 P->getLocation().asLocation().getExpansionColumnNumber();
595 os << "<tr><td class=\"rowname\">Note:</td><td>"
596 << "<a href=\"#Note" << NumExtraPieces << "\">line "
597 << LineNumber << ", column " << ColumnNumber << "</a><br />"
598 << P->getString() << "</td></tr>";
599 ++NumExtraPieces;
600 }
601 }
602
603 // Output any other meta data.
604
606 I != E; ++I) {
607 os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n";
608 }
609
610 os << R"<<<(
611</table>
612<!-- REPORTSUMMARYEXTRA -->
613<h3>Annotated Source Code</h3>
614<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
615 to see keyboard shortcuts</p>
616<input type="checkbox" class="spoilerhider" id="showinvocation" />
617<label for="showinvocation" >Show analyzer invocation</label>
618<div class="spoiler">clang -cc1 )<<<";
619 os << html::EscapeText(DiagOpts.ToolInvocation);
620 os << R"<<<(
621</div>
622<div id='tooltiphint' hidden="true">
623 <p>Keyboard shortcuts: </p>
624 <ul>
625 <li>Use 'j/k' keys for keyboard navigation</li>
626 <li>Use 'Shift+S' to show/hide relevant lines</li>
627 <li>Use '?' to toggle this window</li>
628 </ul>
629 <a href="#" onclick="toggleHelp(); return false;">Close</a>
630</div>
631)<<<";
632
633 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
634 }
635
636 // Embed meta-data tags.
638 std::string s;
639 llvm::raw_string_ostream os(s);
640
641 StringRef BugDesc = D.getVerboseDescription();
642 if (!BugDesc.empty())
643 os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
644
645 StringRef BugType = D.getBugType();
646 if (!BugType.empty())
647 os << "\n<!-- BUGTYPE " << BugType << " -->\n";
648
651 ? UPDLoc.asLocation()
652 : D.getLocation().asLocation()),
653 SMgr);
655 StringRef BugCategory = D.getCategory();
656 if (!BugCategory.empty())
657 os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
658
659 os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n";
660
661 os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n";
662
663 os << "\n<!-- FUNCTIONNAME " << declName << " -->\n";
664
665 os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " << getIssueHash(D, PP)
666 << " -->\n";
667
668 os << "\n<!-- BUGLINE "
669 << LineNumber
670 << " -->\n";
671
672 os << "\n<!-- BUGCOLUMN "
673 << ColumnNumber
674 << " -->\n";
675
676 os << "\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) << " -->\n";
677
678 // Mark the end of the tags.
679 os << "\n<!-- BUGMETAEND -->\n";
680
681 // Insert the text.
682 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
683 }
684
686}
687
688StringRef HTMLDiagnostics::showHelpJavascript() {
689 return R"<<<(
690<script type='text/javascript'>
691
692var toggleHelp = function() {
693 var hint = document.querySelector("#tooltiphint");
694 var attributeName = "hidden";
695 if (hint.hasAttribute(attributeName)) {
696 hint.removeAttribute(attributeName);
697 } else {
698 hint.setAttribute("hidden", "true");
699 }
700};
701window.addEventListener("keydown", function (event) {
702 if (event.defaultPrevented) {
703 return;
704 }
705 if (event.key == "?") {
706 toggleHelp();
707 } else {
708 return;
709 }
710 event.preventDefault();
711});
712</script>
713)<<<";
714}
715
716static bool shouldDisplayPopUpRange(const SourceRange &Range) {
717 return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID());
718}
719
720static void
722 const std::vector<SourceRange> &PopUpRanges) {
723 for (const auto &Range : PopUpRanges) {
725 continue;
726
727 html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "",
728 "<table class='variable_popup'><tbody>",
729 /*IsTokenRange=*/true);
730 }
731}
732
733static void HandlePopUpPieceEndTag(Rewriter &R,
734 const PathDiagnosticPopUpPiece &Piece,
735 std::vector<SourceRange> &PopUpRanges,
736 unsigned int LastReportedPieceIndex,
737 unsigned int PopUpPieceIndex) {
739 llvm::raw_svector_ostream Out(Buf);
740
743 return;
744
745 // Write out the path indices with a right arrow and the message as a row.
746 Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
747 << LastReportedPieceIndex;
748
749 // Also annotate the state transition with extra indices.
750 Out << '.' << PopUpPieceIndex;
751
752 Out << "</div></td><td>" << Piece.getString() << "</td></tr>";
753
754 // If no report made at this range mark the variable and add the end tags.
755 if (!llvm::is_contained(PopUpRanges, Range)) {
756 // Store that we create a report at this range.
757 PopUpRanges.push_back(Range);
758
759 Out << "</tbody></table></span>";
760 html::HighlightRange(R, Range.getBegin(), Range.getEnd(),
761 "<span class='variable'>", Buf.c_str(),
762 /*IsTokenRange=*/true);
763 } else {
764 // Otherwise inject just the new row at the end of the range.
765 html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(),
766 /*IsTokenRange=*/true);
767 }
768}
769
770void HTMLDiagnostics::RewriteFile(Rewriter &R, const PathPieces &path,
771 FileID FID) {
772
773 // Process the path.
774 // Maintain the counts of extra note pieces separately.
775 unsigned TotalPieces = getPathSizeWithoutArrows(path);
776 unsigned TotalNotePieces =
777 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
778 return isa<PathDiagnosticNotePiece>(*p);
779 });
780 unsigned PopUpPieceCount =
781 llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
782 return isa<PathDiagnosticPopUpPiece>(*p);
783 });
784
785 unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
786 unsigned NumRegularPieces = TotalRegularPieces;
787 unsigned NumNotePieces = TotalNotePieces;
788 unsigned NumberOfArrows = 0;
789 // Stores the count of the regular piece indices.
790 std::map<int, int> IndexMap;
791 ArrowMap ArrowIndices(TotalRegularPieces + 1);
792
793 // Stores the different ranges where we have reported something.
794 std::vector<SourceRange> PopUpRanges;
795 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
796 const auto &Piece = *I.get();
797
798 if (isa<PathDiagnosticPopUpPiece>(Piece)) {
799 ++IndexMap[NumRegularPieces];
800 } else if (isa<PathDiagnosticNotePiece>(Piece)) {
801 // This adds diagnostic bubbles, but not navigation.
802 // Navigation through note pieces would be added later,
803 // as a separate pass through the piece list.
804 HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
805 --NumNotePieces;
806
807 } else if (isArrowPiece(Piece)) {
808 NumberOfArrows = ProcessControlFlowPiece(
809 R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows);
810 ArrowIndices[NumRegularPieces] = NumberOfArrows;
811
812 } else {
813 HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
814 TotalRegularPieces);
815 --NumRegularPieces;
816 ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
817 }
818 }
819 ArrowIndices[0] = NumberOfArrows;
820
821 // At this point ArrowIndices represent the following data structure:
822 // [a_0, a_1, ..., a_N]
823 // where N is the number of events in the path.
824 //
825 // Then for every event with index i \in [0, N - 1], we can say that
826 // arrows with indices \in [a_(i+1), a_i) correspond to that event.
827 // We can say that because arrows with these indices appeared in the
828 // path in between the i-th and the (i+1)-th events.
829 assert(ArrowIndices.back() == 0 &&
830 "No arrows should be after the last event");
831 // This assertion also guarantees that all indices in are <= NumberOfArrows.
832 assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
833 "Incorrect arrow indices map");
834
835 // Secondary indexing if we are having multiple pop-ups between two notes.
836 // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...)
837 NumRegularPieces = TotalRegularPieces;
838 for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
839 const auto &Piece = *I.get();
840
841 if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
842 int PopUpPieceIndex = IndexMap[NumRegularPieces];
843
844 // Pop-up pieces needs the index of the last reported piece and its count
845 // how many times we report to handle multiple reports on the same range.
846 // This marks the variable, adds the </table> end tag and the message
847 // (list element) as a row. The <table> start tag will be added after the
848 // rows has been written out. Note: It stores every different range.
849 HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces,
850 PopUpPieceIndex);
851
852 if (PopUpPieceIndex > 0)
853 --IndexMap[NumRegularPieces];
854
855 } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) {
856 --NumRegularPieces;
857 }
858 }
859
860 // Add the <table> start tag of pop-up pieces based on the stored ranges.
861 HandlePopUpPieceStartTag(R, PopUpRanges);
862
863 // Add line numbers, header, footer, etc.
864 html::EscapeText(R, FID);
865 html::AddLineNumbers(R, FID);
866
867 addArrowSVGs(R, FID, ArrowIndices);
868
869 // If we have a preprocessor, relex the file and syntax highlight.
870 // We might not have a preprocessor if we come from a deserialized AST file,
871 // for example.
872 html::SyntaxHighlight(R, FID, PP);
873 html::HighlightMacros(R, FID, PP);
874}
875
876void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
877 const PathDiagnosticPiece &P,
878 const std::vector<SourceRange> &PopUpRanges,
879 unsigned num, unsigned max) {
880 // For now, just draw a box above the line in question, and emit the
881 // warning.
882 FullSourceLoc Pos = P.getLocation().asLocation();
883
884 if (!Pos.isValid())
885 return;
886
888 assert(&Pos.getManager() == &SM && "SourceManagers are different!");
889 std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos);
890
891 if (LPosInfo.first != BugFileID)
892 return;
893
894 llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first);
895 const char *FileStart = Buf.getBufferStart();
896
897 // Compute the column number. Rewind from the current position to the start
898 // of the line.
899 unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
900 const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData();
901 const char *LineStart = TokInstantiationPtr-ColNo;
902
903 // Compute LineEnd.
904 const char *LineEnd = TokInstantiationPtr;
905 const char *FileEnd = Buf.getBufferEnd();
906 while (*LineEnd != '\n' && LineEnd != FileEnd)
907 ++LineEnd;
908
909 // Compute the margin offset by counting tabs and non-tabs.
910 unsigned PosNo = 0;
911 for (const char* c = LineStart; c != TokInstantiationPtr; ++c)
912 PosNo += *c == '\t' ? 8 : 1;
913
914 // Create the html for the message.
915
916 const char *Kind = nullptr;
917 bool IsNote = false;
918 bool SuppressIndex = (max == 1);
919 switch (P.getKind()) {
920 case PathDiagnosticPiece::Event: Kind = "Event"; break;
921 case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break;
922 // Setting Kind to "Control" is intentional.
923 case PathDiagnosticPiece::Macro: Kind = "Control"; break;
925 Kind = "Note";
926 IsNote = true;
927 SuppressIndex = true;
928 break;
931 llvm_unreachable("Calls and extra notes should already be handled");
932 }
933
934 std::string sbuf;
935 llvm::raw_string_ostream os(sbuf);
936
937 os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
938
939 if (IsNote)
940 os << "Note" << num;
941 else if (num == max)
942 os << "EndPath";
943 else
944 os << "Path" << num;
945
946 os << "\" class=\"msg";
947 if (Kind)
948 os << " msg" << Kind;
949 os << "\" style=\"margin-left:" << PosNo << "ex";
950
951 // Output a maximum size.
952 if (!isa<PathDiagnosticMacroPiece>(P)) {
953 // Get the string and determining its maximum substring.
954 const auto &Msg = P.getString();
955 unsigned max_token = 0;
956 unsigned cnt = 0;
957 unsigned len = Msg.size();
958
959 for (char C : Msg)
960 switch (C) {
961 default:
962 ++cnt;
963 continue;
964 case ' ':
965 case '\t':
966 case '\n':
967 if (cnt > max_token) max_token = cnt;
968 cnt = 0;
969 }
970
971 if (cnt > max_token)
972 max_token = cnt;
973
974 // Determine the approximate size of the message bubble in em.
975 unsigned em;
976 const unsigned max_line = 120;
977
978 if (max_token >= max_line)
979 em = max_token / 2;
980 else {
981 unsigned characters = max_line;
982 unsigned lines = len / max_line;
983
984 if (lines > 0) {
985 for (; characters > max_token; --characters)
986 if (len / characters > lines) {
987 ++characters;
988 break;
989 }
990 }
991
992 em = characters / 2;
993 }
994
995 if (em < max_line/2)
996 os << "; max-width:" << em << "em";
997 }
998 else
999 os << "; max-width:100em";
1000
1001 os << "\">";
1002
1003 if (!SuppressIndex) {
1004 os << "<table class=\"msgT\"><tr><td valign=\"top\">";
1005 os << "<div class=\"PathIndex";
1006 if (Kind) os << " PathIndex" << Kind;
1007 os << "\">" << num << "</div>";
1008
1009 if (num > 1) {
1010 os << "</td><td><div class=\"PathNav\"><a href=\"#Path"
1011 << (num - 1)
1012 << "\" title=\"Previous event ("
1013 << (num - 1)
1014 << ")\">&#x2190;</a></div>";
1015 }
1016
1017 os << "</td><td>";
1018 }
1019
1020 if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
1021 os << "Within the expansion of the macro '";
1022
1023 // Get the name of the macro by relexing it.
1024 {
1025 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
1026 assert(L.isFileID());
1027 StringRef BufferInfo = L.getBufferData();
1028 std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc();
1029 const char* MacroName = LocInfo.second + BufferInfo.data();
1030 Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(),
1031 BufferInfo.begin(), MacroName, BufferInfo.end());
1032
1033 Token TheTok;
1034 rawLexer.LexFromRawLexer(TheTok);
1035 for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i)
1036 os << MacroName[i];
1037 }
1038
1039 os << "':\n";
1040
1041 if (!SuppressIndex) {
1042 os << "</td>";
1043 if (num < max) {
1044 os << "<td><div class=\"PathNav\"><a href=\"#";
1045 if (num == max - 1)
1046 os << "EndPath";
1047 else
1048 os << "Path" << (num + 1);
1049 os << "\" title=\"Next event ("
1050 << (num + 1)
1051 << ")\">&#x2192;</a></div></td>";
1052 }
1053
1054 os << "</tr></table>";
1055 }
1056
1057 // Within a macro piece. Write out each event.
1058 ProcessMacroPiece(os, *MP, 0);
1059 }
1060 else {
1061 os << html::EscapeText(P.getString());
1062
1063 if (!SuppressIndex) {
1064 os << "</td>";
1065 if (num < max) {
1066 os << "<td><div class=\"PathNav\"><a href=\"#";
1067 if (num == max - 1)
1068 os << "EndPath";
1069 else
1070 os << "Path" << (num + 1);
1071 os << "\" title=\"Next event ("
1072 << (num + 1)
1073 << ")\">&#x2192;</a></div></td>";
1075
1076 os << "</tr></table>";
1077 }
1078 }
1079
1080 os << "</div></td></tr>";
1082 // Insert the new html.
1083 unsigned DisplayPos = LineEnd - FileStart;
1085 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
1086
1087 R.InsertTextBefore(Loc, os.str());
1088
1089 // Now highlight the ranges.
1090 ArrayRef<SourceRange> Ranges = P.getRanges();
1091 for (const auto &Range : Ranges) {
1092 // If we have already highlighted the range as a pop-up there is no work.
1093 if (llvm::is_contained(PopUpRanges, Range))
1094 continue;
1095
1096 HighlightRange(R, LPosInfo.first, Range);
1097 }
1098}
1099
1100static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
1101 unsigned x = n % ('z' - 'a');
1102 n /= 'z' - 'a';
1103
1104 if (n > 0)
1105 EmitAlphaCounter(os, n);
1106
1107 os << char('a' + x);
1108}
1109
1110unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
1112 unsigned num) {
1113 for (const auto &subPiece : P.subPieces) {
1114 if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
1115 num = ProcessMacroPiece(os, *MP, num);
1116 continue;
1117 }
1118
1119 if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
1120 os << "<div class=\"msg msgEvent\" style=\"width:94%; "
1121 "margin-left:5px\">"
1122 "<table class=\"msgT\"><tr>"
1123 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
1124 EmitAlphaCounter(os, num++);
1125 os << "</div></td><td valign=\"top\">"
1126 << html::EscapeText(EP->getString())
1127 << "</td></tr></table></div>\n";
1128 }
1129 }
1130
1131 return num;
1132}
1133
1134void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1135 const ArrowMap &ArrowIndices) {
1136 std::string S;
1137 llvm::raw_string_ostream OS(S);
1138
1139 OS << R"<<<(
1140<style type="text/css">
1141 svg {
1142 position:absolute;
1143 top:0;
1144 left:0;
1145 height:100%;
1146 width:100%;
1147 pointer-events: none;
1148 overflow: visible
1149 }
1150 .arrow {
1151 stroke-opacity: 0.2;
1152 stroke-width: 1;
1153 marker-end: url(#arrowhead);
1154 }
1155
1156 .arrow.selected {
1157 stroke-opacity: 0.6;
1158 stroke-width: 2;
1159 marker-end: url(#arrowheadSelected);
1160 }
1161
1162 .arrowhead {
1163 orient: auto;
1164 stroke: none;
1165 opacity: 0.6;
1166 fill: blue;
1167 }
1168</style>
1169<svg xmlns="http://www.w3.org/2000/svg">
1170 <defs>
1171 <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1172 viewBox="0 0 10 10" refX="3" refY="5"
1173 markerWidth="4" markerHeight="4">
1174 <path d="M 0 0 L 10 5 L 0 10 z" />
1175 </marker>
1176 <marker id="arrowhead" class="arrowhead" opacity="0.2"
1177 viewBox="0 0 10 10" refX="3" refY="5"
1178 markerWidth="4" markerHeight="4">
1179 <path d="M 0 0 L 10 5 L 0 10 z" />
1180 </marker>
1181 </defs>
1182 <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1183)<<<";
1184
1185 for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1186 OS << " <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n";
1187 }
1188
1189 OS << R"<<<(
1190 </g>
1191</svg>
1192<script type='text/javascript'>
1193const arrowIndices = )<<<";
1194
1195 OS << ArrowIndices << "\n</script>\n";
1196
1198 OS.str());
1199}
1200
1201std::string getSpanBeginForControl(const char *ClassName, unsigned Index) {
1202 std::string Result;
1203 llvm::raw_string_ostream OS(Result);
1204 OS << "<span id=\"" << ClassName << Index << "\">";
1205 return Result;
1206}
1207
1208std::string getSpanBeginForControlStart(unsigned Index) {
1209 return getSpanBeginForControl("start", Index);
1210}
1211
1212std::string getSpanBeginForControlEnd(unsigned Index) {
1213 return getSpanBeginForControl("end", Index);
1214}
1215
1216unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1217 Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P,
1218 unsigned Number) {
1219 for (const PathDiagnosticLocationPair &LPair : P) {
1220 std::string Start = getSpanBeginForControlStart(Number),
1221 End = getSpanBeginForControlEnd(Number++);
1222
1223 HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1224 Start.c_str());
1225 HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1226 End.c_str());
1227 }
1228
1229 return Number;
1230}
1231
1232void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
1234 const char *HighlightStart,
1235 const char *HighlightEnd) {
1237 const LangOptions &LangOpts = R.getLangOpts();
1238
1239 SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
1240 unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
1241
1242 SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
1243 unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
1244
1245 if (EndLineNo < StartLineNo)
1246 return;
1247
1248 if (SM.getFileID(InstantiationStart) != BugFileID ||
1249 SM.getFileID(InstantiationEnd) != BugFileID)
1250 return;
1251
1252 // Compute the column number of the end.
1253 unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
1254 unsigned OldEndColNo = EndColNo;
1255
1256 if (EndColNo) {
1257 // Add in the length of the token, so that we cover multi-char tokens.
1258 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
1259 }
1260
1261 // Highlight the range. Make the span tag the outermost tag for the
1262 // selected range.
1263
1264 SourceLocation E =
1265 InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
1266
1267 html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
1268}
1269
1270StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
1271 return R"<<<(
1272<script type='text/javascript'>
1273var digitMatcher = new RegExp("[0-9]+");
1274
1275var querySelectorAllArray = function(selector) {
1276 return Array.prototype.slice.call(
1277 document.querySelectorAll(selector));
1278}
1279
1280document.addEventListener("DOMContentLoaded", function() {
1281 querySelectorAllArray(".PathNav > a").forEach(
1282 function(currentValue, currentIndex) {
1283 var hrefValue = currentValue.getAttribute("href");
1284 currentValue.onclick = function() {
1285 scrollTo(document.querySelector(hrefValue));
1286 return false;
1287 };
1288 });
1289});
1290
1291var findNum = function() {
1292 var s = document.querySelector(".msg.selected");
1293 if (!s || s.id == "EndPath") {
1294 return 0;
1295 }
1296 var out = parseInt(digitMatcher.exec(s.id)[0]);
1297 return out;
1298};
1299
1300var classListAdd = function(el, theClass) {
1301 if(!el.className.baseVal)
1302 el.className += " " + theClass;
1303 else
1304 el.className.baseVal += " " + theClass;
1305};
1306
1307var classListRemove = function(el, theClass) {
1308 var className = (!el.className.baseVal) ?
1309 el.className : el.className.baseVal;
1310 className = className.replace(" " + theClass, "");
1311 if(!el.className.baseVal)
1312 el.className = className;
1313 else
1314 el.className.baseVal = className;
1315};
1316
1317var scrollTo = function(el) {
1318 querySelectorAllArray(".selected").forEach(function(s) {
1319 classListRemove(s, "selected");
1320 });
1321 classListAdd(el, "selected");
1322 window.scrollBy(0, el.getBoundingClientRect().top -
1323 (window.innerHeight / 2));
1324 highlightArrowsForSelectedEvent();
1325};
1326
1327var move = function(num, up, numItems) {
1328 if (num == 1 && up || num == numItems - 1 && !up) {
1329 return 0;
1330 } else if (num == 0 && up) {
1331 return numItems - 1;
1332 } else if (num == 0 && !up) {
1333 return 1 % numItems;
1334 }
1335 return up ? num - 1 : num + 1;
1336}
1337
1338var numToId = function(num) {
1339 if (num == 0) {
1340 return document.getElementById("EndPath")
1341 }
1342 return document.getElementById("Path" + num);
1343};
1344
1345var navigateTo = function(up) {
1346 var numItems = document.querySelectorAll(
1347 ".line > .msgEvent, .line > .msgControl").length;
1348 var currentSelected = findNum();
1349 var newSelected = move(currentSelected, up, numItems);
1350 var newEl = numToId(newSelected, numItems);
1351
1352 // Scroll element into center.
1353 scrollTo(newEl);
1354};
1355
1356window.addEventListener("keydown", function (event) {
1357 if (event.defaultPrevented) {
1358 return;
1359 }
1360 // key 'j'
1361 if (event.keyCode == 74) {
1362 navigateTo(/*up=*/false);
1363 // key 'k'
1364 } else if (event.keyCode == 75) {
1365 navigateTo(/*up=*/true);
1366 } else {
1367 return;
1368 }
1369 event.preventDefault();
1370}, true);
1371</script>
1372 )<<<";
1373}
1374
1375StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1376 return R"<<<(
1377<script type='text/javascript'>
1378// Return range of numbers from a range [lower, upper).
1379function range(lower, upper) {
1380 var array = [];
1381 for (var i = lower; i <= upper; ++i) {
1382 array.push(i);
1383 }
1384 return array;
1385}
1386
1387var getRelatedArrowIndices = function(pathId) {
1388 // HTML numeration of events is a bit different than it is in the path.
1389 // Everything is rotated one step to the right, so the last element
1390 // (error diagnostic) has index 0.
1391 if (pathId == 0) {
1392 // arrowIndices has at least 2 elements
1393 pathId = arrowIndices.length - 1;
1394 }
1395
1396 return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1397}
1398
1399var highlightArrowsForSelectedEvent = function() {
1400 const selectedNum = findNum();
1401 const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1402 arrowIndicesToHighlight.forEach((index) => {
1403 var arrow = document.querySelector("#arrow" + index);
1404 if(arrow) {
1405 classListAdd(arrow, "selected")
1406 }
1407 });
1408}
1409
1410var getAbsoluteBoundingRect = function(element) {
1411 const relative = element.getBoundingClientRect();
1412 return {
1413 left: relative.left + window.pageXOffset,
1414 right: relative.right + window.pageXOffset,
1415 top: relative.top + window.pageYOffset,
1416 bottom: relative.bottom + window.pageYOffset,
1417 height: relative.height,
1418 width: relative.width
1419 };
1420}
1421
1422var drawArrow = function(index) {
1423 // This function is based on the great answer from SO:
1424 // https://stackoverflow.com/a/39575674/11582326
1425 var start = document.querySelector("#start" + index);
1426 var end = document.querySelector("#end" + index);
1427 var arrow = document.querySelector("#arrow" + index);
1428
1429 var startRect = getAbsoluteBoundingRect(start);
1430 var endRect = getAbsoluteBoundingRect(end);
1431
1432 // It is an arrow from a token to itself, no need to visualize it.
1433 if (startRect.top == endRect.top &&
1434 startRect.left == endRect.left)
1435 return;
1436
1437 // Each arrow is a very simple Bézier curve, with two nodes and
1438 // two handles. So, we need to calculate four points in the window:
1439 // * start node
1440 var posStart = { x: 0, y: 0 };
1441 // * end node
1442 var posEnd = { x: 0, y: 0 };
1443 // * handle for the start node
1444 var startHandle = { x: 0, y: 0 };
1445 // * handle for the end node
1446 var endHandle = { x: 0, y: 0 };
1447 // One can visualize it as follows:
1448 //
1449 // start handle
1450 // /
1451 // X"""_.-""""X
1452 // .' \
1453 // / start node
1454 // |
1455 // |
1456 // | end node
1457 // \ /
1458 // `->X
1459 // X-'
1460 // \
1461 // end handle
1462 //
1463 // NOTE: (0, 0) is the top left corner of the window.
1464
1465 // We have 3 similar, but still different scenarios to cover:
1466 //
1467 // 1. Two tokens on different lines.
1468 // -xxx
1469 // /
1470 // \
1471 // -> xxx
1472 // In this situation, we draw arrow on the left curving to the left.
1473 // 2. Two tokens on the same line, and the destination is on the right.
1474 // ____
1475 // / \
1476 // / V
1477 // xxx xxx
1478 // In this situation, we draw arrow above curving upwards.
1479 // 3. Two tokens on the same line, and the destination is on the left.
1480 // xxx xxx
1481 // ^ /
1482 // \____/
1483 // In this situation, we draw arrow below curving downwards.
1484 const onDifferentLines = startRect.top <= endRect.top - 5 ||
1485 startRect.top >= endRect.top + 5;
1486 const leftToRight = startRect.left < endRect.left;
1487
1488 // NOTE: various magic constants are chosen empirically for
1489 // better positioning and look
1490 if (onDifferentLines) {
1491 // Case #1
1492 const topToBottom = startRect.top < endRect.top;
1493 posStart.x = startRect.left - 1;
1494 // We don't want to start it at the top left corner of the token,
1495 // it doesn't feel like this is where the arrow comes from.
1496 // For this reason, we start it in the middle of the left side
1497 // of the token.
1498 posStart.y = startRect.top + startRect.height / 2;
1499
1500 // End node has arrow head and we give it a bit more space.
1501 posEnd.x = endRect.left - 4;
1502 posEnd.y = endRect.top;
1503
1504 // Utility object with x and y offsets for handles.
1505 var curvature = {
1506 // We want bottom-to-top arrow to curve a bit more, so it doesn't
1507 // overlap much with top-to-bottom curves (much more frequent).
1508 x: topToBottom ? 15 : 25,
1509 y: Math.min((posEnd.y - posStart.y) / 3, 10)
1510 }
1511
1512 // When destination is on the different line, we can make a
1513 // curvier arrow because we have space for it.
1514 // So, instead of using
1515 //
1516 // startHandle.x = posStart.x - curvature.x
1517 // endHandle.x = posEnd.x - curvature.x
1518 //
1519 // We use the leftmost of these two values for both handles.
1520 startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1521 endHandle.x = startHandle.x;
1522
1523 // Curving downwards from the start node...
1524 startHandle.y = posStart.y + curvature.y;
1525 // ... and upwards from the end node.
1526 endHandle.y = posEnd.y - curvature.y;
1527
1528 } else if (leftToRight) {
1529 // Case #2
1530 // Starting from the top right corner...
1531 posStart.x = startRect.right - 1;
1532 posStart.y = startRect.top;
1533
1534 // ...and ending at the top left corner of the end token.
1535 posEnd.x = endRect.left + 1;
1536 posEnd.y = endRect.top - 1;
1537
1538 // Utility object with x and y offsets for handles.
1539 var curvature = {
1540 x: Math.min((posEnd.x - posStart.x) / 3, 15),
1541 y: 5
1542 }
1543
1544 // Curving to the right...
1545 startHandle.x = posStart.x + curvature.x;
1546 // ... and upwards from the start node.
1547 startHandle.y = posStart.y - curvature.y;
1548
1549 // And to the left...
1550 endHandle.x = posEnd.x - curvature.x;
1551 // ... and upwards from the end node.
1552 endHandle.y = posEnd.y - curvature.y;
1553
1554 } else {
1555 // Case #3
1556 // Starting from the bottom right corner...
1557 posStart.x = startRect.right;
1558 posStart.y = startRect.bottom;
1559
1560 // ...and ending also at the bottom right corner, but of the end token.
1561 posEnd.x = endRect.right - 1;
1562 posEnd.y = endRect.bottom + 1;
1563
1564 // Utility object with x and y offsets for handles.
1565 var curvature = {
1566 x: Math.min((posStart.x - posEnd.x) / 3, 15),
1567 y: 5
1568 }
1569
1570 // Curving to the left...
1571 startHandle.x = posStart.x - curvature.x;
1572 // ... and downwards from the start node.
1573 startHandle.y = posStart.y + curvature.y;
1574
1575 // And to the right...
1576 endHandle.x = posEnd.x + curvature.x;
1577 // ... and downwards from the end node.
1578 endHandle.y = posEnd.y + curvature.y;
1579 }
1580
1581 // Put it all together into a path.
1582 // More information on the format:
1583 // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1584 var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1585 "C" + startHandle.x + "," + startHandle.y + " " +
1586 endHandle.x + "," + endHandle.y + " " +
1587 posEnd.x + "," + posEnd.y;
1588
1589 arrow.setAttribute("d", pathStr);
1590};
1591
1592var drawArrows = function() {
1593 const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1594 for (var i = 0; i < numOfArrows; ++i) {
1595 drawArrow(i);
1596 }
1597}
1598
1599var toggleArrows = function(event) {
1600 const arrows = document.querySelector("#arrows");
1601 if (event.target.checked) {
1602 arrows.setAttribute("visibility", "visible");
1603 } else {
1604 arrows.setAttribute("visibility", "hidden");
1605 }
1606}
1607
1608window.addEventListener("resize", drawArrows);
1609document.addEventListener("DOMContentLoaded", function() {
1610 // Whenever we show invocation, locations change, i.e. we
1611 // need to redraw arrows.
1612 document
1613 .querySelector('input[id="showinvocation"]')
1614 .addEventListener("click", drawArrows);
1615 // Hiding irrelevant lines also should cause arrow rerender.
1616 document
1617 .querySelector('input[name="showCounterexample"]')
1618 .addEventListener("change", drawArrows);
1619 document
1620 .querySelector('input[name="showArrows"]')
1621 .addEventListener("change", toggleArrows);
1622 drawArrows();
1623 // Default highlighting for the last event.
1624 highlightArrowsForSelectedEvent();
1625});
1626</script>
1627 )<<<";
1628}
StringRef P
#define SM(sm)
Definition: Cuda.cpp:78
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.
llvm::raw_ostream & OS
Definition: Logger.cpp:24
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 c
__device__ __2f16 float bool s
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:83
Cached information about one file (either on disk or in the virtual file system).
Definition: FileEntry.h:353
StringRef getName() const
Definition: FileEntry.h:384
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:82
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:450
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.
SourceLocation getLocForEndOfFile(FileID FID) const
Return the source location corresponding to the last byte of the specified file.
const FileEntry * getFileEntryForID(FileID FID) const
Returns the FileEntry record for the provided FileID.
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file.
SourceLocation getExpansionLoc(SourceLocation Loc) const
Given a SourceLocation object Loc, return the expansion location referenced by the ID.
A trivial tuple used to represent a source range.
Stmt - This represents one statement.
Definition: Stmt.h:72
Token - This structure provides full information about a lexed token.
Definition: Token.h:35
unsigned getLength() const
Definition: Token.h:129
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.
std::deque< std::string >::const_iterator meta_iterator
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.
@ C
Languages that the frontend can parse and compile.
Definition: Format.h:4664
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.