brintos

brintos / llvm-project-archived public Read only

0
0
Text · 52.5 KiB · 217b853 Raw
1643 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9//  This file defines the HTMLDiagnostics object.10//11//===----------------------------------------------------------------------===//12 13#include "HTMLDiagnostics.h"14#include "PlistDiagnostics.h"15#include "SarifDiagnostics.h"16#include "clang/AST/Decl.h"17#include "clang/AST/DeclBase.h"18#include "clang/AST/Stmt.h"19#include "clang/Analysis/IssueHash.h"20#include "clang/Analysis/MacroExpansionContext.h"21#include "clang/Analysis/PathDiagnostic.h"22#include "clang/Basic/LLVM.h"23#include "clang/Basic/SourceLocation.h"24#include "clang/Basic/SourceManager.h"25#include "clang/Lex/Lexer.h"26#include "clang/Lex/Preprocessor.h"27#include "clang/Lex/Token.h"28#include "clang/Rewrite/Core/HTMLRewrite.h"29#include "clang/Rewrite/Core/Rewriter.h"30#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"31#include "llvm/ADT/RewriteBuffer.h"32#include "llvm/ADT/STLExtras.h"33#include "llvm/ADT/Sequence.h"34#include "llvm/ADT/SmallString.h"35#include "llvm/ADT/StringRef.h"36#include "llvm/ADT/iterator_range.h"37#include "llvm/Support/Errc.h"38#include "llvm/Support/ErrorHandling.h"39#include "llvm/Support/FileSystem.h"40#include "llvm/Support/Path.h"41#include "llvm/Support/raw_ostream.h"42#include <cassert>43#include <map>44#include <memory>45#include <set>46#include <string>47#include <system_error>48#include <utility>49#include <vector>50 51using namespace clang;52using namespace ento;53using llvm::RewriteBuffer;54 55//===----------------------------------------------------------------------===//56// Boilerplate.57//===----------------------------------------------------------------------===//58 59namespace {60 61class ArrowMap;62 63class HTMLDiagnostics : public PathDiagnosticConsumer {64  PathDiagnosticConsumerOptions DiagOpts;65  std::string Directory;66  bool createdDir = false;67  bool noDir = false;68  const Preprocessor &PP;69  const bool SupportsCrossFileDiagnostics;70  llvm::StringSet<> EmittedHashes;71  html::RelexRewriteCacheRef RewriterCache =72      html::instantiateRelexRewriteCache();73 74public:75  HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,76                  const std::string &OutputDir, const Preprocessor &pp,77                  bool supportsMultipleFiles)78      : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),79        SupportsCrossFileDiagnostics(supportsMultipleFiles) {}80 81  ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }82 83  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,84                            FilesMade *filesMade) override;85 86  StringRef getName() const override { return HTML_DIAGNOSTICS_NAME; }87 88  bool supportsCrossFileDiagnostics() const override {89    return SupportsCrossFileDiagnostics;90  }91 92  unsigned ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece &P,93                             unsigned num);94 95  unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID,96                                   const PathDiagnosticControlFlowPiece &P,97                                   unsigned Number);98 99  void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P,100                   const std::vector<SourceRange> &PopUpRanges, unsigned num,101                   unsigned max);102 103  void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range,104                      const char *HighlightStart = "<span class=\"mrange\">",105                      const char *HighlightEnd = "</span>");106 107  void ReportDiag(const PathDiagnostic &D, FilesMade *filesMade);108 109  // Generate the full HTML report110  std::string GenerateHTML(const PathDiagnostic &D, Rewriter &R,111                           const SourceManager &SMgr, const PathPieces &path,112                           const char *declName);113 114  // Add HTML header/footers to file specified by FID115  void FinalizeHTML(const PathDiagnostic &D, Rewriter &R,116                    const SourceManager &SMgr, const PathPieces &path,117                    FileID FID, FileEntryRef Entry, const char *declName);118 119  // Rewrite the file specified by FID with HTML formatting.120  void RewriteFile(Rewriter &R, const PathPieces &path, FileID FID);121 122  PathGenerationScheme getGenerationScheme() const override {123    return Everything;124  }125 126private:127  void addArrowSVGs(Rewriter &R, FileID BugFileID,128                    const ArrowMap &ArrowIndices);129 130  /// \return Javascript for displaying shortcuts help;131  StringRef showHelpJavascript();132 133  /// \return Javascript for navigating the HTML report using j/k keys.134  StringRef generateKeyboardNavigationJavascript();135 136  /// \return Javascript for drawing control-flow arrows.137  StringRef generateArrowDrawingJavascript();138 139  /// \return JavaScript for an option to only show relevant lines.140  std::string showRelevantLinesJavascript(const PathDiagnostic &D,141                                          const PathPieces &path);142 143  /// Write executed lines from \p D in JSON format into \p os.144  void dumpCoverageData(const PathDiagnostic &D, const PathPieces &path,145                        llvm::raw_string_ostream &os);146};147 148bool isArrowPiece(const PathDiagnosticPiece &P) {149  return isa<PathDiagnosticControlFlowPiece>(P) && P.getString().empty();150}151 152unsigned getPathSizeWithoutArrows(const PathPieces &Path) {153  unsigned TotalPieces = Path.size();154  unsigned TotalArrowPieces = llvm::count_if(155      Path, [](const PathDiagnosticPieceRef &P) { return isArrowPiece(*P); });156  return TotalPieces - TotalArrowPieces;157}158 159class ArrowMap : public std::vector<unsigned> {160  using Base = std::vector<unsigned>;161 162public:163  ArrowMap(unsigned Size) : Base(Size, 0) {}164  unsigned getTotalNumberOfArrows() const { return at(0); }165};166 167llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ArrowMap &Indices) {168  OS << "[ ";169  llvm::interleave(Indices, OS, ",");170  return OS << " ]";171}172 173} // namespace174 175/// Creates and registers an HTML diagnostic consumer, without any additional176/// text consumer.177static void createHTMLDiagnosticConsumerImpl(178    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,179    const std::string &OutputDir, const Preprocessor &PP,180    bool SupportMultipleFiles) {181 182  // TODO: Emit an error here.183  if (OutputDir.empty())184    return;185 186  C.emplace_back(std::make_unique<HTMLDiagnostics>(187      std::move(DiagOpts), OutputDir, PP, SupportMultipleFiles));188}189 190void ento::createHTMLDiagnosticConsumer(191    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,192    const std::string &OutputDir, const Preprocessor &PP,193    const cross_tu::CrossTranslationUnitContext &CTU,194    const MacroExpansionContext &MacroExpansions) {195 196  // FIXME: HTML is currently our default output type, but if the output197  // directory isn't specified, it acts like if it was in the minimal text198  // output mode. This doesn't make much sense, we should have the minimal text199  // as our default. In the case of backward compatibility concerns, this could200  // be preserved with -analyzer-config-compatibility-mode=true.201  createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,202                                          MacroExpansions);203 204  createHTMLDiagnosticConsumerImpl(DiagOpts, C, OutputDir, PP,205                                   /*SupportMultipleFiles=*/true);206}207 208void ento::createHTMLSingleFileDiagnosticConsumer(209    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,210    const std::string &OutputDir, const Preprocessor &PP,211    const cross_tu::CrossTranslationUnitContext &CTU,212    const clang::MacroExpansionContext &MacroExpansions) {213  createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,214                                          MacroExpansions);215 216  createHTMLDiagnosticConsumerImpl(DiagOpts, C, OutputDir, PP,217                                   /*SupportMultipleFiles=*/false);218}219 220void ento::createPlistHTMLDiagnosticConsumer(221    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,222    const std::string &prefix, const Preprocessor &PP,223    const cross_tu::CrossTranslationUnitContext &CTU,224    const MacroExpansionContext &MacroExpansions) {225  createHTMLDiagnosticConsumerImpl(226      DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, true);227  createPlistDiagnosticConsumerImpl(DiagOpts, C, prefix, PP, CTU,228                                    MacroExpansions, true);229  createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP,230                                          CTU, MacroExpansions);231}232 233void ento::createSarifHTMLDiagnosticConsumer(234    PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,235    const std::string &sarif_file, const Preprocessor &PP,236    const cross_tu::CrossTranslationUnitContext &CTU,237    const MacroExpansionContext &MacroExpansions) {238  createHTMLDiagnosticConsumerImpl(239      DiagOpts, C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,240      true);241  createSarifDiagnosticConsumerImpl(DiagOpts, C, sarif_file, PP);242 243  createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, sarif_file,244                                          PP, CTU, MacroExpansions);245}246 247//===----------------------------------------------------------------------===//248// Report processing.249//===----------------------------------------------------------------------===//250 251void HTMLDiagnostics::FlushDiagnosticsImpl(252  std::vector<const PathDiagnostic *> &Diags,253  FilesMade *filesMade) {254  for (const auto Diag : Diags)255    ReportDiag(*Diag, filesMade);256}257 258void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,259                                 FilesMade *filesMade) {260  // Create the HTML directory if it is missing.261  if (!createdDir) {262    createdDir = true;263    if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {264      llvm::errs() << "warning: could not create directory '"265                   << Directory << "': " << ec.message() << '\n';266      noDir = true;267      return;268    }269  }270 271  if (noDir)272    return;273 274  // First flatten out the entire path to make it easier to use.275  PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);276 277  // The path as already been prechecked that the path is non-empty.278  assert(!path.empty());279  const SourceManager &SMgr = path.front()->getLocation().getManager();280 281  // Create a new rewriter to generate HTML.282  Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());283 284  // Get the function/method name285  SmallString<128> declName("unknown");286  int offsetDecl = 0;287  if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {288      if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))289          declName = ND->getDeclName().getAsString();290 291      if (const Stmt *Body = DeclWithIssue->getBody()) {292          // Retrieve the relative position of the declaration which will be used293          // for the file name294          FullSourceLoc L(295              SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),296              SMgr);297          FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);298          offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();299      }300  }301 302  SmallString<32> IssueHash =303      D.getIssueHash(PP.getSourceManager(), PP.getLangOpts());304  auto [It, IsNew] = EmittedHashes.insert(IssueHash);305  if (!IsNew) {306    // We've already emitted a duplicate issue. It'll get overwritten anyway.307    return;308  }309 310  std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());311  if (report.empty()) {312    llvm::errs() << "warning: no diagnostics generated for main file.\n";313    return;314  }315 316  // Create a path for the target HTML file.317  int FD;318 319  SmallString<128> FileNameStr;320  llvm::raw_svector_ostream FileName(FileNameStr);321  FileName << "report-";322 323  // Historically, neither the stable report filename nor the unstable report324  // filename were actually stable. That said, the stable report filename325  // was more stable because it was mostly composed of information326  // about the bug report instead of being completely random.327  // Now both stable and unstable report filenames are in fact stable328  // but the stable report filename is still more verbose.329  if (DiagOpts.ShouldWriteVerboseReportFilename) {330    // FIXME: This code relies on knowing what constitutes the issue hash.331    // Otherwise deduplication won't work correctly.332    FileID ReportFile =333        path.back()->getLocation().asLocation().getExpansionLoc().getFileID();334 335    OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(ReportFile);336 337    FileName << llvm::sys::path::filename(Entry->getName()).str() << "-"338             << declName.c_str() << "-" << offsetDecl << "-";339  }340 341  FileName << StringRef(IssueHash).substr(0, 6).str() << ".html";342 343  SmallString<128> ResultPath;344  llvm::sys::path::append(ResultPath, Directory, FileName.str());345  if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) {346    llvm::errs() << "warning: could not make '" << ResultPath347                 << "' absolute: " << EC.message() << '\n';348    return;349  }350 351  if (std::error_code EC = llvm::sys::fs::openFileForReadWrite(352          ResultPath, FD, llvm::sys::fs::CD_CreateNew,353          llvm::sys::fs::OF_Text)) {354    // Existence of the file corresponds to the situation where a different355    // Clang instance has emitted a bug report with the same issue hash.356    // This is an entirely normal situation that does not deserve a warning,357    // as apart from hash collisions this can happen because the reports358    // are in fact similar enough to be considered duplicates of each other.359    if (EC != llvm::errc::file_exists) {360      llvm::errs() << "warning: could not create file in '" << Directory361                   << "': " << EC.message() << '\n';362    } else if (filesMade) {363      // Record that we created the file so that it gets referenced in the364      // plist and SARIF reports for every translation unit that found the365      // issue.366      filesMade->addDiagnostic(D, getName(),367                               llvm::sys::path::filename(ResultPath));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 crosses385  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 file397    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 arrow407      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 arrow415      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 path425    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();444  OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(FID);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 + S518  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 lines540    </label>541    <input type="checkbox" name="showArrows"542           id="showArrows" style="margin-left: 10px" />543    <label for="showArrows">544       Show control flow arrows545    </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 full557  // working directory if we have no directory information.  This is558  // 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 572  R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),573                     generateKeyboardNavigationJavascript());574 575  R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),576                     generateArrowDrawingJavascript());577 578  // Checkbox and javascript for filtering the output to the counterexample.579  R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),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       << LineNumber595       << ", column "596       << ColumnNumber597       << "</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.650  {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();663    FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()664                                             ? UPDLoc.asLocation()665                                             : D.getLocation().asLocation()),666                    SMgr);667 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 "679       << D.getIssueHash(PP.getSourceManager(), PP.getLangOpts()) << " -->\n";680 681    os << "\n<!-- BUGLINE "682       << LineNumber683       << " -->\n";684 685    os << "\n<!-- BUGCOLUMN "686      << ColumnNumber687      << " -->\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 698  html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry.getName());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 void734HandlePopUpPieceStartTag(Rewriter &R,735                         const std::vector<SourceRange> &PopUpRanges) {736  for (const auto &Range : PopUpRanges) {737    if (!shouldDisplayPopUpRange(Range))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) {751  SmallString<256> Buf;752  llvm::raw_svector_ostream Out(Buf);753 754  SourceRange Range(Piece.getLocation().asRange());755  if (!shouldDisplayPopUpRange(Range))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 that839  // 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 the841  // 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 count858      // 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 message860      // (list element) as a row. The <table> start tag will be added after the861      // 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 the894  // warning.895  FullSourceLoc Pos = P.getLocation().asLocation();896 897  if (!Pos.isValid())898    return;899 900  SourceManager &SM = R.getSourceMgr();901  assert(&Pos.getManager() == &SM && "SourceManagers are different!");902  FileIDAndOffset 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 start911  // 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;937  case PathDiagnosticPiece::Note:938    Kind = "Note";939    IsNote = true;940    SuppressIndex = true;941    break;942  case PathDiagnosticPiece::Call:943  case PathDiagnosticPiece::PopUp: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  else957    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  else1012    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      FileIDAndOffset 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        else1061          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        else1083          os << "Path" << (num + 1);1084        os << "\" title=\"Next event ("1085           << (num + 1)1086           << ")\">&#x2192;</a></div></td>";1087      }1088 1089      os << "</tr></table>";1090    }1091  }1092 1093  os << "</div></td></tr>";1094 1095  // Insert the new html.1096  unsigned DisplayPos = LineEnd - FileStart;1097  SourceLocation Loc =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,1124                                            const PathDiagnosticMacroPiece& P,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: visible1162  }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 1210  R.InsertTextBefore(R.getSourceMgr().getLocForStartOfFile(BugFileID),1211                     OS.str());1212}1213 1214static std::string getSpanBeginForControl(const char *ClassName,1215                                          unsigned Index) {1216  std::string Result;1217  llvm::raw_string_ostream OS(Result);1218  OS << "<span id=\"" << ClassName << Index << "\">";1219  return Result;1220}1221 1222static std::string getSpanBeginForControlStart(unsigned Index) {1223  return getSpanBeginForControl("start", Index);1224}1225 1226static std::string getSpanBeginForControlEnd(unsigned Index) {1227  return getSpanBeginForControl("end", Index);1228}1229 1230unsigned HTMLDiagnostics::ProcessControlFlowPiece(1231    Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P,1232    unsigned Number) {1233  for (const PathDiagnosticLocationPair &LPair : P) {1234    std::string Start = getSpanBeginForControlStart(Number),1235                End = getSpanBeginForControlEnd(Number++);1236 1237    HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),1238                   Start.c_str());1239    HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),1240                   End.c_str());1241  }1242 1243  return Number;1244}1245 1246void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,1247                                     SourceRange Range,1248                                     const char *HighlightStart,1249                                     const char *HighlightEnd) {1250  SourceManager &SM = R.getSourceMgr();1251  const LangOptions &LangOpts = R.getLangOpts();1252 1253  SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());1254  unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);1255 1256  SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());1257  unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);1258 1259  if (EndLineNo < StartLineNo)1260    return;1261 1262  if (SM.getFileID(InstantiationStart) != BugFileID ||1263      SM.getFileID(InstantiationEnd) != BugFileID)1264    return;1265 1266  // Compute the column number of the end.1267  unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);1268  unsigned OldEndColNo = EndColNo;1269 1270  if (EndColNo) {1271    // Add in the length of the token, so that we cover multi-char tokens.1272    EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;1273  }1274 1275  // Highlight the range.  Make the span tag the outermost tag for the1276  // selected range.1277 1278  SourceLocation E =1279    InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);1280 1281  html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);1282}1283 1284StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {1285  return R"<<<(1286<script type='text/javascript'>1287var digitMatcher = new RegExp("[0-9]+");1288 1289var querySelectorAllArray = function(selector) {1290  return Array.prototype.slice.call(1291    document.querySelectorAll(selector));1292}1293 1294document.addEventListener("DOMContentLoaded", function() {1295    querySelectorAllArray(".PathNav > a").forEach(1296        function(currentValue, currentIndex) {1297            var hrefValue = currentValue.getAttribute("href");1298            currentValue.onclick = function() {1299                scrollTo(document.querySelector(hrefValue));1300                return false;1301            };1302        });1303});1304 1305var findNum = function() {1306    var s = document.querySelector(".msg.selected");1307    if (!s || s.id == "EndPath") {1308        return 0;1309    }1310    var out = parseInt(digitMatcher.exec(s.id)[0]);1311    return out;1312};1313 1314var classListAdd = function(el, theClass) {1315  if(!el.className.baseVal)1316    el.className += " " + theClass;1317  else1318    el.className.baseVal += " " + theClass;1319};1320 1321var classListRemove = function(el, theClass) {1322  var className = (!el.className.baseVal) ?1323      el.className : el.className.baseVal;1324    className = className.replace(" " + theClass, "");1325  if(!el.className.baseVal)1326    el.className = className;1327  else1328    el.className.baseVal = className;1329};1330 1331var scrollTo = function(el) {1332    querySelectorAllArray(".selected").forEach(function(s) {1333      classListRemove(s, "selected");1334    });1335    classListAdd(el, "selected");1336    window.scrollBy(0, el.getBoundingClientRect().top -1337        (window.innerHeight / 2));1338    highlightArrowsForSelectedEvent();1339};1340 1341var move = function(num, up, numItems) {1342  if (num == 1 && up || num == numItems - 1 && !up) {1343    return 0;1344  } else if (num == 0 && up) {1345    return numItems - 1;1346  } else if (num == 0 && !up) {1347    return 1 % numItems;1348  }1349  return up ? num - 1 : num + 1;1350}1351 1352var numToId = function(num) {1353  if (num == 0) {1354    return document.getElementById("EndPath")1355  }1356  return document.getElementById("Path" + num);1357};1358 1359var navigateTo = function(up) {1360  var numItems = document.querySelectorAll(1361      ".line > .msgEvent, .line > .msgControl").length;1362  var currentSelected = findNum();1363  var newSelected = move(currentSelected, up, numItems);1364  var newEl = numToId(newSelected, numItems);1365 1366  // Scroll element into center.1367  scrollTo(newEl);1368};1369 1370window.addEventListener("keydown", function (event) {1371  if (event.defaultPrevented) {1372    return;1373  }1374  // key 'j'1375  if (event.keyCode == 74) {1376    navigateTo(/*up=*/false);1377  // key 'k'1378  } else if (event.keyCode == 75) {1379    navigateTo(/*up=*/true);1380  } else {1381    return;1382  }1383  event.preventDefault();1384}, true);1385</script>1386  )<<<";1387}1388 1389StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {1390  return R"<<<(1391<script type='text/javascript'>1392// Return range of numbers from a range [lower, upper).1393function range(lower, upper) {1394  var array = [];1395  for (var i = lower; i <= upper; ++i) {1396      array.push(i);1397  }1398  return array;1399}1400 1401var getRelatedArrowIndices = function(pathId) {1402  // HTML numeration of events is a bit different than it is in the path.1403  // Everything is rotated one step to the right, so the last element1404  // (error diagnostic) has index 0.1405  if (pathId == 0) {1406    // arrowIndices has at least 2 elements1407    pathId = arrowIndices.length - 1;1408  }1409 1410  return range(arrowIndices[pathId], arrowIndices[pathId - 1]);1411}1412 1413var highlightArrowsForSelectedEvent = function() {1414  const selectedNum = findNum();1415  const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);1416  arrowIndicesToHighlight.forEach((index) => {1417    var arrow = document.querySelector("#arrow" + index);1418    if(arrow) {1419      classListAdd(arrow, "selected")1420    }1421  });1422}1423 1424var getAbsoluteBoundingRect = function(element) {1425  const relative = element.getBoundingClientRect();1426  return {1427    left: relative.left + window.pageXOffset,1428    right: relative.right + window.pageXOffset,1429    top: relative.top + window.pageYOffset,1430    bottom: relative.bottom + window.pageYOffset,1431    height: relative.height,1432    width: relative.width1433  };1434}1435 1436var drawArrow = function(index) {1437  // This function is based on the great answer from SO:1438  //   https://stackoverflow.com/a/39575674/115823261439  var start = document.querySelector("#start" + index);1440  var end   = document.querySelector("#end" + index);1441  var arrow = document.querySelector("#arrow" + index);1442 1443  var startRect = getAbsoluteBoundingRect(start);1444  var endRect   = getAbsoluteBoundingRect(end);1445 1446  // It is an arrow from a token to itself, no need to visualize it.1447  if (startRect.top == endRect.top &&1448      startRect.left == endRect.left)1449    return;1450 1451  // Each arrow is a very simple Bézier curve, with two nodes and1452  // two handles.  So, we need to calculate four points in the window:1453  //   * start node1454  var posStart    = { x: 0, y: 0 };1455  //   * end node1456  var posEnd      = { x: 0, y: 0 };1457  //   * handle for the start node1458  var startHandle = { x: 0, y: 0 };1459  //   * handle for the end node1460  var endHandle   = { x: 0, y: 0 };1461  // One can visualize it as follows:1462  //1463  //         start handle1464  //        /1465  //       X"""_.-""""X1466  //         .'        \1467  //        /           start node1468  //       |1469  //       |1470  //       |      end node1471  //        \    /1472  //         `->X1473  //        X-'1474  //         \1475  //          end handle1476  //1477  // NOTE: (0, 0) is the top left corner of the window.1478 1479  // We have 3 similar, but still different scenarios to cover:1480  //1481  //   1. Two tokens on different lines.1482  //             -xxx1483  //           /1484  //           \1485  //             -> xxx1486  //      In this situation, we draw arrow on the left curving to the left.1487  //   2. Two tokens on the same line, and the destination is on the right.1488  //             ____1489  //            /    \1490  //           /      V1491  //        xxx        xxx1492  //      In this situation, we draw arrow above curving upwards.1493  //   3. Two tokens on the same line, and the destination is on the left.1494  //        xxx        xxx1495  //           ^      /1496  //            \____/1497  //      In this situation, we draw arrow below curving downwards.1498  const onDifferentLines = startRect.top <= endRect.top - 5 ||1499    startRect.top >= endRect.top + 5;1500  const leftToRight = startRect.left < endRect.left;1501 1502  // NOTE: various magic constants are chosen empirically for1503  //       better positioning and look1504  if (onDifferentLines) {1505    // Case #11506    const topToBottom = startRect.top < endRect.top;1507    posStart.x = startRect.left - 1;1508    // We don't want to start it at the top left corner of the token,1509    // it doesn't feel like this is where the arrow comes from.1510    // For this reason, we start it in the middle of the left side1511    // of the token.1512    posStart.y = startRect.top + startRect.height / 2;1513 1514    // End node has arrow head and we give it a bit more space.1515    posEnd.x = endRect.left - 4;1516    posEnd.y = endRect.top;1517 1518    // Utility object with x and y offsets for handles.1519    var curvature = {1520      // We want bottom-to-top arrow to curve a bit more, so it doesn't1521      // overlap much with top-to-bottom curves (much more frequent).1522      x: topToBottom ? 15 : 25,1523      y: Math.min((posEnd.y - posStart.y) / 3, 10)1524    }1525 1526    // When destination is on the different line, we can make a1527    // curvier arrow because we have space for it.1528    // So, instead of using1529    //1530    //   startHandle.x = posStart.x - curvature.x1531    //   endHandle.x   = posEnd.x - curvature.x1532    //1533    // We use the leftmost of these two values for both handles.1534    startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;1535    endHandle.x = startHandle.x;1536 1537    // Curving downwards from the start node...1538    startHandle.y = posStart.y + curvature.y;1539    // ... and upwards from the end node.1540    endHandle.y = posEnd.y - curvature.y;1541 1542  } else if (leftToRight) {1543    // Case #21544    // Starting from the top right corner...1545    posStart.x = startRect.right - 1;1546    posStart.y = startRect.top;1547 1548    // ...and ending at the top left corner of the end token.1549    posEnd.x = endRect.left + 1;1550    posEnd.y = endRect.top - 1;1551 1552    // Utility object with x and y offsets for handles.1553    var curvature = {1554      x: Math.min((posEnd.x - posStart.x) / 3, 15),1555      y: 51556    }1557 1558    // Curving to the right...1559    startHandle.x = posStart.x + curvature.x;1560    // ... and upwards from the start node.1561    startHandle.y = posStart.y - curvature.y;1562 1563    // And to the left...1564    endHandle.x = posEnd.x - curvature.x;1565    // ... and upwards from the end node.1566    endHandle.y = posEnd.y - curvature.y;1567 1568  } else {1569    // Case #31570    // Starting from the bottom right corner...1571    posStart.x = startRect.right;1572    posStart.y = startRect.bottom;1573 1574    // ...and ending also at the bottom right corner, but of the end token.1575    posEnd.x = endRect.right - 1;1576    posEnd.y = endRect.bottom + 1;1577 1578    // Utility object with x and y offsets for handles.1579    var curvature = {1580      x: Math.min((posStart.x - posEnd.x) / 3, 15),1581      y: 51582    }1583 1584    // Curving to the left...1585    startHandle.x = posStart.x - curvature.x;1586    // ... and downwards from the start node.1587    startHandle.y = posStart.y + curvature.y;1588 1589    // And to the right...1590    endHandle.x = posEnd.x + curvature.x;1591    // ... and downwards from the end node.1592    endHandle.y = posEnd.y + curvature.y;1593  }1594 1595  // Put it all together into a path.1596  // More information on the format:1597  //   https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths1598  var pathStr = "M" + posStart.x + "," + posStart.y + " " +1599    "C" + startHandle.x + "," + startHandle.y + " " +1600    endHandle.x + "," + endHandle.y + " " +1601    posEnd.x + "," + posEnd.y;1602 1603  arrow.setAttribute("d", pathStr);1604};1605 1606var drawArrows = function() {1607  const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;1608  for (var i = 0; i < numOfArrows; ++i) {1609    drawArrow(i);1610  }1611}1612 1613var toggleArrows = function(event) {1614  const arrows = document.querySelector("#arrows");1615  if (event.target.checked) {1616    arrows.setAttribute("visibility", "visible");1617  } else {1618    arrows.setAttribute("visibility", "hidden");1619  }1620}1621 1622window.addEventListener("resize", drawArrows);1623document.addEventListener("DOMContentLoaded", function() {1624  // Whenever we show invocation, locations change, i.e. we1625  // need to redraw arrows.1626  document1627    .querySelector('input[id="showinvocation"]')1628    .addEventListener("click", drawArrows);1629  // Hiding irrelevant lines also should cause arrow rerender.1630  document1631    .querySelector('input[name="showCounterexample"]')1632    .addEventListener("change", drawArrows);1633  document1634    .querySelector('input[name="showArrows"]')1635    .addEventListener("change", toggleArrows);1636  drawArrows();1637  // Default highlighting for the last event.1638  highlightArrowsForSelectedEvent();1639});1640</script>1641  )<<<";1642}1643