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 << "\">←</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 << "\">→</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 << ")\">←</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 << ")\">→</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 << ")\">→</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