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