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