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