xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
10b57cec5SDimitry Andric //===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric //  This file defines the HTMLDiagnostics object.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
130b57cec5SDimitry Andric #include "clang/AST/Decl.h"
140b57cec5SDimitry Andric #include "clang/AST/DeclBase.h"
150b57cec5SDimitry Andric #include "clang/AST/Stmt.h"
16fe6060f1SDimitry Andric #include "clang/Analysis/IssueHash.h"
17fe6060f1SDimitry Andric #include "clang/Analysis/MacroExpansionContext.h"
18fe6060f1SDimitry Andric #include "clang/Analysis/PathDiagnostic.h"
190b57cec5SDimitry Andric #include "clang/Basic/FileManager.h"
200b57cec5SDimitry Andric #include "clang/Basic/LLVM.h"
210b57cec5SDimitry Andric #include "clang/Basic/SourceLocation.h"
220b57cec5SDimitry Andric #include "clang/Basic/SourceManager.h"
230b57cec5SDimitry Andric #include "clang/Lex/Lexer.h"
240b57cec5SDimitry Andric #include "clang/Lex/Preprocessor.h"
250b57cec5SDimitry Andric #include "clang/Lex/Token.h"
260b57cec5SDimitry Andric #include "clang/Rewrite/Core/HTMLRewrite.h"
270b57cec5SDimitry Andric #include "clang/Rewrite/Core/Rewriter.h"
280b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
290b57cec5SDimitry Andric #include "llvm/ADT/ArrayRef.h"
30349cc55cSDimitry Andric #include "llvm/ADT/STLExtras.h"
31349cc55cSDimitry Andric #include "llvm/ADT/Sequence.h"
320b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h"
330b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
340b57cec5SDimitry Andric #include "llvm/ADT/iterator_range.h"
350b57cec5SDimitry Andric #include "llvm/Support/Casting.h"
360b57cec5SDimitry Andric #include "llvm/Support/Errc.h"
370b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h"
380b57cec5SDimitry Andric #include "llvm/Support/FileSystem.h"
390b57cec5SDimitry Andric #include "llvm/Support/MemoryBuffer.h"
400b57cec5SDimitry Andric #include "llvm/Support/Path.h"
410b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
420b57cec5SDimitry Andric #include <algorithm>
430b57cec5SDimitry Andric #include <cassert>
440b57cec5SDimitry Andric #include <map>
450b57cec5SDimitry Andric #include <memory>
460b57cec5SDimitry Andric #include <set>
470b57cec5SDimitry Andric #include <sstream>
480b57cec5SDimitry Andric #include <string>
490b57cec5SDimitry Andric #include <system_error>
500b57cec5SDimitry Andric #include <utility>
510b57cec5SDimitry Andric #include <vector>
520b57cec5SDimitry Andric 
530b57cec5SDimitry Andric using namespace clang;
540b57cec5SDimitry Andric using namespace ento;
550b57cec5SDimitry Andric 
560b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
570b57cec5SDimitry Andric // Boilerplate.
580b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
590b57cec5SDimitry Andric 
600b57cec5SDimitry Andric namespace {
610b57cec5SDimitry Andric 
62349cc55cSDimitry Andric class ArrowMap;
63349cc55cSDimitry Andric 
640b57cec5SDimitry Andric class HTMLDiagnostics : public PathDiagnosticConsumer {
65e8d8bef9SDimitry Andric   PathDiagnosticConsumerOptions DiagOpts;
660b57cec5SDimitry Andric   std::string Directory;
670b57cec5SDimitry Andric   bool createdDir = false;
680b57cec5SDimitry Andric   bool noDir = false;
690b57cec5SDimitry Andric   const Preprocessor &PP;
700b57cec5SDimitry Andric   const bool SupportsCrossFileDiagnostics;
717a6dacacSDimitry Andric   llvm::StringSet<> EmittedHashes;
72*0fca6ea1SDimitry Andric   html::RelexRewriteCacheRef RewriterCache =
73*0fca6ea1SDimitry Andric       html::instantiateRelexRewriteCache();
740b57cec5SDimitry Andric 
750b57cec5SDimitry Andric public:
76e8d8bef9SDimitry Andric   HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,
77e8d8bef9SDimitry Andric                   const std::string &OutputDir, const Preprocessor &pp,
78e8d8bef9SDimitry Andric                   bool supportsMultipleFiles)
79e8d8bef9SDimitry Andric       : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),
800b57cec5SDimitry Andric         SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
810b57cec5SDimitry Andric 
820b57cec5SDimitry Andric   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
830b57cec5SDimitry Andric 
840b57cec5SDimitry Andric   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
850b57cec5SDimitry Andric                             FilesMade *filesMade) override;
860b57cec5SDimitry Andric 
87349cc55cSDimitry Andric   StringRef getName() const override { return "HTMLDiagnostics"; }
880b57cec5SDimitry Andric 
890b57cec5SDimitry Andric   bool supportsCrossFileDiagnostics() const override {
900b57cec5SDimitry Andric     return SupportsCrossFileDiagnostics;
910b57cec5SDimitry Andric   }
920b57cec5SDimitry Andric 
93349cc55cSDimitry Andric   unsigned ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece &P,
940b57cec5SDimitry Andric                              unsigned num);
950b57cec5SDimitry Andric 
96349cc55cSDimitry Andric   unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID,
97349cc55cSDimitry Andric                                    const PathDiagnosticControlFlowPiece &P,
98349cc55cSDimitry Andric                                    unsigned Number);
99349cc55cSDimitry Andric 
1000b57cec5SDimitry Andric   void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P,
1010b57cec5SDimitry Andric                    const std::vector<SourceRange> &PopUpRanges, unsigned num,
1020b57cec5SDimitry Andric                    unsigned max);
1030b57cec5SDimitry Andric 
1040b57cec5SDimitry Andric   void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range,
1050b57cec5SDimitry Andric                       const char *HighlightStart = "<span class=\"mrange\">",
1060b57cec5SDimitry Andric                       const char *HighlightEnd = "</span>");
1070b57cec5SDimitry Andric 
108349cc55cSDimitry Andric   void ReportDiag(const PathDiagnostic &D, FilesMade *filesMade);
1090b57cec5SDimitry Andric 
1100b57cec5SDimitry Andric   // Generate the full HTML report
1110b57cec5SDimitry Andric   std::string GenerateHTML(const PathDiagnostic &D, Rewriter &R,
1120b57cec5SDimitry Andric                            const SourceManager &SMgr, const PathPieces &path,
1130b57cec5SDimitry Andric                            const char *declName);
1140b57cec5SDimitry Andric 
1150b57cec5SDimitry Andric   // Add HTML header/footers to file specified by FID
1160b57cec5SDimitry Andric   void FinalizeHTML(const PathDiagnostic &D, Rewriter &R,
1170b57cec5SDimitry Andric                     const SourceManager &SMgr, const PathPieces &path,
1185f757f3fSDimitry Andric                     FileID FID, FileEntryRef Entry, const char *declName);
1190b57cec5SDimitry Andric 
1200b57cec5SDimitry Andric   // Rewrite the file specified by FID with HTML formatting.
1210b57cec5SDimitry Andric   void RewriteFile(Rewriter &R, const PathPieces &path, FileID FID);
1220b57cec5SDimitry Andric 
123349cc55cSDimitry Andric   PathGenerationScheme getGenerationScheme() const override {
124349cc55cSDimitry Andric     return Everything;
125349cc55cSDimitry Andric   }
1260b57cec5SDimitry Andric 
1270b57cec5SDimitry Andric private:
128349cc55cSDimitry Andric   void addArrowSVGs(Rewriter &R, FileID BugFileID,
129349cc55cSDimitry Andric                     const ArrowMap &ArrowIndices);
130349cc55cSDimitry Andric 
1310b57cec5SDimitry Andric   /// \return Javascript for displaying shortcuts help;
1320b57cec5SDimitry Andric   StringRef showHelpJavascript();
1330b57cec5SDimitry Andric 
1340b57cec5SDimitry Andric   /// \return Javascript for navigating the HTML report using j/k keys.
1350b57cec5SDimitry Andric   StringRef generateKeyboardNavigationJavascript();
1360b57cec5SDimitry Andric 
137349cc55cSDimitry Andric   /// \return Javascript for drawing control-flow arrows.
138349cc55cSDimitry Andric   StringRef generateArrowDrawingJavascript();
139349cc55cSDimitry Andric 
1400b57cec5SDimitry Andric   /// \return JavaScript for an option to only show relevant lines.
141349cc55cSDimitry Andric   std::string showRelevantLinesJavascript(const PathDiagnostic &D,
142349cc55cSDimitry Andric                                           const PathPieces &path);
1430b57cec5SDimitry Andric 
1440b57cec5SDimitry Andric   /// Write executed lines from \p D in JSON format into \p os.
145349cc55cSDimitry Andric   void dumpCoverageData(const PathDiagnostic &D, const PathPieces &path,
1460b57cec5SDimitry Andric                         llvm::raw_string_ostream &os);
1470b57cec5SDimitry Andric };
1480b57cec5SDimitry Andric 
149349cc55cSDimitry Andric bool isArrowPiece(const PathDiagnosticPiece &P) {
150349cc55cSDimitry Andric   return isa<PathDiagnosticControlFlowPiece>(P) && P.getString().empty();
151349cc55cSDimitry Andric }
152349cc55cSDimitry Andric 
153349cc55cSDimitry Andric unsigned getPathSizeWithoutArrows(const PathPieces &Path) {
154349cc55cSDimitry Andric   unsigned TotalPieces = Path.size();
155349cc55cSDimitry Andric   unsigned TotalArrowPieces = llvm::count_if(
156349cc55cSDimitry Andric       Path, [](const PathDiagnosticPieceRef &P) { return isArrowPiece(*P); });
157349cc55cSDimitry Andric   return TotalPieces - TotalArrowPieces;
158349cc55cSDimitry Andric }
159349cc55cSDimitry Andric 
160349cc55cSDimitry Andric class ArrowMap : public std::vector<unsigned> {
161349cc55cSDimitry Andric   using Base = std::vector<unsigned>;
162349cc55cSDimitry Andric 
163349cc55cSDimitry Andric public:
164349cc55cSDimitry Andric   ArrowMap(unsigned Size) : Base(Size, 0) {}
165349cc55cSDimitry Andric   unsigned getTotalNumberOfArrows() const { return at(0); }
166349cc55cSDimitry Andric };
167349cc55cSDimitry Andric 
168349cc55cSDimitry Andric llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ArrowMap &Indices) {
169349cc55cSDimitry Andric   OS << "[ ";
170349cc55cSDimitry Andric   llvm::interleave(Indices, OS, ",");
171349cc55cSDimitry Andric   return OS << " ]";
172349cc55cSDimitry Andric }
173349cc55cSDimitry Andric 
1740b57cec5SDimitry Andric } // namespace
1750b57cec5SDimitry Andric 
176a7dea167SDimitry Andric void ento::createHTMLDiagnosticConsumer(
177e8d8bef9SDimitry Andric     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
1785ffd83dbSDimitry Andric     const std::string &OutputDir, const Preprocessor &PP,
179fe6060f1SDimitry Andric     const cross_tu::CrossTranslationUnitContext &CTU,
180fe6060f1SDimitry Andric     const MacroExpansionContext &MacroExpansions) {
1815ffd83dbSDimitry Andric 
1825ffd83dbSDimitry Andric   // FIXME: HTML is currently our default output type, but if the output
1835ffd83dbSDimitry Andric   // directory isn't specified, it acts like if it was in the minimal text
1845ffd83dbSDimitry Andric   // output mode. This doesn't make much sense, we should have the minimal text
1855ffd83dbSDimitry Andric   // as our default. In the case of backward compatibility concerns, this could
1865ffd83dbSDimitry Andric   // be preserved with -analyzer-config-compatibility-mode=true.
187fe6060f1SDimitry Andric   createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
188fe6060f1SDimitry Andric                                           MacroExpansions);
1895ffd83dbSDimitry Andric 
1905ffd83dbSDimitry Andric   // TODO: Emit an error here.
1915ffd83dbSDimitry Andric   if (OutputDir.empty())
1925ffd83dbSDimitry Andric     return;
1935ffd83dbSDimitry Andric 
194e8d8bef9SDimitry Andric   C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, true));
1950b57cec5SDimitry Andric }
1960b57cec5SDimitry Andric 
197a7dea167SDimitry Andric void ento::createHTMLSingleFileDiagnosticConsumer(
198e8d8bef9SDimitry Andric     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
1995ffd83dbSDimitry Andric     const std::string &OutputDir, const Preprocessor &PP,
200fe6060f1SDimitry Andric     const cross_tu::CrossTranslationUnitContext &CTU,
201fe6060f1SDimitry Andric     const clang::MacroExpansionContext &MacroExpansions) {
202fe6060f1SDimitry Andric   createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
203fe6060f1SDimitry Andric                                           MacroExpansions);
2045ffd83dbSDimitry Andric 
2055ffd83dbSDimitry Andric   // TODO: Emit an error here.
2065ffd83dbSDimitry Andric   if (OutputDir.empty())
2075ffd83dbSDimitry Andric     return;
2085ffd83dbSDimitry Andric 
209e8d8bef9SDimitry Andric   C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, false));
2105ffd83dbSDimitry Andric }
2115ffd83dbSDimitry Andric 
2125ffd83dbSDimitry Andric void ento::createPlistHTMLDiagnosticConsumer(
213e8d8bef9SDimitry Andric     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
214a7dea167SDimitry Andric     const std::string &prefix, const Preprocessor &PP,
215fe6060f1SDimitry Andric     const cross_tu::CrossTranslationUnitContext &CTU,
216fe6060f1SDimitry Andric     const MacroExpansionContext &MacroExpansions) {
2175ffd83dbSDimitry Andric   createHTMLDiagnosticConsumer(
218fe6060f1SDimitry Andric       DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, CTU,
219fe6060f1SDimitry Andric       MacroExpansions);
220fe6060f1SDimitry Andric   createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU,
221fe6060f1SDimitry Andric                                          MacroExpansions);
222e8d8bef9SDimitry Andric   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP,
223fe6060f1SDimitry Andric                                           CTU, MacroExpansions);
224fe6060f1SDimitry Andric }
225fe6060f1SDimitry Andric 
226fe6060f1SDimitry Andric void ento::createSarifHTMLDiagnosticConsumer(
227fe6060f1SDimitry Andric     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
228fe6060f1SDimitry Andric     const std::string &sarif_file, const Preprocessor &PP,
229fe6060f1SDimitry Andric     const cross_tu::CrossTranslationUnitContext &CTU,
230fe6060f1SDimitry Andric     const MacroExpansionContext &MacroExpansions) {
231fe6060f1SDimitry Andric   createHTMLDiagnosticConsumer(
232fe6060f1SDimitry Andric       DiagOpts, C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,
233fe6060f1SDimitry Andric       CTU, MacroExpansions);
234fe6060f1SDimitry Andric   createSarifDiagnosticConsumer(DiagOpts, C, sarif_file, PP, CTU,
235fe6060f1SDimitry Andric                                 MacroExpansions);
236fe6060f1SDimitry Andric   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, sarif_file,
237fe6060f1SDimitry Andric                                           PP, CTU, MacroExpansions);
2380b57cec5SDimitry Andric }
2390b57cec5SDimitry Andric 
2400b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
2410b57cec5SDimitry Andric // Report processing.
2420b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
2430b57cec5SDimitry Andric 
2440b57cec5SDimitry Andric void HTMLDiagnostics::FlushDiagnosticsImpl(
2450b57cec5SDimitry Andric   std::vector<const PathDiagnostic *> &Diags,
2460b57cec5SDimitry Andric   FilesMade *filesMade) {
2470b57cec5SDimitry Andric   for (const auto Diag : Diags)
2480b57cec5SDimitry Andric     ReportDiag(*Diag, filesMade);
2490b57cec5SDimitry Andric }
2500b57cec5SDimitry Andric 
251349cc55cSDimitry Andric static llvm::SmallString<32> getIssueHash(const PathDiagnostic &D,
252349cc55cSDimitry Andric                                           const Preprocessor &PP) {
253349cc55cSDimitry Andric   SourceManager &SMgr = PP.getSourceManager();
254349cc55cSDimitry Andric   PathDiagnosticLocation UPDLoc = D.getUniqueingLoc();
255349cc55cSDimitry Andric   FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()
256349cc55cSDimitry Andric                                            ? UPDLoc.asLocation()
257349cc55cSDimitry Andric                                            : D.getLocation().asLocation()),
258349cc55cSDimitry Andric                   SMgr);
259349cc55cSDimitry Andric   return getIssueHash(L, D.getCheckerName(), D.getBugType(),
260349cc55cSDimitry Andric                       D.getDeclWithIssue(), PP.getLangOpts());
261349cc55cSDimitry Andric }
262349cc55cSDimitry Andric 
2630b57cec5SDimitry Andric void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
2640b57cec5SDimitry Andric                                  FilesMade *filesMade) {
2650b57cec5SDimitry Andric   // Create the HTML directory if it is missing.
2660b57cec5SDimitry Andric   if (!createdDir) {
2670b57cec5SDimitry Andric     createdDir = true;
2680b57cec5SDimitry Andric     if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
2690b57cec5SDimitry Andric       llvm::errs() << "warning: could not create directory '"
2700b57cec5SDimitry Andric                    << Directory << "': " << ec.message() << '\n';
2710b57cec5SDimitry Andric       noDir = true;
2720b57cec5SDimitry Andric       return;
2730b57cec5SDimitry Andric     }
2740b57cec5SDimitry Andric   }
2750b57cec5SDimitry Andric 
2760b57cec5SDimitry Andric   if (noDir)
2770b57cec5SDimitry Andric     return;
2780b57cec5SDimitry Andric 
2790b57cec5SDimitry Andric   // First flatten out the entire path to make it easier to use.
2800b57cec5SDimitry Andric   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
2810b57cec5SDimitry Andric 
2820b57cec5SDimitry Andric   // The path as already been prechecked that the path is non-empty.
2830b57cec5SDimitry Andric   assert(!path.empty());
2840b57cec5SDimitry Andric   const SourceManager &SMgr = path.front()->getLocation().getManager();
2850b57cec5SDimitry Andric 
2860b57cec5SDimitry Andric   // Create a new rewriter to generate HTML.
2870b57cec5SDimitry Andric   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
2880b57cec5SDimitry Andric 
2890b57cec5SDimitry Andric   // Get the function/method name
2900b57cec5SDimitry Andric   SmallString<128> declName("unknown");
2910b57cec5SDimitry Andric   int offsetDecl = 0;
2920b57cec5SDimitry Andric   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
2930b57cec5SDimitry Andric       if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
2940b57cec5SDimitry Andric           declName = ND->getDeclName().getAsString();
2950b57cec5SDimitry Andric 
2960b57cec5SDimitry Andric       if (const Stmt *Body = DeclWithIssue->getBody()) {
2970b57cec5SDimitry Andric           // Retrieve the relative position of the declaration which will be used
2980b57cec5SDimitry Andric           // for the file name
2990b57cec5SDimitry Andric           FullSourceLoc L(
3000b57cec5SDimitry Andric               SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),
3010b57cec5SDimitry Andric               SMgr);
3020b57cec5SDimitry Andric           FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);
3030b57cec5SDimitry Andric           offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
3040b57cec5SDimitry Andric       }
3050b57cec5SDimitry Andric   }
3060b57cec5SDimitry Andric 
3077a6dacacSDimitry Andric   SmallString<32> IssueHash = getIssueHash(D, PP);
3087a6dacacSDimitry Andric   auto [It, IsNew] = EmittedHashes.insert(IssueHash);
3097a6dacacSDimitry Andric   if (!IsNew) {
3107a6dacacSDimitry Andric     // We've already emitted a duplicate issue. It'll get overwritten anyway.
3117a6dacacSDimitry Andric     return;
3127a6dacacSDimitry Andric   }
3137a6dacacSDimitry Andric 
3140b57cec5SDimitry Andric   std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
3150b57cec5SDimitry Andric   if (report.empty()) {
3160b57cec5SDimitry Andric     llvm::errs() << "warning: no diagnostics generated for main file.\n";
3170b57cec5SDimitry Andric     return;
3180b57cec5SDimitry Andric   }
3190b57cec5SDimitry Andric 
3200b57cec5SDimitry Andric   // Create a path for the target HTML file.
3210b57cec5SDimitry Andric   int FD;
3220b57cec5SDimitry Andric 
323349cc55cSDimitry Andric   SmallString<128> FileNameStr;
324349cc55cSDimitry Andric   llvm::raw_svector_ostream FileName(FileNameStr);
325349cc55cSDimitry Andric   FileName << "report-";
326349cc55cSDimitry Andric 
327349cc55cSDimitry Andric   // Historically, neither the stable report filename nor the unstable report
328349cc55cSDimitry Andric   // filename were actually stable. That said, the stable report filename
329349cc55cSDimitry Andric   // was more stable because it was mostly composed of information
330349cc55cSDimitry Andric   // about the bug report instead of being completely random.
331349cc55cSDimitry Andric   // Now both stable and unstable report filenames are in fact stable
332349cc55cSDimitry Andric   // but the stable report filename is still more verbose.
333349cc55cSDimitry Andric   if (DiagOpts.ShouldWriteVerboseReportFilename) {
334349cc55cSDimitry Andric     // FIXME: This code relies on knowing what constitutes the issue hash.
335349cc55cSDimitry Andric     // Otherwise deduplication won't work correctly.
336349cc55cSDimitry Andric     FileID ReportFile =
337349cc55cSDimitry Andric         path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
338349cc55cSDimitry Andric 
3395f757f3fSDimitry Andric     OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(ReportFile);
340349cc55cSDimitry Andric 
341349cc55cSDimitry Andric     FileName << llvm::sys::path::filename(Entry->getName()).str() << "-"
342349cc55cSDimitry Andric              << declName.c_str() << "-" << offsetDecl << "-";
343349cc55cSDimitry Andric   }
344349cc55cSDimitry Andric 
3457a6dacacSDimitry Andric   FileName << StringRef(IssueHash).substr(0, 6).str() << ".html";
346349cc55cSDimitry Andric 
347349cc55cSDimitry Andric   SmallString<128> ResultPath;
348349cc55cSDimitry Andric   llvm::sys::path::append(ResultPath, Directory, FileName.str());
349349cc55cSDimitry Andric   if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) {
350349cc55cSDimitry Andric     llvm::errs() << "warning: could not make '" << ResultPath
3510b57cec5SDimitry Andric                  << "' absolute: " << EC.message() << '\n';
3520b57cec5SDimitry Andric     return;
3530b57cec5SDimitry Andric   }
354349cc55cSDimitry Andric 
355349cc55cSDimitry Andric   if (std::error_code EC = llvm::sys::fs::openFileForReadWrite(
356349cc55cSDimitry Andric           ResultPath, FD, llvm::sys::fs::CD_CreateNew,
357349cc55cSDimitry Andric           llvm::sys::fs::OF_Text)) {
358349cc55cSDimitry Andric     // Existence of the file corresponds to the situation where a different
359349cc55cSDimitry Andric     // Clang instance has emitted a bug report with the same issue hash.
360349cc55cSDimitry Andric     // This is an entirely normal situation that does not deserve a warning,
361349cc55cSDimitry Andric     // as apart from hash collisions this can happen because the reports
362349cc55cSDimitry Andric     // are in fact similar enough to be considered duplicates of each other.
363349cc55cSDimitry Andric     if (EC != llvm::errc::file_exists) {
3640b57cec5SDimitry Andric       llvm::errs() << "warning: could not create file in '" << Directory
3650b57cec5SDimitry Andric                    << "': " << EC.message() << '\n';
3660b57cec5SDimitry Andric     }
3670b57cec5SDimitry Andric     return;
3680b57cec5SDimitry Andric   }
3690b57cec5SDimitry Andric 
3700b57cec5SDimitry Andric   llvm::raw_fd_ostream os(FD, true);
3710b57cec5SDimitry Andric 
3720b57cec5SDimitry Andric   if (filesMade)
3730b57cec5SDimitry Andric     filesMade->addDiagnostic(D, getName(),
3740b57cec5SDimitry Andric                              llvm::sys::path::filename(ResultPath));
3750b57cec5SDimitry Andric 
3760b57cec5SDimitry Andric   // Emit the HTML to disk.
3770b57cec5SDimitry Andric   os << report;
3780b57cec5SDimitry Andric }
3790b57cec5SDimitry Andric 
3800b57cec5SDimitry Andric std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
3810b57cec5SDimitry Andric     const SourceManager& SMgr, const PathPieces& path, const char *declName) {
3820b57cec5SDimitry Andric   // Rewrite source files as HTML for every new file the path crosses
3830b57cec5SDimitry Andric   std::vector<FileID> FileIDs;
3840b57cec5SDimitry Andric   for (auto I : path) {
3850b57cec5SDimitry Andric     FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
3860b57cec5SDimitry Andric     if (llvm::is_contained(FileIDs, FID))
3870b57cec5SDimitry Andric       continue;
3880b57cec5SDimitry Andric 
3890b57cec5SDimitry Andric     FileIDs.push_back(FID);
3900b57cec5SDimitry Andric     RewriteFile(R, path, FID);
3910b57cec5SDimitry Andric   }
3920b57cec5SDimitry Andric 
3930b57cec5SDimitry Andric   if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
3940b57cec5SDimitry Andric     // Prefix file names, anchor tags, and nav cursors to every file
3950b57cec5SDimitry Andric     for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
3960b57cec5SDimitry Andric       std::string s;
3970b57cec5SDimitry Andric       llvm::raw_string_ostream os(s);
3980b57cec5SDimitry Andric 
3990b57cec5SDimitry Andric       if (I != FileIDs.begin())
4000b57cec5SDimitry Andric         os << "<hr class=divider>\n";
4010b57cec5SDimitry Andric 
4020b57cec5SDimitry Andric       os << "<div id=File" << I->getHashValue() << ">\n";
4030b57cec5SDimitry Andric 
4040b57cec5SDimitry Andric       // Left nav arrow
4050b57cec5SDimitry Andric       if (I != FileIDs.begin())
4060b57cec5SDimitry Andric         os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
4070b57cec5SDimitry Andric            << "\">&#x2190;</a></div>";
4080b57cec5SDimitry Andric 
4095f757f3fSDimitry Andric       os << "<h4 class=FileName>" << SMgr.getFileEntryRefForID(*I)->getName()
4100b57cec5SDimitry Andric          << "</h4>\n";
4110b57cec5SDimitry Andric 
4120b57cec5SDimitry Andric       // Right nav arrow
4130b57cec5SDimitry Andric       if (I + 1 != E)
4140b57cec5SDimitry Andric         os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
4150b57cec5SDimitry Andric            << "\">&#x2192;</a></div>";
4160b57cec5SDimitry Andric 
4170b57cec5SDimitry Andric       os << "</div>\n";
4180b57cec5SDimitry Andric 
4190b57cec5SDimitry Andric       R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str());
4200b57cec5SDimitry Andric     }
4210b57cec5SDimitry Andric 
4220b57cec5SDimitry Andric     // Append files to the main report file in the order they appear in the path
4230eae32dcSDimitry Andric     for (auto I : llvm::drop_begin(FileIDs)) {
4240b57cec5SDimitry Andric       std::string s;
4250b57cec5SDimitry Andric       llvm::raw_string_ostream os(s);
4260b57cec5SDimitry Andric 
4270b57cec5SDimitry Andric       const RewriteBuffer *Buf = R.getRewriteBufferFor(I);
4280b57cec5SDimitry Andric       for (auto BI : *Buf)
4290b57cec5SDimitry Andric         os << BI;
4300b57cec5SDimitry Andric 
4310b57cec5SDimitry Andric       R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str());
4320b57cec5SDimitry Andric     }
4330b57cec5SDimitry Andric   }
4340b57cec5SDimitry Andric 
4350b57cec5SDimitry Andric   const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]);
4360b57cec5SDimitry Andric   if (!Buf)
4370b57cec5SDimitry Andric     return {};
4380b57cec5SDimitry Andric 
4390b57cec5SDimitry Andric   // Add CSS, header, and footer.
4400b57cec5SDimitry Andric   FileID FID =
4410b57cec5SDimitry Andric       path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
4425f757f3fSDimitry Andric   OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(FID);
4435f757f3fSDimitry Andric   FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName);
4440b57cec5SDimitry Andric 
4450b57cec5SDimitry Andric   std::string file;
4460b57cec5SDimitry Andric   llvm::raw_string_ostream os(file);
4470b57cec5SDimitry Andric   for (auto BI : *Buf)
4480b57cec5SDimitry Andric     os << BI;
4490b57cec5SDimitry Andric 
4500eae32dcSDimitry Andric   return file;
4510b57cec5SDimitry Andric }
4520b57cec5SDimitry Andric 
4530b57cec5SDimitry Andric void HTMLDiagnostics::dumpCoverageData(
4540b57cec5SDimitry Andric     const PathDiagnostic &D,
4550b57cec5SDimitry Andric     const PathPieces &path,
4560b57cec5SDimitry Andric     llvm::raw_string_ostream &os) {
4570b57cec5SDimitry Andric 
4580b57cec5SDimitry Andric   const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines();
4590b57cec5SDimitry Andric 
4600b57cec5SDimitry Andric   os << "var relevant_lines = {";
4610b57cec5SDimitry Andric   for (auto I = ExecutedLines.begin(),
4620b57cec5SDimitry Andric             E = ExecutedLines.end(); I != E; ++I) {
4630b57cec5SDimitry Andric     if (I != ExecutedLines.begin())
4640b57cec5SDimitry Andric       os << ", ";
4650b57cec5SDimitry Andric 
4660b57cec5SDimitry Andric     os << "\"" << I->first.getHashValue() << "\": {";
4670b57cec5SDimitry Andric     for (unsigned LineNo : I->second) {
4680b57cec5SDimitry Andric       if (LineNo != *(I->second.begin()))
4690b57cec5SDimitry Andric         os << ", ";
4700b57cec5SDimitry Andric 
4710b57cec5SDimitry Andric       os << "\"" << LineNo << "\": 1";
4720b57cec5SDimitry Andric     }
4730b57cec5SDimitry Andric     os << "}";
4740b57cec5SDimitry Andric   }
4750b57cec5SDimitry Andric 
4760b57cec5SDimitry Andric   os << "};";
4770b57cec5SDimitry Andric }
4780b57cec5SDimitry Andric 
4790b57cec5SDimitry Andric std::string HTMLDiagnostics::showRelevantLinesJavascript(
4800b57cec5SDimitry Andric       const PathDiagnostic &D, const PathPieces &path) {
4810b57cec5SDimitry Andric   std::string s;
4820b57cec5SDimitry Andric   llvm::raw_string_ostream os(s);
4830b57cec5SDimitry Andric   os << "<script type='text/javascript'>\n";
4840b57cec5SDimitry Andric   dumpCoverageData(D, path, os);
4850b57cec5SDimitry Andric   os << R"<<<(
4860b57cec5SDimitry Andric 
4870b57cec5SDimitry Andric var filterCounterexample = function (hide) {
4880b57cec5SDimitry Andric   var tables = document.getElementsByClassName("code");
4890b57cec5SDimitry Andric   for (var t=0; t<tables.length; t++) {
4900b57cec5SDimitry Andric     var table = tables[t];
4910b57cec5SDimitry Andric     var file_id = table.getAttribute("data-fileid");
4920b57cec5SDimitry Andric     var lines_in_fid = relevant_lines[file_id];
4930b57cec5SDimitry Andric     if (!lines_in_fid) {
4940b57cec5SDimitry Andric       lines_in_fid = {};
4950b57cec5SDimitry Andric     }
4960b57cec5SDimitry Andric     var lines = table.getElementsByClassName("codeline");
4970b57cec5SDimitry Andric     for (var i=0; i<lines.length; i++) {
4980b57cec5SDimitry Andric         var el = lines[i];
4990b57cec5SDimitry Andric         var lineNo = el.getAttribute("data-linenumber");
5000b57cec5SDimitry Andric         if (!lines_in_fid[lineNo]) {
5010b57cec5SDimitry Andric           if (hide) {
5020b57cec5SDimitry Andric             el.setAttribute("hidden", "");
5030b57cec5SDimitry Andric           } else {
5040b57cec5SDimitry Andric             el.removeAttribute("hidden");
5050b57cec5SDimitry Andric           }
5060b57cec5SDimitry Andric         }
5070b57cec5SDimitry Andric     }
5080b57cec5SDimitry Andric   }
5090b57cec5SDimitry Andric }
5100b57cec5SDimitry Andric 
5110b57cec5SDimitry Andric window.addEventListener("keydown", function (event) {
5120b57cec5SDimitry Andric   if (event.defaultPrevented) {
5130b57cec5SDimitry Andric     return;
5140b57cec5SDimitry Andric   }
515349cc55cSDimitry Andric   // SHIFT + S
516349cc55cSDimitry Andric   if (event.shiftKey && event.keyCode == 83) {
5170b57cec5SDimitry Andric     var checked = document.getElementsByName("showCounterexample")[0].checked;
5180b57cec5SDimitry Andric     filterCounterexample(!checked);
519349cc55cSDimitry Andric     document.getElementsByName("showCounterexample")[0].click();
5200b57cec5SDimitry Andric   } else {
5210b57cec5SDimitry Andric     return;
5220b57cec5SDimitry Andric   }
5230b57cec5SDimitry Andric   event.preventDefault();
5240b57cec5SDimitry Andric }, true);
5250b57cec5SDimitry Andric 
5260b57cec5SDimitry Andric document.addEventListener("DOMContentLoaded", function() {
5270b57cec5SDimitry Andric     document.querySelector('input[name="showCounterexample"]').onchange=
5280b57cec5SDimitry Andric         function (event) {
5290b57cec5SDimitry Andric       filterCounterexample(this.checked);
5300b57cec5SDimitry Andric     };
5310b57cec5SDimitry Andric });
5320b57cec5SDimitry Andric </script>
5330b57cec5SDimitry Andric 
5340b57cec5SDimitry Andric <form>
5350b57cec5SDimitry Andric     <input type="checkbox" name="showCounterexample" id="showCounterexample" />
5360b57cec5SDimitry Andric     <label for="showCounterexample">
5370b57cec5SDimitry Andric        Show only relevant lines
5380b57cec5SDimitry Andric     </label>
539349cc55cSDimitry Andric     <input type="checkbox" name="showArrows"
540349cc55cSDimitry Andric            id="showArrows" style="margin-left: 10px" />
541349cc55cSDimitry Andric     <label for="showArrows">
542349cc55cSDimitry Andric        Show control flow arrows
543349cc55cSDimitry Andric     </label>
5440b57cec5SDimitry Andric </form>
5450b57cec5SDimitry Andric )<<<";
5460b57cec5SDimitry Andric 
5470eae32dcSDimitry Andric   return s;
5480b57cec5SDimitry Andric }
5490b57cec5SDimitry Andric 
5500b57cec5SDimitry Andric void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic &D, Rewriter &R,
5515f757f3fSDimitry Andric                                    const SourceManager &SMgr,
5525f757f3fSDimitry Andric                                    const PathPieces &path, FileID FID,
5535f757f3fSDimitry Andric                                    FileEntryRef Entry, const char *declName) {
5540b57cec5SDimitry Andric   // This is a cludge; basically we want to append either the full
5550b57cec5SDimitry Andric   // working directory if we have no directory information.  This is
5560b57cec5SDimitry Andric   // a work in progress.
5570b57cec5SDimitry Andric 
5580b57cec5SDimitry Andric   llvm::SmallString<0> DirName;
5590b57cec5SDimitry Andric 
5605f757f3fSDimitry Andric   if (llvm::sys::path::is_relative(Entry.getName())) {
5610b57cec5SDimitry Andric     llvm::sys::fs::current_path(DirName);
5620b57cec5SDimitry Andric     DirName += '/';
5630b57cec5SDimitry Andric   }
5640b57cec5SDimitry Andric 
5650b57cec5SDimitry Andric   int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
5660b57cec5SDimitry Andric   int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
5670b57cec5SDimitry Andric 
5680b57cec5SDimitry Andric   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript());
5690b57cec5SDimitry Andric 
5700b57cec5SDimitry Andric   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
5710b57cec5SDimitry Andric                      generateKeyboardNavigationJavascript());
5720b57cec5SDimitry Andric 
573349cc55cSDimitry Andric   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
574349cc55cSDimitry Andric                      generateArrowDrawingJavascript());
575349cc55cSDimitry Andric 
5760b57cec5SDimitry Andric   // Checkbox and javascript for filtering the output to the counterexample.
5770b57cec5SDimitry Andric   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
5780b57cec5SDimitry Andric                      showRelevantLinesJavascript(D, path));
5790b57cec5SDimitry Andric 
5800b57cec5SDimitry Andric   // Add the name of the file as an <h1> tag.
5810b57cec5SDimitry Andric   {
5820b57cec5SDimitry Andric     std::string s;
5830b57cec5SDimitry Andric     llvm::raw_string_ostream os(s);
5840b57cec5SDimitry Andric 
5850b57cec5SDimitry Andric     os << "<!-- REPORTHEADER -->\n"
5860b57cec5SDimitry Andric        << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
5870b57cec5SDimitry Andric           "<tr><td class=\"rowname\">File:</td><td>"
5880b57cec5SDimitry Andric        << html::EscapeText(DirName)
5895f757f3fSDimitry Andric        << html::EscapeText(Entry.getName())
5900b57cec5SDimitry Andric        << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
5910b57cec5SDimitry Andric           "<a href=\"#EndPath\">line "
5920b57cec5SDimitry Andric        << LineNumber
5930b57cec5SDimitry Andric        << ", column "
5940b57cec5SDimitry Andric        << ColumnNumber
5950b57cec5SDimitry Andric        << "</a><br />"
5960b57cec5SDimitry Andric        << D.getVerboseDescription() << "</td></tr>\n";
5970b57cec5SDimitry Andric 
5980b57cec5SDimitry Andric     // The navigation across the extra notes pieces.
5990b57cec5SDimitry Andric     unsigned NumExtraPieces = 0;
6000b57cec5SDimitry Andric     for (const auto &Piece : path) {
6010b57cec5SDimitry Andric       if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
6020b57cec5SDimitry Andric         int LineNumber =
6030b57cec5SDimitry Andric             P->getLocation().asLocation().getExpansionLineNumber();
6040b57cec5SDimitry Andric         int ColumnNumber =
6050b57cec5SDimitry Andric             P->getLocation().asLocation().getExpansionColumnNumber();
6065f757f3fSDimitry Andric         ++NumExtraPieces;
6070b57cec5SDimitry Andric         os << "<tr><td class=\"rowname\">Note:</td><td>"
6080b57cec5SDimitry Andric            << "<a href=\"#Note" << NumExtraPieces << "\">line "
6090b57cec5SDimitry Andric            << LineNumber << ", column " << ColumnNumber << "</a><br />"
6100b57cec5SDimitry Andric            << P->getString() << "</td></tr>";
6110b57cec5SDimitry Andric       }
6120b57cec5SDimitry Andric     }
6130b57cec5SDimitry Andric 
6140b57cec5SDimitry Andric     // Output any other meta data.
6150b57cec5SDimitry Andric 
61606c3fb27SDimitry Andric     for (const std::string &Metadata :
61706c3fb27SDimitry Andric          llvm::make_range(D.meta_begin(), D.meta_end())) {
61806c3fb27SDimitry Andric       os << "<tr><td></td><td>" << html::EscapeText(Metadata) << "</td></tr>\n";
6190b57cec5SDimitry Andric     }
6200b57cec5SDimitry Andric 
6210b57cec5SDimitry Andric     os << R"<<<(
6220b57cec5SDimitry Andric </table>
6230b57cec5SDimitry Andric <!-- REPORTSUMMARYEXTRA -->
6240b57cec5SDimitry Andric <h3>Annotated Source Code</h3>
6250b57cec5SDimitry Andric <p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
6260b57cec5SDimitry Andric    to see keyboard shortcuts</p>
6270b57cec5SDimitry Andric <input type="checkbox" class="spoilerhider" id="showinvocation" />
6280b57cec5SDimitry Andric <label for="showinvocation" >Show analyzer invocation</label>
6290b57cec5SDimitry Andric <div class="spoiler">clang -cc1 )<<<";
630e8d8bef9SDimitry Andric     os << html::EscapeText(DiagOpts.ToolInvocation);
6310b57cec5SDimitry Andric     os << R"<<<(
6320b57cec5SDimitry Andric </div>
6330b57cec5SDimitry Andric <div id='tooltiphint' hidden="true">
6340b57cec5SDimitry Andric   <p>Keyboard shortcuts: </p>
6350b57cec5SDimitry Andric   <ul>
6360b57cec5SDimitry Andric     <li>Use 'j/k' keys for keyboard navigation</li>
6370b57cec5SDimitry Andric     <li>Use 'Shift+S' to show/hide relevant lines</li>
6380b57cec5SDimitry Andric     <li>Use '?' to toggle this window</li>
6390b57cec5SDimitry Andric   </ul>
6400b57cec5SDimitry Andric   <a href="#" onclick="toggleHelp(); return false;">Close</a>
6410b57cec5SDimitry Andric </div>
6420b57cec5SDimitry Andric )<<<";
643349cc55cSDimitry Andric 
6440b57cec5SDimitry Andric     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
6450b57cec5SDimitry Andric   }
6460b57cec5SDimitry Andric 
6470b57cec5SDimitry Andric   // Embed meta-data tags.
6480b57cec5SDimitry Andric   {
6490b57cec5SDimitry Andric     std::string s;
6500b57cec5SDimitry Andric     llvm::raw_string_ostream os(s);
6510b57cec5SDimitry Andric 
6520b57cec5SDimitry Andric     StringRef BugDesc = D.getVerboseDescription();
6530b57cec5SDimitry Andric     if (!BugDesc.empty())
6540b57cec5SDimitry Andric       os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
6550b57cec5SDimitry Andric 
6560b57cec5SDimitry Andric     StringRef BugType = D.getBugType();
6570b57cec5SDimitry Andric     if (!BugType.empty())
6580b57cec5SDimitry Andric       os << "\n<!-- BUGTYPE " << BugType << " -->\n";
6590b57cec5SDimitry Andric 
6600b57cec5SDimitry Andric     PathDiagnosticLocation UPDLoc = D.getUniqueingLoc();
6610b57cec5SDimitry Andric     FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()
6620b57cec5SDimitry Andric                                              ? UPDLoc.asLocation()
6630b57cec5SDimitry Andric                                              : D.getLocation().asLocation()),
6640b57cec5SDimitry Andric                     SMgr);
6650b57cec5SDimitry Andric 
6660b57cec5SDimitry Andric     StringRef BugCategory = D.getCategory();
6670b57cec5SDimitry Andric     if (!BugCategory.empty())
6680b57cec5SDimitry Andric       os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
6690b57cec5SDimitry Andric 
6705f757f3fSDimitry Andric     os << "\n<!-- BUGFILE " << DirName << Entry.getName() << " -->\n";
6710b57cec5SDimitry Andric 
6725f757f3fSDimitry Andric     os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry.getName()) << " -->\n";
6730b57cec5SDimitry Andric 
6740b57cec5SDimitry Andric     os  << "\n<!-- FUNCTIONNAME " <<  declName << " -->\n";
6750b57cec5SDimitry Andric 
676349cc55cSDimitry Andric     os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " << getIssueHash(D, PP)
677a7dea167SDimitry Andric        << " -->\n";
6780b57cec5SDimitry Andric 
6790b57cec5SDimitry Andric     os << "\n<!-- BUGLINE "
6800b57cec5SDimitry Andric        << LineNumber
6810b57cec5SDimitry Andric        << " -->\n";
6820b57cec5SDimitry Andric 
6830b57cec5SDimitry Andric     os << "\n<!-- BUGCOLUMN "
6840b57cec5SDimitry Andric       << ColumnNumber
6850b57cec5SDimitry Andric       << " -->\n";
6860b57cec5SDimitry Andric 
687349cc55cSDimitry Andric     os << "\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) << " -->\n";
6880b57cec5SDimitry Andric 
6890b57cec5SDimitry Andric     // Mark the end of the tags.
6900b57cec5SDimitry Andric     os << "\n<!-- BUGMETAEND -->\n";
6910b57cec5SDimitry Andric 
6920b57cec5SDimitry Andric     // Insert the text.
6930b57cec5SDimitry Andric     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
6940b57cec5SDimitry Andric   }
6950b57cec5SDimitry Andric 
6965f757f3fSDimitry Andric   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry.getName());
6970b57cec5SDimitry Andric }
6980b57cec5SDimitry Andric 
6990b57cec5SDimitry Andric StringRef HTMLDiagnostics::showHelpJavascript() {
7000b57cec5SDimitry Andric   return R"<<<(
7010b57cec5SDimitry Andric <script type='text/javascript'>
7020b57cec5SDimitry Andric 
7030b57cec5SDimitry Andric var toggleHelp = function() {
7040b57cec5SDimitry Andric     var hint = document.querySelector("#tooltiphint");
7050b57cec5SDimitry Andric     var attributeName = "hidden";
7060b57cec5SDimitry Andric     if (hint.hasAttribute(attributeName)) {
7070b57cec5SDimitry Andric       hint.removeAttribute(attributeName);
7080b57cec5SDimitry Andric     } else {
7090b57cec5SDimitry Andric       hint.setAttribute("hidden", "true");
7100b57cec5SDimitry Andric     }
7110b57cec5SDimitry Andric };
7120b57cec5SDimitry Andric window.addEventListener("keydown", function (event) {
7130b57cec5SDimitry Andric   if (event.defaultPrevented) {
7140b57cec5SDimitry Andric     return;
7150b57cec5SDimitry Andric   }
7160b57cec5SDimitry Andric   if (event.key == "?") {
7170b57cec5SDimitry Andric     toggleHelp();
7180b57cec5SDimitry Andric   } else {
7190b57cec5SDimitry Andric     return;
7200b57cec5SDimitry Andric   }
7210b57cec5SDimitry Andric   event.preventDefault();
7220b57cec5SDimitry Andric });
7230b57cec5SDimitry Andric </script>
7240b57cec5SDimitry Andric )<<<";
7250b57cec5SDimitry Andric }
7260b57cec5SDimitry Andric 
72713138422SDimitry Andric static bool shouldDisplayPopUpRange(const SourceRange &Range) {
72813138422SDimitry Andric   return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID());
72913138422SDimitry Andric }
73013138422SDimitry Andric 
7310b57cec5SDimitry Andric static void
7320b57cec5SDimitry Andric HandlePopUpPieceStartTag(Rewriter &R,
7330b57cec5SDimitry Andric                          const std::vector<SourceRange> &PopUpRanges) {
7340b57cec5SDimitry Andric   for (const auto &Range : PopUpRanges) {
73513138422SDimitry Andric     if (!shouldDisplayPopUpRange(Range))
73613138422SDimitry Andric       continue;
73713138422SDimitry Andric 
7380b57cec5SDimitry Andric     html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "",
7390b57cec5SDimitry Andric                          "<table class='variable_popup'><tbody>",
740a7dea167SDimitry Andric                          /*IsTokenRange=*/true);
7410b57cec5SDimitry Andric   }
7420b57cec5SDimitry Andric }
7430b57cec5SDimitry Andric 
7440b57cec5SDimitry Andric static void HandlePopUpPieceEndTag(Rewriter &R,
7450b57cec5SDimitry Andric                                    const PathDiagnosticPopUpPiece &Piece,
7460b57cec5SDimitry Andric                                    std::vector<SourceRange> &PopUpRanges,
7470b57cec5SDimitry Andric                                    unsigned int LastReportedPieceIndex,
7480b57cec5SDimitry Andric                                    unsigned int PopUpPieceIndex) {
7490b57cec5SDimitry Andric   SmallString<256> Buf;
7500b57cec5SDimitry Andric   llvm::raw_svector_ostream Out(Buf);
7510b57cec5SDimitry Andric 
7520b57cec5SDimitry Andric   SourceRange Range(Piece.getLocation().asRange());
75313138422SDimitry Andric   if (!shouldDisplayPopUpRange(Range))
75413138422SDimitry Andric     return;
7550b57cec5SDimitry Andric 
7560b57cec5SDimitry Andric   // Write out the path indices with a right arrow and the message as a row.
7570b57cec5SDimitry Andric   Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
7580b57cec5SDimitry Andric       << LastReportedPieceIndex;
7590b57cec5SDimitry Andric 
7600b57cec5SDimitry Andric   // Also annotate the state transition with extra indices.
7610b57cec5SDimitry Andric   Out << '.' << PopUpPieceIndex;
7620b57cec5SDimitry Andric 
7630b57cec5SDimitry Andric   Out << "</div></td><td>" << Piece.getString() << "</td></tr>";
7640b57cec5SDimitry Andric 
7650b57cec5SDimitry Andric   // If no report made at this range mark the variable and add the end tags.
766349cc55cSDimitry Andric   if (!llvm::is_contained(PopUpRanges, Range)) {
7670b57cec5SDimitry Andric     // Store that we create a report at this range.
7680b57cec5SDimitry Andric     PopUpRanges.push_back(Range);
7690b57cec5SDimitry Andric 
7700b57cec5SDimitry Andric     Out << "</tbody></table></span>";
7710b57cec5SDimitry Andric     html::HighlightRange(R, Range.getBegin(), Range.getEnd(),
7720b57cec5SDimitry Andric                          "<span class='variable'>", Buf.c_str(),
773a7dea167SDimitry Andric                          /*IsTokenRange=*/true);
7740b57cec5SDimitry Andric   } else {
775a7dea167SDimitry Andric     // Otherwise inject just the new row at the end of the range.
7760b57cec5SDimitry Andric     html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(),
777a7dea167SDimitry Andric                          /*IsTokenRange=*/true);
7780b57cec5SDimitry Andric   }
7790b57cec5SDimitry Andric }
7800b57cec5SDimitry Andric 
781349cc55cSDimitry Andric void HTMLDiagnostics::RewriteFile(Rewriter &R, const PathPieces &path,
782349cc55cSDimitry Andric                                   FileID FID) {
783349cc55cSDimitry Andric 
7840b57cec5SDimitry Andric   // Process the path.
7850b57cec5SDimitry Andric   // Maintain the counts of extra note pieces separately.
786349cc55cSDimitry Andric   unsigned TotalPieces = getPathSizeWithoutArrows(path);
787349cc55cSDimitry Andric   unsigned TotalNotePieces =
788349cc55cSDimitry Andric       llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
7890b57cec5SDimitry Andric         return isa<PathDiagnosticNotePiece>(*p);
7900b57cec5SDimitry Andric       });
791349cc55cSDimitry Andric   unsigned PopUpPieceCount =
792349cc55cSDimitry Andric       llvm::count_if(path, [](const PathDiagnosticPieceRef &p) {
7930b57cec5SDimitry Andric         return isa<PathDiagnosticPopUpPiece>(*p);
7940b57cec5SDimitry Andric       });
7950b57cec5SDimitry Andric 
7960b57cec5SDimitry Andric   unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
7970b57cec5SDimitry Andric   unsigned NumRegularPieces = TotalRegularPieces;
7980b57cec5SDimitry Andric   unsigned NumNotePieces = TotalNotePieces;
799349cc55cSDimitry Andric   unsigned NumberOfArrows = 0;
8000b57cec5SDimitry Andric   // Stores the count of the regular piece indices.
8010b57cec5SDimitry Andric   std::map<int, int> IndexMap;
802349cc55cSDimitry Andric   ArrowMap ArrowIndices(TotalRegularPieces + 1);
8030b57cec5SDimitry Andric 
8040b57cec5SDimitry Andric   // Stores the different ranges where we have reported something.
8050b57cec5SDimitry Andric   std::vector<SourceRange> PopUpRanges;
806349cc55cSDimitry Andric   for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
807349cc55cSDimitry Andric     const auto &Piece = *I.get();
8080b57cec5SDimitry Andric 
8090b57cec5SDimitry Andric     if (isa<PathDiagnosticPopUpPiece>(Piece)) {
8100b57cec5SDimitry Andric       ++IndexMap[NumRegularPieces];
8110b57cec5SDimitry Andric     } else if (isa<PathDiagnosticNotePiece>(Piece)) {
8120b57cec5SDimitry Andric       // This adds diagnostic bubbles, but not navigation.
8130b57cec5SDimitry Andric       // Navigation through note pieces would be added later,
8140b57cec5SDimitry Andric       // as a separate pass through the piece list.
8150b57cec5SDimitry Andric       HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
8160b57cec5SDimitry Andric       --NumNotePieces;
817349cc55cSDimitry Andric 
818349cc55cSDimitry Andric     } else if (isArrowPiece(Piece)) {
819349cc55cSDimitry Andric       NumberOfArrows = ProcessControlFlowPiece(
820349cc55cSDimitry Andric           R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows);
821349cc55cSDimitry Andric       ArrowIndices[NumRegularPieces] = NumberOfArrows;
822349cc55cSDimitry Andric 
8230b57cec5SDimitry Andric     } else {
8240b57cec5SDimitry Andric       HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
8250b57cec5SDimitry Andric                   TotalRegularPieces);
8260b57cec5SDimitry Andric       --NumRegularPieces;
827349cc55cSDimitry Andric       ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1];
8280b57cec5SDimitry Andric     }
8290b57cec5SDimitry Andric   }
830349cc55cSDimitry Andric   ArrowIndices[0] = NumberOfArrows;
831349cc55cSDimitry Andric 
832349cc55cSDimitry Andric   // At this point ArrowIndices represent the following data structure:
833349cc55cSDimitry Andric   //   [a_0, a_1, ..., a_N]
834349cc55cSDimitry Andric   // where N is the number of events in the path.
835349cc55cSDimitry Andric   //
836349cc55cSDimitry Andric   // Then for every event with index i \in [0, N - 1], we can say that
837349cc55cSDimitry Andric   // arrows with indices \in [a_(i+1), a_i) correspond to that event.
838349cc55cSDimitry Andric   // We can say that because arrows with these indices appeared in the
839349cc55cSDimitry Andric   // path in between the i-th and the (i+1)-th events.
840349cc55cSDimitry Andric   assert(ArrowIndices.back() == 0 &&
841349cc55cSDimitry Andric          "No arrows should be after the last event");
842349cc55cSDimitry Andric   // This assertion also guarantees that all indices in are <= NumberOfArrows.
843349cc55cSDimitry Andric   assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) &&
844349cc55cSDimitry Andric          "Incorrect arrow indices map");
8450b57cec5SDimitry Andric 
8460b57cec5SDimitry Andric   // Secondary indexing if we are having multiple pop-ups between two notes.
8470b57cec5SDimitry Andric   // (e.g. [(13) 'a' is 'true'];  [(13.1) 'b' is 'false'];  [(13.2) 'c' is...)
8480b57cec5SDimitry Andric   NumRegularPieces = TotalRegularPieces;
849349cc55cSDimitry Andric   for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) {
850349cc55cSDimitry Andric     const auto &Piece = *I.get();
8510b57cec5SDimitry Andric 
8520b57cec5SDimitry Andric     if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
8530b57cec5SDimitry Andric       int PopUpPieceIndex = IndexMap[NumRegularPieces];
8540b57cec5SDimitry Andric 
8550b57cec5SDimitry Andric       // Pop-up pieces needs the index of the last reported piece and its count
8560b57cec5SDimitry Andric       // how many times we report to handle multiple reports on the same range.
8570b57cec5SDimitry Andric       // This marks the variable, adds the </table> end tag and the message
8580b57cec5SDimitry Andric       // (list element) as a row. The <table> start tag will be added after the
8590b57cec5SDimitry Andric       // rows has been written out. Note: It stores every different range.
8600b57cec5SDimitry Andric       HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces,
8610b57cec5SDimitry Andric                              PopUpPieceIndex);
8620b57cec5SDimitry Andric 
8630b57cec5SDimitry Andric       if (PopUpPieceIndex > 0)
8640b57cec5SDimitry Andric         --IndexMap[NumRegularPieces];
8650b57cec5SDimitry Andric 
866349cc55cSDimitry Andric     } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) {
8670b57cec5SDimitry Andric       --NumRegularPieces;
8680b57cec5SDimitry Andric     }
8690b57cec5SDimitry Andric   }
8700b57cec5SDimitry Andric 
8710b57cec5SDimitry Andric   // Add the <table> start tag of pop-up pieces based on the stored ranges.
8720b57cec5SDimitry Andric   HandlePopUpPieceStartTag(R, PopUpRanges);
8730b57cec5SDimitry Andric 
8740b57cec5SDimitry Andric   // Add line numbers, header, footer, etc.
8750b57cec5SDimitry Andric   html::EscapeText(R, FID);
8760b57cec5SDimitry Andric   html::AddLineNumbers(R, FID);
8770b57cec5SDimitry Andric 
878349cc55cSDimitry Andric   addArrowSVGs(R, FID, ArrowIndices);
879349cc55cSDimitry Andric 
8800b57cec5SDimitry Andric   // If we have a preprocessor, relex the file and syntax highlight.
8810b57cec5SDimitry Andric   // We might not have a preprocessor if we come from a deserialized AST file,
8820b57cec5SDimitry Andric   // for example.
883*0fca6ea1SDimitry Andric   html::SyntaxHighlight(R, FID, PP, RewriterCache);
884*0fca6ea1SDimitry Andric   html::HighlightMacros(R, FID, PP, RewriterCache);
8850b57cec5SDimitry Andric }
8860b57cec5SDimitry Andric 
8870b57cec5SDimitry Andric void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
8880b57cec5SDimitry Andric                                   const PathDiagnosticPiece &P,
8890b57cec5SDimitry Andric                                   const std::vector<SourceRange> &PopUpRanges,
8900b57cec5SDimitry Andric                                   unsigned num, unsigned max) {
8910b57cec5SDimitry Andric   // For now, just draw a box above the line in question, and emit the
8920b57cec5SDimitry Andric   // warning.
8930b57cec5SDimitry Andric   FullSourceLoc Pos = P.getLocation().asLocation();
8940b57cec5SDimitry Andric 
8950b57cec5SDimitry Andric   if (!Pos.isValid())
8960b57cec5SDimitry Andric     return;
8970b57cec5SDimitry Andric 
8980b57cec5SDimitry Andric   SourceManager &SM = R.getSourceMgr();
8990b57cec5SDimitry Andric   assert(&Pos.getManager() == &SM && "SourceManagers are different!");
9000b57cec5SDimitry Andric   std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos);
9010b57cec5SDimitry Andric 
9020b57cec5SDimitry Andric   if (LPosInfo.first != BugFileID)
9030b57cec5SDimitry Andric     return;
9040b57cec5SDimitry Andric 
905e8d8bef9SDimitry Andric   llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first);
906e8d8bef9SDimitry Andric   const char *FileStart = Buf.getBufferStart();
9070b57cec5SDimitry Andric 
9080b57cec5SDimitry Andric   // Compute the column number.  Rewind from the current position to the start
9090b57cec5SDimitry Andric   // of the line.
9100b57cec5SDimitry Andric   unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
9110b57cec5SDimitry Andric   const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData();
9120b57cec5SDimitry Andric   const char *LineStart = TokInstantiationPtr-ColNo;
9130b57cec5SDimitry Andric 
9140b57cec5SDimitry Andric   // Compute LineEnd.
9150b57cec5SDimitry Andric   const char *LineEnd = TokInstantiationPtr;
916e8d8bef9SDimitry Andric   const char *FileEnd = Buf.getBufferEnd();
9170b57cec5SDimitry Andric   while (*LineEnd != '\n' && LineEnd != FileEnd)
9180b57cec5SDimitry Andric     ++LineEnd;
9190b57cec5SDimitry Andric 
9200b57cec5SDimitry Andric   // Compute the margin offset by counting tabs and non-tabs.
9210b57cec5SDimitry Andric   unsigned PosNo = 0;
9220b57cec5SDimitry Andric   for (const char* c = LineStart; c != TokInstantiationPtr; ++c)
9230b57cec5SDimitry Andric     PosNo += *c == '\t' ? 8 : 1;
9240b57cec5SDimitry Andric 
9250b57cec5SDimitry Andric   // Create the html for the message.
9260b57cec5SDimitry Andric 
9270b57cec5SDimitry Andric   const char *Kind = nullptr;
9280b57cec5SDimitry Andric   bool IsNote = false;
9290b57cec5SDimitry Andric   bool SuppressIndex = (max == 1);
9300b57cec5SDimitry Andric   switch (P.getKind()) {
9310b57cec5SDimitry Andric   case PathDiagnosticPiece::Event: Kind = "Event"; break;
9320b57cec5SDimitry Andric   case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break;
9330b57cec5SDimitry Andric     // Setting Kind to "Control" is intentional.
9340b57cec5SDimitry Andric   case PathDiagnosticPiece::Macro: Kind = "Control"; break;
9350b57cec5SDimitry Andric   case PathDiagnosticPiece::Note:
9360b57cec5SDimitry Andric     Kind = "Note";
9370b57cec5SDimitry Andric     IsNote = true;
9380b57cec5SDimitry Andric     SuppressIndex = true;
9390b57cec5SDimitry Andric     break;
9400b57cec5SDimitry Andric   case PathDiagnosticPiece::Call:
9410b57cec5SDimitry Andric   case PathDiagnosticPiece::PopUp:
9420b57cec5SDimitry Andric     llvm_unreachable("Calls and extra notes should already be handled");
9430b57cec5SDimitry Andric   }
9440b57cec5SDimitry Andric 
9450b57cec5SDimitry Andric   std::string sbuf;
9460b57cec5SDimitry Andric   llvm::raw_string_ostream os(sbuf);
9470b57cec5SDimitry Andric 
9480b57cec5SDimitry Andric   os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
9490b57cec5SDimitry Andric 
9500b57cec5SDimitry Andric   if (IsNote)
9510b57cec5SDimitry Andric     os << "Note" << num;
9520b57cec5SDimitry Andric   else if (num == max)
9530b57cec5SDimitry Andric     os << "EndPath";
9540b57cec5SDimitry Andric   else
9550b57cec5SDimitry Andric     os << "Path" << num;
9560b57cec5SDimitry Andric 
9570b57cec5SDimitry Andric   os << "\" class=\"msg";
9580b57cec5SDimitry Andric   if (Kind)
9590b57cec5SDimitry Andric     os << " msg" << Kind;
9600b57cec5SDimitry Andric   os << "\" style=\"margin-left:" << PosNo << "ex";
9610b57cec5SDimitry Andric 
9620b57cec5SDimitry Andric   // Output a maximum size.
9630b57cec5SDimitry Andric   if (!isa<PathDiagnosticMacroPiece>(P)) {
9640b57cec5SDimitry Andric     // Get the string and determining its maximum substring.
9650b57cec5SDimitry Andric     const auto &Msg = P.getString();
9660b57cec5SDimitry Andric     unsigned max_token = 0;
9670b57cec5SDimitry Andric     unsigned cnt = 0;
9680b57cec5SDimitry Andric     unsigned len = Msg.size();
9690b57cec5SDimitry Andric 
9700b57cec5SDimitry Andric     for (char C : Msg)
9710b57cec5SDimitry Andric       switch (C) {
9720b57cec5SDimitry Andric       default:
9730b57cec5SDimitry Andric         ++cnt;
9740b57cec5SDimitry Andric         continue;
9750b57cec5SDimitry Andric       case ' ':
9760b57cec5SDimitry Andric       case '\t':
9770b57cec5SDimitry Andric       case '\n':
9780b57cec5SDimitry Andric         if (cnt > max_token) max_token = cnt;
9790b57cec5SDimitry Andric         cnt = 0;
9800b57cec5SDimitry Andric       }
9810b57cec5SDimitry Andric 
9820b57cec5SDimitry Andric     if (cnt > max_token)
9830b57cec5SDimitry Andric       max_token = cnt;
9840b57cec5SDimitry Andric 
9850b57cec5SDimitry Andric     // Determine the approximate size of the message bubble in em.
9860b57cec5SDimitry Andric     unsigned em;
9870b57cec5SDimitry Andric     const unsigned max_line = 120;
9880b57cec5SDimitry Andric 
9890b57cec5SDimitry Andric     if (max_token >= max_line)
9900b57cec5SDimitry Andric       em = max_token / 2;
9910b57cec5SDimitry Andric     else {
9920b57cec5SDimitry Andric       unsigned characters = max_line;
9930b57cec5SDimitry Andric       unsigned lines = len / max_line;
9940b57cec5SDimitry Andric 
9950b57cec5SDimitry Andric       if (lines > 0) {
9960b57cec5SDimitry Andric         for (; characters > max_token; --characters)
9970b57cec5SDimitry Andric           if (len / characters > lines) {
9980b57cec5SDimitry Andric             ++characters;
9990b57cec5SDimitry Andric             break;
10000b57cec5SDimitry Andric           }
10010b57cec5SDimitry Andric       }
10020b57cec5SDimitry Andric 
10030b57cec5SDimitry Andric       em = characters / 2;
10040b57cec5SDimitry Andric     }
10050b57cec5SDimitry Andric 
10060b57cec5SDimitry Andric     if (em < max_line/2)
10070b57cec5SDimitry Andric       os << "; max-width:" << em << "em";
10080b57cec5SDimitry Andric   }
10090b57cec5SDimitry Andric   else
10100b57cec5SDimitry Andric     os << "; max-width:100em";
10110b57cec5SDimitry Andric 
10120b57cec5SDimitry Andric   os << "\">";
10130b57cec5SDimitry Andric 
10140b57cec5SDimitry Andric   if (!SuppressIndex) {
10150b57cec5SDimitry Andric     os << "<table class=\"msgT\"><tr><td valign=\"top\">";
10160b57cec5SDimitry Andric     os << "<div class=\"PathIndex";
10170b57cec5SDimitry Andric     if (Kind) os << " PathIndex" << Kind;
10180b57cec5SDimitry Andric     os << "\">" << num << "</div>";
10190b57cec5SDimitry Andric 
10200b57cec5SDimitry Andric     if (num > 1) {
10210b57cec5SDimitry Andric       os << "</td><td><div class=\"PathNav\"><a href=\"#Path"
10220b57cec5SDimitry Andric          << (num - 1)
10230b57cec5SDimitry Andric          << "\" title=\"Previous event ("
10240b57cec5SDimitry Andric          << (num - 1)
102513138422SDimitry Andric          << ")\">&#x2190;</a></div>";
10260b57cec5SDimitry Andric     }
10270b57cec5SDimitry Andric 
10280b57cec5SDimitry Andric     os << "</td><td>";
10290b57cec5SDimitry Andric   }
10300b57cec5SDimitry Andric 
10310b57cec5SDimitry Andric   if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
10320b57cec5SDimitry Andric     os << "Within the expansion of the macro '";
10330b57cec5SDimitry Andric 
10340b57cec5SDimitry Andric     // Get the name of the macro by relexing it.
10350b57cec5SDimitry Andric     {
10360b57cec5SDimitry Andric       FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
10370b57cec5SDimitry Andric       assert(L.isFileID());
10380b57cec5SDimitry Andric       StringRef BufferInfo = L.getBufferData();
10390b57cec5SDimitry Andric       std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc();
10400b57cec5SDimitry Andric       const char* MacroName = LocInfo.second + BufferInfo.data();
10410b57cec5SDimitry Andric       Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(),
10420b57cec5SDimitry Andric                      BufferInfo.begin(), MacroName, BufferInfo.end());
10430b57cec5SDimitry Andric 
10440b57cec5SDimitry Andric       Token TheTok;
10450b57cec5SDimitry Andric       rawLexer.LexFromRawLexer(TheTok);
10460b57cec5SDimitry Andric       for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i)
10470b57cec5SDimitry Andric         os << MacroName[i];
10480b57cec5SDimitry Andric     }
10490b57cec5SDimitry Andric 
10500b57cec5SDimitry Andric     os << "':\n";
10510b57cec5SDimitry Andric 
10520b57cec5SDimitry Andric     if (!SuppressIndex) {
10530b57cec5SDimitry Andric       os << "</td>";
10540b57cec5SDimitry Andric       if (num < max) {
10550b57cec5SDimitry Andric         os << "<td><div class=\"PathNav\"><a href=\"#";
10560b57cec5SDimitry Andric         if (num == max - 1)
10570b57cec5SDimitry Andric           os << "EndPath";
10580b57cec5SDimitry Andric         else
10590b57cec5SDimitry Andric           os << "Path" << (num + 1);
10600b57cec5SDimitry Andric         os << "\" title=\"Next event ("
10610b57cec5SDimitry Andric         << (num + 1)
10620b57cec5SDimitry Andric         << ")\">&#x2192;</a></div></td>";
10630b57cec5SDimitry Andric       }
10640b57cec5SDimitry Andric 
10650b57cec5SDimitry Andric       os << "</tr></table>";
10660b57cec5SDimitry Andric     }
10670b57cec5SDimitry Andric 
10680b57cec5SDimitry Andric     // Within a macro piece.  Write out each event.
10690b57cec5SDimitry Andric     ProcessMacroPiece(os, *MP, 0);
10700b57cec5SDimitry Andric   }
10710b57cec5SDimitry Andric   else {
10720b57cec5SDimitry Andric     os << html::EscapeText(P.getString());
10730b57cec5SDimitry Andric 
10740b57cec5SDimitry Andric     if (!SuppressIndex) {
10750b57cec5SDimitry Andric       os << "</td>";
10760b57cec5SDimitry Andric       if (num < max) {
10770b57cec5SDimitry Andric         os << "<td><div class=\"PathNav\"><a href=\"#";
10780b57cec5SDimitry Andric         if (num == max - 1)
10790b57cec5SDimitry Andric           os << "EndPath";
10800b57cec5SDimitry Andric         else
10810b57cec5SDimitry Andric           os << "Path" << (num + 1);
10820b57cec5SDimitry Andric         os << "\" title=\"Next event ("
10830b57cec5SDimitry Andric            << (num + 1)
10840b57cec5SDimitry Andric            << ")\">&#x2192;</a></div></td>";
10850b57cec5SDimitry Andric       }
10860b57cec5SDimitry Andric 
10870b57cec5SDimitry Andric       os << "</tr></table>";
10880b57cec5SDimitry Andric     }
10890b57cec5SDimitry Andric   }
10900b57cec5SDimitry Andric 
10910b57cec5SDimitry Andric   os << "</div></td></tr>";
10920b57cec5SDimitry Andric 
10930b57cec5SDimitry Andric   // Insert the new html.
10940b57cec5SDimitry Andric   unsigned DisplayPos = LineEnd - FileStart;
10950b57cec5SDimitry Andric   SourceLocation Loc =
10960b57cec5SDimitry Andric     SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
10970b57cec5SDimitry Andric 
10980b57cec5SDimitry Andric   R.InsertTextBefore(Loc, os.str());
10990b57cec5SDimitry Andric 
11000b57cec5SDimitry Andric   // Now highlight the ranges.
11010b57cec5SDimitry Andric   ArrayRef<SourceRange> Ranges = P.getRanges();
11020b57cec5SDimitry Andric   for (const auto &Range : Ranges) {
11030b57cec5SDimitry Andric     // If we have already highlighted the range as a pop-up there is no work.
1104349cc55cSDimitry Andric     if (llvm::is_contained(PopUpRanges, Range))
11050b57cec5SDimitry Andric       continue;
11060b57cec5SDimitry Andric 
11070b57cec5SDimitry Andric     HighlightRange(R, LPosInfo.first, Range);
11080b57cec5SDimitry Andric   }
11090b57cec5SDimitry Andric }
11100b57cec5SDimitry Andric 
11110b57cec5SDimitry Andric static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
11120b57cec5SDimitry Andric   unsigned x = n % ('z' - 'a');
11130b57cec5SDimitry Andric   n /= 'z' - 'a';
11140b57cec5SDimitry Andric 
11150b57cec5SDimitry Andric   if (n > 0)
11160b57cec5SDimitry Andric     EmitAlphaCounter(os, n);
11170b57cec5SDimitry Andric 
11180b57cec5SDimitry Andric   os << char('a' + x);
11190b57cec5SDimitry Andric }
11200b57cec5SDimitry Andric 
11210b57cec5SDimitry Andric unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
11220b57cec5SDimitry Andric                                             const PathDiagnosticMacroPiece& P,
11230b57cec5SDimitry Andric                                             unsigned num) {
11240b57cec5SDimitry Andric   for (const auto &subPiece : P.subPieces) {
11250b57cec5SDimitry Andric     if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
11260b57cec5SDimitry Andric       num = ProcessMacroPiece(os, *MP, num);
11270b57cec5SDimitry Andric       continue;
11280b57cec5SDimitry Andric     }
11290b57cec5SDimitry Andric 
11300b57cec5SDimitry Andric     if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
11310b57cec5SDimitry Andric       os << "<div class=\"msg msgEvent\" style=\"width:94%; "
11320b57cec5SDimitry Andric             "margin-left:5px\">"
11330b57cec5SDimitry Andric             "<table class=\"msgT\"><tr>"
11340b57cec5SDimitry Andric             "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
11350b57cec5SDimitry Andric       EmitAlphaCounter(os, num++);
11360b57cec5SDimitry Andric       os << "</div></td><td valign=\"top\">"
11370b57cec5SDimitry Andric          << html::EscapeText(EP->getString())
11380b57cec5SDimitry Andric          << "</td></tr></table></div>\n";
11390b57cec5SDimitry Andric     }
11400b57cec5SDimitry Andric   }
11410b57cec5SDimitry Andric 
11420b57cec5SDimitry Andric   return num;
11430b57cec5SDimitry Andric }
11440b57cec5SDimitry Andric 
1145349cc55cSDimitry Andric void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID,
1146349cc55cSDimitry Andric                                    const ArrowMap &ArrowIndices) {
1147349cc55cSDimitry Andric   std::string S;
1148349cc55cSDimitry Andric   llvm::raw_string_ostream OS(S);
1149349cc55cSDimitry Andric 
1150349cc55cSDimitry Andric   OS << R"<<<(
1151349cc55cSDimitry Andric <style type="text/css">
1152349cc55cSDimitry Andric   svg {
1153349cc55cSDimitry Andric       position:absolute;
1154349cc55cSDimitry Andric       top:0;
1155349cc55cSDimitry Andric       left:0;
1156349cc55cSDimitry Andric       height:100%;
1157349cc55cSDimitry Andric       width:100%;
1158349cc55cSDimitry Andric       pointer-events: none;
1159349cc55cSDimitry Andric       overflow: visible
1160349cc55cSDimitry Andric   }
1161349cc55cSDimitry Andric   .arrow {
1162349cc55cSDimitry Andric       stroke-opacity: 0.2;
1163349cc55cSDimitry Andric       stroke-width: 1;
1164349cc55cSDimitry Andric       marker-end: url(#arrowhead);
1165349cc55cSDimitry Andric   }
1166349cc55cSDimitry Andric 
1167349cc55cSDimitry Andric   .arrow.selected {
1168349cc55cSDimitry Andric       stroke-opacity: 0.6;
1169349cc55cSDimitry Andric       stroke-width: 2;
1170349cc55cSDimitry Andric       marker-end: url(#arrowheadSelected);
1171349cc55cSDimitry Andric   }
1172349cc55cSDimitry Andric 
1173349cc55cSDimitry Andric   .arrowhead {
1174349cc55cSDimitry Andric       orient: auto;
1175349cc55cSDimitry Andric       stroke: none;
1176349cc55cSDimitry Andric       opacity: 0.6;
1177349cc55cSDimitry Andric       fill: blue;
1178349cc55cSDimitry Andric   }
1179349cc55cSDimitry Andric </style>
1180349cc55cSDimitry Andric <svg xmlns="http://www.w3.org/2000/svg">
1181349cc55cSDimitry Andric   <defs>
1182349cc55cSDimitry Andric     <marker id="arrowheadSelected" class="arrowhead" opacity="0.6"
1183349cc55cSDimitry Andric             viewBox="0 0 10 10" refX="3" refY="5"
1184349cc55cSDimitry Andric             markerWidth="4" markerHeight="4">
1185349cc55cSDimitry Andric       <path d="M 0 0 L 10 5 L 0 10 z" />
1186349cc55cSDimitry Andric     </marker>
1187349cc55cSDimitry Andric     <marker id="arrowhead" class="arrowhead" opacity="0.2"
1188349cc55cSDimitry Andric             viewBox="0 0 10 10" refX="3" refY="5"
1189349cc55cSDimitry Andric             markerWidth="4" markerHeight="4">
1190349cc55cSDimitry Andric       <path d="M 0 0 L 10 5 L 0 10 z" />
1191349cc55cSDimitry Andric     </marker>
1192349cc55cSDimitry Andric   </defs>
1193349cc55cSDimitry Andric   <g id="arrows" fill="none" stroke="blue" visibility="hidden">
1194349cc55cSDimitry Andric )<<<";
1195349cc55cSDimitry Andric 
1196349cc55cSDimitry Andric   for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) {
1197349cc55cSDimitry Andric     OS << "    <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n";
1198349cc55cSDimitry Andric   }
1199349cc55cSDimitry Andric 
1200349cc55cSDimitry Andric   OS << R"<<<(
1201349cc55cSDimitry Andric   </g>
1202349cc55cSDimitry Andric </svg>
1203349cc55cSDimitry Andric <script type='text/javascript'>
1204349cc55cSDimitry Andric const arrowIndices = )<<<";
1205349cc55cSDimitry Andric 
1206349cc55cSDimitry Andric   OS << ArrowIndices << "\n</script>\n";
1207349cc55cSDimitry Andric 
1208349cc55cSDimitry Andric   R.InsertTextBefore(R.getSourceMgr().getLocForStartOfFile(BugFileID),
1209349cc55cSDimitry Andric                      OS.str());
1210349cc55cSDimitry Andric }
1211349cc55cSDimitry Andric 
1212349cc55cSDimitry Andric std::string getSpanBeginForControl(const char *ClassName, unsigned Index) {
1213349cc55cSDimitry Andric   std::string Result;
1214349cc55cSDimitry Andric   llvm::raw_string_ostream OS(Result);
1215349cc55cSDimitry Andric   OS << "<span id=\"" << ClassName << Index << "\">";
12160eae32dcSDimitry Andric   return Result;
1217349cc55cSDimitry Andric }
1218349cc55cSDimitry Andric 
1219349cc55cSDimitry Andric std::string getSpanBeginForControlStart(unsigned Index) {
1220349cc55cSDimitry Andric   return getSpanBeginForControl("start", Index);
1221349cc55cSDimitry Andric }
1222349cc55cSDimitry Andric 
1223349cc55cSDimitry Andric std::string getSpanBeginForControlEnd(unsigned Index) {
1224349cc55cSDimitry Andric   return getSpanBeginForControl("end", Index);
1225349cc55cSDimitry Andric }
1226349cc55cSDimitry Andric 
1227349cc55cSDimitry Andric unsigned HTMLDiagnostics::ProcessControlFlowPiece(
1228349cc55cSDimitry Andric     Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P,
1229349cc55cSDimitry Andric     unsigned Number) {
1230349cc55cSDimitry Andric   for (const PathDiagnosticLocationPair &LPair : P) {
1231349cc55cSDimitry Andric     std::string Start = getSpanBeginForControlStart(Number),
1232349cc55cSDimitry Andric                 End = getSpanBeginForControlEnd(Number++);
1233349cc55cSDimitry Andric 
1234349cc55cSDimitry Andric     HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(),
1235349cc55cSDimitry Andric                    Start.c_str());
1236349cc55cSDimitry Andric     HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(),
1237349cc55cSDimitry Andric                    End.c_str());
1238349cc55cSDimitry Andric   }
1239349cc55cSDimitry Andric 
1240349cc55cSDimitry Andric   return Number;
1241349cc55cSDimitry Andric }
1242349cc55cSDimitry Andric 
12430b57cec5SDimitry Andric void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
12440b57cec5SDimitry Andric                                      SourceRange Range,
12450b57cec5SDimitry Andric                                      const char *HighlightStart,
12460b57cec5SDimitry Andric                                      const char *HighlightEnd) {
12470b57cec5SDimitry Andric   SourceManager &SM = R.getSourceMgr();
12480b57cec5SDimitry Andric   const LangOptions &LangOpts = R.getLangOpts();
12490b57cec5SDimitry Andric 
12500b57cec5SDimitry Andric   SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
12510b57cec5SDimitry Andric   unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
12520b57cec5SDimitry Andric 
12530b57cec5SDimitry Andric   SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
12540b57cec5SDimitry Andric   unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
12550b57cec5SDimitry Andric 
12560b57cec5SDimitry Andric   if (EndLineNo < StartLineNo)
12570b57cec5SDimitry Andric     return;
12580b57cec5SDimitry Andric 
12590b57cec5SDimitry Andric   if (SM.getFileID(InstantiationStart) != BugFileID ||
12600b57cec5SDimitry Andric       SM.getFileID(InstantiationEnd) != BugFileID)
12610b57cec5SDimitry Andric     return;
12620b57cec5SDimitry Andric 
12630b57cec5SDimitry Andric   // Compute the column number of the end.
12640b57cec5SDimitry Andric   unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
12650b57cec5SDimitry Andric   unsigned OldEndColNo = EndColNo;
12660b57cec5SDimitry Andric 
12670b57cec5SDimitry Andric   if (EndColNo) {
12680b57cec5SDimitry Andric     // Add in the length of the token, so that we cover multi-char tokens.
12690b57cec5SDimitry Andric     EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
12700b57cec5SDimitry Andric   }
12710b57cec5SDimitry Andric 
12720b57cec5SDimitry Andric   // Highlight the range.  Make the span tag the outermost tag for the
12730b57cec5SDimitry Andric   // selected range.
12740b57cec5SDimitry Andric 
12750b57cec5SDimitry Andric   SourceLocation E =
12760b57cec5SDimitry Andric     InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
12770b57cec5SDimitry Andric 
12780b57cec5SDimitry Andric   html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
12790b57cec5SDimitry Andric }
12800b57cec5SDimitry Andric 
12810b57cec5SDimitry Andric StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
12820b57cec5SDimitry Andric   return R"<<<(
12830b57cec5SDimitry Andric <script type='text/javascript'>
12840b57cec5SDimitry Andric var digitMatcher = new RegExp("[0-9]+");
12850b57cec5SDimitry Andric 
12865ffd83dbSDimitry Andric var querySelectorAllArray = function(selector) {
12875ffd83dbSDimitry Andric   return Array.prototype.slice.call(
12885ffd83dbSDimitry Andric     document.querySelectorAll(selector));
12895ffd83dbSDimitry Andric }
12905ffd83dbSDimitry Andric 
12910b57cec5SDimitry Andric document.addEventListener("DOMContentLoaded", function() {
12925ffd83dbSDimitry Andric     querySelectorAllArray(".PathNav > a").forEach(
12930b57cec5SDimitry Andric         function(currentValue, currentIndex) {
12940b57cec5SDimitry Andric             var hrefValue = currentValue.getAttribute("href");
12950b57cec5SDimitry Andric             currentValue.onclick = function() {
12960b57cec5SDimitry Andric                 scrollTo(document.querySelector(hrefValue));
12970b57cec5SDimitry Andric                 return false;
12980b57cec5SDimitry Andric             };
12990b57cec5SDimitry Andric         });
13000b57cec5SDimitry Andric });
13010b57cec5SDimitry Andric 
13020b57cec5SDimitry Andric var findNum = function() {
1303349cc55cSDimitry Andric     var s = document.querySelector(".msg.selected");
13040b57cec5SDimitry Andric     if (!s || s.id == "EndPath") {
13050b57cec5SDimitry Andric         return 0;
13060b57cec5SDimitry Andric     }
13070b57cec5SDimitry Andric     var out = parseInt(digitMatcher.exec(s.id)[0]);
13080b57cec5SDimitry Andric     return out;
13090b57cec5SDimitry Andric };
13100b57cec5SDimitry Andric 
1311349cc55cSDimitry Andric var classListAdd = function(el, theClass) {
1312349cc55cSDimitry Andric   if(!el.className.baseVal)
1313349cc55cSDimitry Andric     el.className += " " + theClass;
1314349cc55cSDimitry Andric   else
1315349cc55cSDimitry Andric     el.className.baseVal += " " + theClass;
1316349cc55cSDimitry Andric };
1317349cc55cSDimitry Andric 
1318349cc55cSDimitry Andric var classListRemove = function(el, theClass) {
1319349cc55cSDimitry Andric   var className = (!el.className.baseVal) ?
1320349cc55cSDimitry Andric       el.className : el.className.baseVal;
1321349cc55cSDimitry Andric     className = className.replace(" " + theClass, "");
1322349cc55cSDimitry Andric   if(!el.className.baseVal)
1323349cc55cSDimitry Andric     el.className = className;
1324349cc55cSDimitry Andric   else
1325349cc55cSDimitry Andric     el.className.baseVal = className;
1326349cc55cSDimitry Andric };
1327349cc55cSDimitry Andric 
13280b57cec5SDimitry Andric var scrollTo = function(el) {
13295ffd83dbSDimitry Andric     querySelectorAllArray(".selected").forEach(function(s) {
1330349cc55cSDimitry Andric       classListRemove(s, "selected");
13310b57cec5SDimitry Andric     });
1332349cc55cSDimitry Andric     classListAdd(el, "selected");
13330b57cec5SDimitry Andric     window.scrollBy(0, el.getBoundingClientRect().top -
13340b57cec5SDimitry Andric         (window.innerHeight / 2));
1335349cc55cSDimitry Andric     highlightArrowsForSelectedEvent();
1336349cc55cSDimitry Andric };
13370b57cec5SDimitry Andric 
13380b57cec5SDimitry Andric var move = function(num, up, numItems) {
13390b57cec5SDimitry Andric   if (num == 1 && up || num == numItems - 1 && !up) {
13400b57cec5SDimitry Andric     return 0;
13410b57cec5SDimitry Andric   } else if (num == 0 && up) {
13420b57cec5SDimitry Andric     return numItems - 1;
13430b57cec5SDimitry Andric   } else if (num == 0 && !up) {
13440b57cec5SDimitry Andric     return 1 % numItems;
13450b57cec5SDimitry Andric   }
13460b57cec5SDimitry Andric   return up ? num - 1 : num + 1;
13470b57cec5SDimitry Andric }
13480b57cec5SDimitry Andric 
13490b57cec5SDimitry Andric var numToId = function(num) {
13500b57cec5SDimitry Andric   if (num == 0) {
13510b57cec5SDimitry Andric     return document.getElementById("EndPath")
13520b57cec5SDimitry Andric   }
13530b57cec5SDimitry Andric   return document.getElementById("Path" + num);
13540b57cec5SDimitry Andric };
13550b57cec5SDimitry Andric 
13560b57cec5SDimitry Andric var navigateTo = function(up) {
13570b57cec5SDimitry Andric   var numItems = document.querySelectorAll(
13580b57cec5SDimitry Andric       ".line > .msgEvent, .line > .msgControl").length;
13590b57cec5SDimitry Andric   var currentSelected = findNum();
13600b57cec5SDimitry Andric   var newSelected = move(currentSelected, up, numItems);
13610b57cec5SDimitry Andric   var newEl = numToId(newSelected, numItems);
13620b57cec5SDimitry Andric 
13630b57cec5SDimitry Andric   // Scroll element into center.
13640b57cec5SDimitry Andric   scrollTo(newEl);
13650b57cec5SDimitry Andric };
13660b57cec5SDimitry Andric 
13670b57cec5SDimitry Andric window.addEventListener("keydown", function (event) {
13680b57cec5SDimitry Andric   if (event.defaultPrevented) {
13690b57cec5SDimitry Andric     return;
13700b57cec5SDimitry Andric   }
1371349cc55cSDimitry Andric   // key 'j'
1372349cc55cSDimitry Andric   if (event.keyCode == 74) {
13730b57cec5SDimitry Andric     navigateTo(/*up=*/false);
1374349cc55cSDimitry Andric   // key 'k'
1375349cc55cSDimitry Andric   } else if (event.keyCode == 75) {
13760b57cec5SDimitry Andric     navigateTo(/*up=*/true);
13770b57cec5SDimitry Andric   } else {
13780b57cec5SDimitry Andric     return;
13790b57cec5SDimitry Andric   }
13800b57cec5SDimitry Andric   event.preventDefault();
13810b57cec5SDimitry Andric }, true);
13820b57cec5SDimitry Andric </script>
13830b57cec5SDimitry Andric   )<<<";
13840b57cec5SDimitry Andric }
1385349cc55cSDimitry Andric 
1386349cc55cSDimitry Andric StringRef HTMLDiagnostics::generateArrowDrawingJavascript() {
1387349cc55cSDimitry Andric   return R"<<<(
1388349cc55cSDimitry Andric <script type='text/javascript'>
1389349cc55cSDimitry Andric // Return range of numbers from a range [lower, upper).
1390349cc55cSDimitry Andric function range(lower, upper) {
1391349cc55cSDimitry Andric   var array = [];
1392349cc55cSDimitry Andric   for (var i = lower; i <= upper; ++i) {
1393349cc55cSDimitry Andric       array.push(i);
1394349cc55cSDimitry Andric   }
1395349cc55cSDimitry Andric   return array;
1396349cc55cSDimitry Andric }
1397349cc55cSDimitry Andric 
1398349cc55cSDimitry Andric var getRelatedArrowIndices = function(pathId) {
1399349cc55cSDimitry Andric   // HTML numeration of events is a bit different than it is in the path.
1400349cc55cSDimitry Andric   // Everything is rotated one step to the right, so the last element
1401349cc55cSDimitry Andric   // (error diagnostic) has index 0.
1402349cc55cSDimitry Andric   if (pathId == 0) {
1403349cc55cSDimitry Andric     // arrowIndices has at least 2 elements
1404349cc55cSDimitry Andric     pathId = arrowIndices.length - 1;
1405349cc55cSDimitry Andric   }
1406349cc55cSDimitry Andric 
1407349cc55cSDimitry Andric   return range(arrowIndices[pathId], arrowIndices[pathId - 1]);
1408349cc55cSDimitry Andric }
1409349cc55cSDimitry Andric 
1410349cc55cSDimitry Andric var highlightArrowsForSelectedEvent = function() {
1411349cc55cSDimitry Andric   const selectedNum = findNum();
1412349cc55cSDimitry Andric   const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum);
1413349cc55cSDimitry Andric   arrowIndicesToHighlight.forEach((index) => {
1414349cc55cSDimitry Andric     var arrow = document.querySelector("#arrow" + index);
1415349cc55cSDimitry Andric     if(arrow) {
1416349cc55cSDimitry Andric       classListAdd(arrow, "selected")
1417349cc55cSDimitry Andric     }
1418349cc55cSDimitry Andric   });
1419349cc55cSDimitry Andric }
1420349cc55cSDimitry Andric 
1421349cc55cSDimitry Andric var getAbsoluteBoundingRect = function(element) {
1422349cc55cSDimitry Andric   const relative = element.getBoundingClientRect();
1423349cc55cSDimitry Andric   return {
1424349cc55cSDimitry Andric     left: relative.left + window.pageXOffset,
1425349cc55cSDimitry Andric     right: relative.right + window.pageXOffset,
1426349cc55cSDimitry Andric     top: relative.top + window.pageYOffset,
1427349cc55cSDimitry Andric     bottom: relative.bottom + window.pageYOffset,
1428349cc55cSDimitry Andric     height: relative.height,
1429349cc55cSDimitry Andric     width: relative.width
1430349cc55cSDimitry Andric   };
1431349cc55cSDimitry Andric }
1432349cc55cSDimitry Andric 
1433349cc55cSDimitry Andric var drawArrow = function(index) {
1434349cc55cSDimitry Andric   // This function is based on the great answer from SO:
1435349cc55cSDimitry Andric   //   https://stackoverflow.com/a/39575674/11582326
1436349cc55cSDimitry Andric   var start = document.querySelector("#start" + index);
1437349cc55cSDimitry Andric   var end   = document.querySelector("#end" + index);
1438349cc55cSDimitry Andric   var arrow = document.querySelector("#arrow" + index);
1439349cc55cSDimitry Andric 
1440349cc55cSDimitry Andric   var startRect = getAbsoluteBoundingRect(start);
1441349cc55cSDimitry Andric   var endRect   = getAbsoluteBoundingRect(end);
1442349cc55cSDimitry Andric 
1443349cc55cSDimitry Andric   // It is an arrow from a token to itself, no need to visualize it.
1444349cc55cSDimitry Andric   if (startRect.top == endRect.top &&
1445349cc55cSDimitry Andric       startRect.left == endRect.left)
1446349cc55cSDimitry Andric     return;
1447349cc55cSDimitry Andric 
1448349cc55cSDimitry Andric   // Each arrow is a very simple Bézier curve, with two nodes and
1449349cc55cSDimitry Andric   // two handles.  So, we need to calculate four points in the window:
1450349cc55cSDimitry Andric   //   * start node
1451349cc55cSDimitry Andric   var posStart    = { x: 0, y: 0 };
1452349cc55cSDimitry Andric   //   * end node
1453349cc55cSDimitry Andric   var posEnd      = { x: 0, y: 0 };
1454349cc55cSDimitry Andric   //   * handle for the start node
1455349cc55cSDimitry Andric   var startHandle = { x: 0, y: 0 };
1456349cc55cSDimitry Andric   //   * handle for the end node
1457349cc55cSDimitry Andric   var endHandle   = { x: 0, y: 0 };
1458349cc55cSDimitry Andric   // One can visualize it as follows:
1459349cc55cSDimitry Andric   //
1460349cc55cSDimitry Andric   //         start handle
1461349cc55cSDimitry Andric   //        /
1462349cc55cSDimitry Andric   //       X"""_.-""""X
1463349cc55cSDimitry Andric   //         .'        \
1464349cc55cSDimitry Andric   //        /           start node
1465349cc55cSDimitry Andric   //       |
1466349cc55cSDimitry Andric   //       |
1467349cc55cSDimitry Andric   //       |      end node
1468349cc55cSDimitry Andric   //        \    /
1469349cc55cSDimitry Andric   //         `->X
1470349cc55cSDimitry Andric   //        X-'
1471349cc55cSDimitry Andric   //         \
1472349cc55cSDimitry Andric   //          end handle
1473349cc55cSDimitry Andric   //
1474349cc55cSDimitry Andric   // NOTE: (0, 0) is the top left corner of the window.
1475349cc55cSDimitry Andric 
1476349cc55cSDimitry Andric   // We have 3 similar, but still different scenarios to cover:
1477349cc55cSDimitry Andric   //
1478349cc55cSDimitry Andric   //   1. Two tokens on different lines.
1479349cc55cSDimitry Andric   //             -xxx
1480349cc55cSDimitry Andric   //           /
1481349cc55cSDimitry Andric   //           \
1482349cc55cSDimitry Andric   //             -> xxx
1483349cc55cSDimitry Andric   //      In this situation, we draw arrow on the left curving to the left.
1484349cc55cSDimitry Andric   //   2. Two tokens on the same line, and the destination is on the right.
1485349cc55cSDimitry Andric   //             ____
1486349cc55cSDimitry Andric   //            /    \
1487349cc55cSDimitry Andric   //           /      V
1488349cc55cSDimitry Andric   //        xxx        xxx
1489349cc55cSDimitry Andric   //      In this situation, we draw arrow above curving upwards.
1490349cc55cSDimitry Andric   //   3. Two tokens on the same line, and the destination is on the left.
1491349cc55cSDimitry Andric   //        xxx        xxx
1492349cc55cSDimitry Andric   //           ^      /
1493349cc55cSDimitry Andric   //            \____/
1494349cc55cSDimitry Andric   //      In this situation, we draw arrow below curving downwards.
1495349cc55cSDimitry Andric   const onDifferentLines = startRect.top <= endRect.top - 5 ||
1496349cc55cSDimitry Andric     startRect.top >= endRect.top + 5;
1497349cc55cSDimitry Andric   const leftToRight = startRect.left < endRect.left;
1498349cc55cSDimitry Andric 
1499349cc55cSDimitry Andric   // NOTE: various magic constants are chosen empirically for
1500349cc55cSDimitry Andric   //       better positioning and look
1501349cc55cSDimitry Andric   if (onDifferentLines) {
1502349cc55cSDimitry Andric     // Case #1
1503349cc55cSDimitry Andric     const topToBottom = startRect.top < endRect.top;
1504349cc55cSDimitry Andric     posStart.x = startRect.left - 1;
1505349cc55cSDimitry Andric     // We don't want to start it at the top left corner of the token,
1506349cc55cSDimitry Andric     // it doesn't feel like this is where the arrow comes from.
1507349cc55cSDimitry Andric     // For this reason, we start it in the middle of the left side
1508349cc55cSDimitry Andric     // of the token.
1509349cc55cSDimitry Andric     posStart.y = startRect.top + startRect.height / 2;
1510349cc55cSDimitry Andric 
1511349cc55cSDimitry Andric     // End node has arrow head and we give it a bit more space.
1512349cc55cSDimitry Andric     posEnd.x = endRect.left - 4;
1513349cc55cSDimitry Andric     posEnd.y = endRect.top;
1514349cc55cSDimitry Andric 
1515349cc55cSDimitry Andric     // Utility object with x and y offsets for handles.
1516349cc55cSDimitry Andric     var curvature = {
1517349cc55cSDimitry Andric       // We want bottom-to-top arrow to curve a bit more, so it doesn't
1518349cc55cSDimitry Andric       // overlap much with top-to-bottom curves (much more frequent).
1519349cc55cSDimitry Andric       x: topToBottom ? 15 : 25,
1520349cc55cSDimitry Andric       y: Math.min((posEnd.y - posStart.y) / 3, 10)
1521349cc55cSDimitry Andric     }
1522349cc55cSDimitry Andric 
1523349cc55cSDimitry Andric     // When destination is on the different line, we can make a
1524349cc55cSDimitry Andric     // curvier arrow because we have space for it.
1525349cc55cSDimitry Andric     // So, instead of using
1526349cc55cSDimitry Andric     //
1527349cc55cSDimitry Andric     //   startHandle.x = posStart.x - curvature.x
1528349cc55cSDimitry Andric     //   endHandle.x   = posEnd.x - curvature.x
1529349cc55cSDimitry Andric     //
1530349cc55cSDimitry Andric     // We use the leftmost of these two values for both handles.
1531349cc55cSDimitry Andric     startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x;
1532349cc55cSDimitry Andric     endHandle.x = startHandle.x;
1533349cc55cSDimitry Andric 
1534349cc55cSDimitry Andric     // Curving downwards from the start node...
1535349cc55cSDimitry Andric     startHandle.y = posStart.y + curvature.y;
1536349cc55cSDimitry Andric     // ... and upwards from the end node.
1537349cc55cSDimitry Andric     endHandle.y = posEnd.y - curvature.y;
1538349cc55cSDimitry Andric 
1539349cc55cSDimitry Andric   } else if (leftToRight) {
1540349cc55cSDimitry Andric     // Case #2
1541349cc55cSDimitry Andric     // Starting from the top right corner...
1542349cc55cSDimitry Andric     posStart.x = startRect.right - 1;
1543349cc55cSDimitry Andric     posStart.y = startRect.top;
1544349cc55cSDimitry Andric 
1545349cc55cSDimitry Andric     // ...and ending at the top left corner of the end token.
1546349cc55cSDimitry Andric     posEnd.x = endRect.left + 1;
1547349cc55cSDimitry Andric     posEnd.y = endRect.top - 1;
1548349cc55cSDimitry Andric 
1549349cc55cSDimitry Andric     // Utility object with x and y offsets for handles.
1550349cc55cSDimitry Andric     var curvature = {
1551349cc55cSDimitry Andric       x: Math.min((posEnd.x - posStart.x) / 3, 15),
1552349cc55cSDimitry Andric       y: 5
1553349cc55cSDimitry Andric     }
1554349cc55cSDimitry Andric 
1555349cc55cSDimitry Andric     // Curving to the right...
1556349cc55cSDimitry Andric     startHandle.x = posStart.x + curvature.x;
1557349cc55cSDimitry Andric     // ... and upwards from the start node.
1558349cc55cSDimitry Andric     startHandle.y = posStart.y - curvature.y;
1559349cc55cSDimitry Andric 
1560349cc55cSDimitry Andric     // And to the left...
1561349cc55cSDimitry Andric     endHandle.x = posEnd.x - curvature.x;
1562349cc55cSDimitry Andric     // ... and upwards from the end node.
1563349cc55cSDimitry Andric     endHandle.y = posEnd.y - curvature.y;
1564349cc55cSDimitry Andric 
1565349cc55cSDimitry Andric   } else {
1566349cc55cSDimitry Andric     // Case #3
1567349cc55cSDimitry Andric     // Starting from the bottom right corner...
1568349cc55cSDimitry Andric     posStart.x = startRect.right;
1569349cc55cSDimitry Andric     posStart.y = startRect.bottom;
1570349cc55cSDimitry Andric 
1571349cc55cSDimitry Andric     // ...and ending also at the bottom right corner, but of the end token.
1572349cc55cSDimitry Andric     posEnd.x = endRect.right - 1;
1573349cc55cSDimitry Andric     posEnd.y = endRect.bottom + 1;
1574349cc55cSDimitry Andric 
1575349cc55cSDimitry Andric     // Utility object with x and y offsets for handles.
1576349cc55cSDimitry Andric     var curvature = {
1577349cc55cSDimitry Andric       x: Math.min((posStart.x - posEnd.x) / 3, 15),
1578349cc55cSDimitry Andric       y: 5
1579349cc55cSDimitry Andric     }
1580349cc55cSDimitry Andric 
1581349cc55cSDimitry Andric     // Curving to the left...
1582349cc55cSDimitry Andric     startHandle.x = posStart.x - curvature.x;
1583349cc55cSDimitry Andric     // ... and downwards from the start node.
1584349cc55cSDimitry Andric     startHandle.y = posStart.y + curvature.y;
1585349cc55cSDimitry Andric 
1586349cc55cSDimitry Andric     // And to the right...
1587349cc55cSDimitry Andric     endHandle.x = posEnd.x + curvature.x;
1588349cc55cSDimitry Andric     // ... and downwards from the end node.
1589349cc55cSDimitry Andric     endHandle.y = posEnd.y + curvature.y;
1590349cc55cSDimitry Andric   }
1591349cc55cSDimitry Andric 
1592349cc55cSDimitry Andric   // Put it all together into a path.
1593349cc55cSDimitry Andric   // More information on the format:
1594349cc55cSDimitry Andric   //   https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
1595349cc55cSDimitry Andric   var pathStr = "M" + posStart.x + "," + posStart.y + " " +
1596349cc55cSDimitry Andric     "C" + startHandle.x + "," + startHandle.y + " " +
1597349cc55cSDimitry Andric     endHandle.x + "," + endHandle.y + " " +
1598349cc55cSDimitry Andric     posEnd.x + "," + posEnd.y;
1599349cc55cSDimitry Andric 
1600349cc55cSDimitry Andric   arrow.setAttribute("d", pathStr);
1601349cc55cSDimitry Andric };
1602349cc55cSDimitry Andric 
1603349cc55cSDimitry Andric var drawArrows = function() {
1604349cc55cSDimitry Andric   const numOfArrows = document.querySelectorAll("path[id^=arrow]").length;
1605349cc55cSDimitry Andric   for (var i = 0; i < numOfArrows; ++i) {
1606349cc55cSDimitry Andric     drawArrow(i);
1607349cc55cSDimitry Andric   }
1608349cc55cSDimitry Andric }
1609349cc55cSDimitry Andric 
1610349cc55cSDimitry Andric var toggleArrows = function(event) {
1611349cc55cSDimitry Andric   const arrows = document.querySelector("#arrows");
1612349cc55cSDimitry Andric   if (event.target.checked) {
1613349cc55cSDimitry Andric     arrows.setAttribute("visibility", "visible");
1614349cc55cSDimitry Andric   } else {
1615349cc55cSDimitry Andric     arrows.setAttribute("visibility", "hidden");
1616349cc55cSDimitry Andric   }
1617349cc55cSDimitry Andric }
1618349cc55cSDimitry Andric 
1619349cc55cSDimitry Andric window.addEventListener("resize", drawArrows);
1620349cc55cSDimitry Andric document.addEventListener("DOMContentLoaded", function() {
1621349cc55cSDimitry Andric   // Whenever we show invocation, locations change, i.e. we
1622349cc55cSDimitry Andric   // need to redraw arrows.
1623349cc55cSDimitry Andric   document
1624349cc55cSDimitry Andric     .querySelector('input[id="showinvocation"]')
1625349cc55cSDimitry Andric     .addEventListener("click", drawArrows);
1626349cc55cSDimitry Andric   // Hiding irrelevant lines also should cause arrow rerender.
1627349cc55cSDimitry Andric   document
1628349cc55cSDimitry Andric     .querySelector('input[name="showCounterexample"]')
1629349cc55cSDimitry Andric     .addEventListener("change", drawArrows);
1630349cc55cSDimitry Andric   document
1631349cc55cSDimitry Andric     .querySelector('input[name="showArrows"]')
1632349cc55cSDimitry Andric     .addEventListener("change", toggleArrows);
1633349cc55cSDimitry Andric   drawArrows();
1634349cc55cSDimitry Andric   // Default highlighting for the last event.
1635349cc55cSDimitry Andric   highlightArrowsForSelectedEvent();
1636349cc55cSDimitry Andric });
1637349cc55cSDimitry Andric </script>
1638349cc55cSDimitry Andric   )<<<";
1639349cc55cSDimitry Andric }
1640