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