19c4b2225SAlexander Belyaev //===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===// 29c4b2225SAlexander Belyaev // 39c4b2225SAlexander Belyaev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 49c4b2225SAlexander Belyaev // See https://llvm.org/LICENSE.txt for license information. 59c4b2225SAlexander Belyaev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 69c4b2225SAlexander Belyaev // 79c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 89c4b2225SAlexander Belyaev // 99c4b2225SAlexander Belyaev // This file defines the HTMLDiagnostics object. 109c4b2225SAlexander Belyaev // 119c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 129c4b2225SAlexander Belyaev 139c4b2225SAlexander Belyaev #include "clang/AST/Decl.h" 149c4b2225SAlexander Belyaev #include "clang/AST/DeclBase.h" 159c4b2225SAlexander Belyaev #include "clang/AST/Stmt.h" 167c58fb6bSBalazs Benics #include "clang/Analysis/IssueHash.h" 177c58fb6bSBalazs Benics #include "clang/Analysis/MacroExpansionContext.h" 187c58fb6bSBalazs Benics #include "clang/Analysis/PathDiagnostic.h" 199c4b2225SAlexander Belyaev #include "clang/Basic/FileManager.h" 209c4b2225SAlexander Belyaev #include "clang/Basic/LLVM.h" 219c4b2225SAlexander Belyaev #include "clang/Basic/SourceLocation.h" 229c4b2225SAlexander Belyaev #include "clang/Basic/SourceManager.h" 239c4b2225SAlexander Belyaev #include "clang/Lex/Lexer.h" 249c4b2225SAlexander Belyaev #include "clang/Lex/Preprocessor.h" 259c4b2225SAlexander Belyaev #include "clang/Lex/Token.h" 269c4b2225SAlexander Belyaev #include "clang/Rewrite/Core/HTMLRewrite.h" 279c4b2225SAlexander Belyaev #include "clang/Rewrite/Core/Rewriter.h" 289c4b2225SAlexander Belyaev #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" 299c4b2225SAlexander Belyaev #include "llvm/ADT/ArrayRef.h" 300d150db2SJacques Pienaar #include "llvm/ADT/RewriteBuffer.h" 319e02f587SValeriy Savchenko #include "llvm/ADT/STLExtras.h" 3297bcafa2SValeriy Savchenko #include "llvm/ADT/Sequence.h" 339c4b2225SAlexander Belyaev #include "llvm/ADT/SmallString.h" 349c4b2225SAlexander Belyaev #include "llvm/ADT/StringRef.h" 359c4b2225SAlexander Belyaev #include "llvm/ADT/iterator_range.h" 369c4b2225SAlexander Belyaev #include "llvm/Support/Casting.h" 379c4b2225SAlexander Belyaev #include "llvm/Support/Errc.h" 389c4b2225SAlexander Belyaev #include "llvm/Support/ErrorHandling.h" 399c4b2225SAlexander Belyaev #include "llvm/Support/FileSystem.h" 409c4b2225SAlexander Belyaev #include "llvm/Support/MemoryBuffer.h" 419c4b2225SAlexander Belyaev #include "llvm/Support/Path.h" 429c4b2225SAlexander Belyaev #include "llvm/Support/raw_ostream.h" 439c4b2225SAlexander Belyaev #include <algorithm> 449c4b2225SAlexander Belyaev #include <cassert> 459c4b2225SAlexander Belyaev #include <map> 469c4b2225SAlexander Belyaev #include <memory> 479c4b2225SAlexander Belyaev #include <set> 489c4b2225SAlexander Belyaev #include <sstream> 499c4b2225SAlexander Belyaev #include <string> 509c4b2225SAlexander Belyaev #include <system_error> 519c4b2225SAlexander Belyaev #include <utility> 529c4b2225SAlexander Belyaev #include <vector> 539c4b2225SAlexander Belyaev 549c4b2225SAlexander Belyaev using namespace clang; 559c4b2225SAlexander Belyaev using namespace ento; 560d150db2SJacques Pienaar using llvm::RewriteBuffer; 579c4b2225SAlexander Belyaev 589c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 599c4b2225SAlexander Belyaev // Boilerplate. 609c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 619c4b2225SAlexander Belyaev 629c4b2225SAlexander Belyaev namespace { 639c4b2225SAlexander Belyaev 649e02f587SValeriy Savchenko class ArrowMap; 659e02f587SValeriy Savchenko 669c4b2225SAlexander Belyaev class HTMLDiagnostics : public PathDiagnosticConsumer { 679c4b2225SAlexander Belyaev PathDiagnosticConsumerOptions DiagOpts; 689c4b2225SAlexander Belyaev std::string Directory; 699c4b2225SAlexander Belyaev bool createdDir = false; 709c4b2225SAlexander Belyaev bool noDir = false; 719c4b2225SAlexander Belyaev const Preprocessor &PP; 729c4b2225SAlexander Belyaev const bool SupportsCrossFileDiagnostics; 73721dd3bcSArtem Dergachev llvm::StringSet<> EmittedHashes; 74243bfed6SArtem Dergachev html::RelexRewriteCacheRef RewriterCache = 75243bfed6SArtem Dergachev html::instantiateRelexRewriteCache(); 769c4b2225SAlexander Belyaev 779c4b2225SAlexander Belyaev public: 789c4b2225SAlexander Belyaev HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts, 799c4b2225SAlexander Belyaev const std::string &OutputDir, const Preprocessor &pp, 809c4b2225SAlexander Belyaev bool supportsMultipleFiles) 819c4b2225SAlexander Belyaev : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp), 829c4b2225SAlexander Belyaev SupportsCrossFileDiagnostics(supportsMultipleFiles) {} 839c4b2225SAlexander Belyaev 849c4b2225SAlexander Belyaev ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } 859c4b2225SAlexander Belyaev 869c4b2225SAlexander Belyaev void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 879c4b2225SAlexander Belyaev FilesMade *filesMade) override; 889c4b2225SAlexander Belyaev 8997bcafa2SValeriy Savchenko StringRef getName() const override { return "HTMLDiagnostics"; } 909c4b2225SAlexander Belyaev 919c4b2225SAlexander Belyaev bool supportsCrossFileDiagnostics() const override { 929c4b2225SAlexander Belyaev return SupportsCrossFileDiagnostics; 939c4b2225SAlexander Belyaev } 949c4b2225SAlexander Belyaev 9597bcafa2SValeriy Savchenko unsigned ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece &P, 969c4b2225SAlexander Belyaev unsigned num); 979c4b2225SAlexander Belyaev 9897bcafa2SValeriy Savchenko unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID, 9997bcafa2SValeriy Savchenko const PathDiagnosticControlFlowPiece &P, 10097bcafa2SValeriy Savchenko unsigned Number); 10197bcafa2SValeriy Savchenko 1029c4b2225SAlexander Belyaev void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P, 1039c4b2225SAlexander Belyaev const std::vector<SourceRange> &PopUpRanges, unsigned num, 1049c4b2225SAlexander Belyaev unsigned max); 1059c4b2225SAlexander Belyaev 1069c4b2225SAlexander Belyaev void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range, 1079c4b2225SAlexander Belyaev const char *HighlightStart = "<span class=\"mrange\">", 1089c4b2225SAlexander Belyaev const char *HighlightEnd = "</span>"); 1099c4b2225SAlexander Belyaev 11097bcafa2SValeriy Savchenko void ReportDiag(const PathDiagnostic &D, FilesMade *filesMade); 1119c4b2225SAlexander Belyaev 1129c4b2225SAlexander Belyaev // Generate the full HTML report 1139c4b2225SAlexander Belyaev std::string GenerateHTML(const PathDiagnostic &D, Rewriter &R, 1149c4b2225SAlexander Belyaev const SourceManager &SMgr, const PathPieces &path, 1159c4b2225SAlexander Belyaev const char *declName); 1169c4b2225SAlexander Belyaev 1179c4b2225SAlexander Belyaev // Add HTML header/footers to file specified by FID 1189c4b2225SAlexander Belyaev void FinalizeHTML(const PathDiagnostic &D, Rewriter &R, 1199c4b2225SAlexander Belyaev const SourceManager &SMgr, const PathPieces &path, 120523c4712SJan Svoboda FileID FID, FileEntryRef Entry, const char *declName); 1219c4b2225SAlexander Belyaev 1229c4b2225SAlexander Belyaev // Rewrite the file specified by FID with HTML formatting. 1239c4b2225SAlexander Belyaev void RewriteFile(Rewriter &R, const PathPieces &path, FileID FID); 1249c4b2225SAlexander Belyaev 12597bcafa2SValeriy Savchenko PathGenerationScheme getGenerationScheme() const override { 12697bcafa2SValeriy Savchenko return Everything; 12797bcafa2SValeriy Savchenko } 1289c4b2225SAlexander Belyaev 1299c4b2225SAlexander Belyaev private: 1309e02f587SValeriy Savchenko void addArrowSVGs(Rewriter &R, FileID BugFileID, 1319e02f587SValeriy Savchenko const ArrowMap &ArrowIndices); 13297bcafa2SValeriy Savchenko 1339c4b2225SAlexander Belyaev /// \return Javascript for displaying shortcuts help; 1349c4b2225SAlexander Belyaev StringRef showHelpJavascript(); 1359c4b2225SAlexander Belyaev 1369c4b2225SAlexander Belyaev /// \return Javascript for navigating the HTML report using j/k keys. 1379c4b2225SAlexander Belyaev StringRef generateKeyboardNavigationJavascript(); 1389c4b2225SAlexander Belyaev 13997bcafa2SValeriy Savchenko /// \return Javascript for drawing control-flow arrows. 14097bcafa2SValeriy Savchenko StringRef generateArrowDrawingJavascript(); 14197bcafa2SValeriy Savchenko 1429c4b2225SAlexander Belyaev /// \return JavaScript for an option to only show relevant lines. 14397bcafa2SValeriy Savchenko std::string showRelevantLinesJavascript(const PathDiagnostic &D, 14497bcafa2SValeriy Savchenko const PathPieces &path); 1459c4b2225SAlexander Belyaev 1469c4b2225SAlexander Belyaev /// Write executed lines from \p D in JSON format into \p os. 14797bcafa2SValeriy Savchenko void dumpCoverageData(const PathDiagnostic &D, const PathPieces &path, 1489c4b2225SAlexander Belyaev llvm::raw_string_ostream &os); 1499c4b2225SAlexander Belyaev }; 1509c4b2225SAlexander Belyaev 15197bcafa2SValeriy Savchenko bool isArrowPiece(const PathDiagnosticPiece &P) { 15297bcafa2SValeriy Savchenko return isa<PathDiagnosticControlFlowPiece>(P) && P.getString().empty(); 15397bcafa2SValeriy Savchenko } 15497bcafa2SValeriy Savchenko 15597bcafa2SValeriy Savchenko unsigned getPathSizeWithoutArrows(const PathPieces &Path) { 15697bcafa2SValeriy Savchenko unsigned TotalPieces = Path.size(); 15797bcafa2SValeriy Savchenko unsigned TotalArrowPieces = llvm::count_if( 15897bcafa2SValeriy Savchenko Path, [](const PathDiagnosticPieceRef &P) { return isArrowPiece(*P); }); 15997bcafa2SValeriy Savchenko return TotalPieces - TotalArrowPieces; 16097bcafa2SValeriy Savchenko } 16197bcafa2SValeriy Savchenko 1629e02f587SValeriy Savchenko class ArrowMap : public std::vector<unsigned> { 1639e02f587SValeriy Savchenko using Base = std::vector<unsigned>; 1649e02f587SValeriy Savchenko 1659e02f587SValeriy Savchenko public: 1669e02f587SValeriy Savchenko ArrowMap(unsigned Size) : Base(Size, 0) {} 1679e02f587SValeriy Savchenko unsigned getTotalNumberOfArrows() const { return at(0); } 1689e02f587SValeriy Savchenko }; 1699e02f587SValeriy Savchenko 1709e02f587SValeriy Savchenko llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ArrowMap &Indices) { 1719e02f587SValeriy Savchenko OS << "[ "; 1729e02f587SValeriy Savchenko llvm::interleave(Indices, OS, ","); 1739e02f587SValeriy Savchenko return OS << " ]"; 1749e02f587SValeriy Savchenko } 1759e02f587SValeriy Savchenko 1769c4b2225SAlexander Belyaev } // namespace 1779c4b2225SAlexander Belyaev 1789c4b2225SAlexander Belyaev void ento::createHTMLDiagnosticConsumer( 1799c4b2225SAlexander Belyaev PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, 1809c4b2225SAlexander Belyaev const std::string &OutputDir, const Preprocessor &PP, 1817c58fb6bSBalazs Benics const cross_tu::CrossTranslationUnitContext &CTU, 1827c58fb6bSBalazs Benics const MacroExpansionContext &MacroExpansions) { 1839c4b2225SAlexander Belyaev 1849c4b2225SAlexander Belyaev // FIXME: HTML is currently our default output type, but if the output 1859c4b2225SAlexander Belyaev // directory isn't specified, it acts like if it was in the minimal text 1869c4b2225SAlexander Belyaev // output mode. This doesn't make much sense, we should have the minimal text 1879c4b2225SAlexander Belyaev // as our default. In the case of backward compatibility concerns, this could 1889c4b2225SAlexander Belyaev // be preserved with -analyzer-config-compatibility-mode=true. 1897c58fb6bSBalazs Benics createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU, 1907c58fb6bSBalazs Benics MacroExpansions); 1919c4b2225SAlexander Belyaev 1929c4b2225SAlexander Belyaev // TODO: Emit an error here. 1939c4b2225SAlexander Belyaev if (OutputDir.empty()) 1949c4b2225SAlexander Belyaev return; 1959c4b2225SAlexander Belyaev 1969c4b2225SAlexander Belyaev C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, true)); 1979c4b2225SAlexander Belyaev } 1989c4b2225SAlexander Belyaev 1999c4b2225SAlexander Belyaev void ento::createHTMLSingleFileDiagnosticConsumer( 2009c4b2225SAlexander Belyaev PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, 2019c4b2225SAlexander Belyaev const std::string &OutputDir, const Preprocessor &PP, 2027c58fb6bSBalazs Benics const cross_tu::CrossTranslationUnitContext &CTU, 2037c58fb6bSBalazs Benics const clang::MacroExpansionContext &MacroExpansions) { 2047c58fb6bSBalazs Benics createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU, 2057c58fb6bSBalazs Benics MacroExpansions); 2069c4b2225SAlexander Belyaev 2079c4b2225SAlexander Belyaev // TODO: Emit an error here. 2089c4b2225SAlexander Belyaev if (OutputDir.empty()) 2099c4b2225SAlexander Belyaev return; 2109c4b2225SAlexander Belyaev 2119c4b2225SAlexander Belyaev C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, false)); 2129c4b2225SAlexander Belyaev } 2139c4b2225SAlexander Belyaev 2149c4b2225SAlexander Belyaev void ento::createPlistHTMLDiagnosticConsumer( 2159c4b2225SAlexander Belyaev PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, 2169c4b2225SAlexander Belyaev const std::string &prefix, const Preprocessor &PP, 2177c58fb6bSBalazs Benics const cross_tu::CrossTranslationUnitContext &CTU, 2187c58fb6bSBalazs Benics const MacroExpansionContext &MacroExpansions) { 2199c4b2225SAlexander Belyaev createHTMLDiagnosticConsumer( 2207c58fb6bSBalazs Benics DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, CTU, 2217c58fb6bSBalazs Benics MacroExpansions); 2227c58fb6bSBalazs Benics createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU, 2237c58fb6bSBalazs Benics MacroExpansions); 2249c4b2225SAlexander Belyaev createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP, 2257c58fb6bSBalazs Benics CTU, MacroExpansions); 2269c4b2225SAlexander Belyaev } 2279c4b2225SAlexander Belyaev 2282407eb08SDaniel Hwang void ento::createSarifHTMLDiagnosticConsumer( 2292407eb08SDaniel Hwang PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, 2302407eb08SDaniel Hwang const std::string &sarif_file, const Preprocessor &PP, 2317c58fb6bSBalazs Benics const cross_tu::CrossTranslationUnitContext &CTU, 2327c58fb6bSBalazs Benics const MacroExpansionContext &MacroExpansions) { 2337c58fb6bSBalazs Benics createHTMLDiagnosticConsumer( 2347c58fb6bSBalazs Benics DiagOpts, C, std::string(llvm::sys::path::parent_path(sarif_file)), PP, 2357c58fb6bSBalazs Benics CTU, MacroExpansions); 2367c58fb6bSBalazs Benics createSarifDiagnosticConsumer(DiagOpts, C, sarif_file, PP, CTU, 2377c58fb6bSBalazs Benics MacroExpansions); 2387c58fb6bSBalazs Benics createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, sarif_file, 2397c58fb6bSBalazs Benics PP, CTU, MacroExpansions); 2402407eb08SDaniel Hwang } 2412407eb08SDaniel Hwang 2429c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 2439c4b2225SAlexander Belyaev // Report processing. 2449c4b2225SAlexander Belyaev //===----------------------------------------------------------------------===// 2459c4b2225SAlexander Belyaev 2469c4b2225SAlexander Belyaev void HTMLDiagnostics::FlushDiagnosticsImpl( 2479c4b2225SAlexander Belyaev std::vector<const PathDiagnostic *> &Diags, 2489c4b2225SAlexander Belyaev FilesMade *filesMade) { 2499c4b2225SAlexander Belyaev for (const auto Diag : Diags) 2509c4b2225SAlexander Belyaev ReportDiag(*Diag, filesMade); 2519c4b2225SAlexander Belyaev } 2529c4b2225SAlexander Belyaev 25373093599SArtem Dergachev static llvm::SmallString<32> getIssueHash(const PathDiagnostic &D, 25473093599SArtem Dergachev const Preprocessor &PP) { 25573093599SArtem Dergachev SourceManager &SMgr = PP.getSourceManager(); 25673093599SArtem Dergachev PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); 25773093599SArtem Dergachev FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() 25873093599SArtem Dergachev ? UPDLoc.asLocation() 25973093599SArtem Dergachev : D.getLocation().asLocation()), 26073093599SArtem Dergachev SMgr); 26173093599SArtem Dergachev return getIssueHash(L, D.getCheckerName(), D.getBugType(), 26273093599SArtem Dergachev D.getDeclWithIssue(), PP.getLangOpts()); 26373093599SArtem Dergachev } 26473093599SArtem Dergachev 2659c4b2225SAlexander Belyaev void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, 2669c4b2225SAlexander Belyaev FilesMade *filesMade) { 2679c4b2225SAlexander Belyaev // Create the HTML directory if it is missing. 2689c4b2225SAlexander Belyaev if (!createdDir) { 2699c4b2225SAlexander Belyaev createdDir = true; 2709c4b2225SAlexander Belyaev if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { 2719c4b2225SAlexander Belyaev llvm::errs() << "warning: could not create directory '" 2729c4b2225SAlexander Belyaev << Directory << "': " << ec.message() << '\n'; 2739c4b2225SAlexander Belyaev noDir = true; 2749c4b2225SAlexander Belyaev return; 2759c4b2225SAlexander Belyaev } 2769c4b2225SAlexander Belyaev } 2779c4b2225SAlexander Belyaev 2789c4b2225SAlexander Belyaev if (noDir) 2799c4b2225SAlexander Belyaev return; 2809c4b2225SAlexander Belyaev 2819c4b2225SAlexander Belyaev // First flatten out the entire path to make it easier to use. 2829c4b2225SAlexander Belyaev PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false); 2839c4b2225SAlexander Belyaev 2849c4b2225SAlexander Belyaev // The path as already been prechecked that the path is non-empty. 2859c4b2225SAlexander Belyaev assert(!path.empty()); 2869c4b2225SAlexander Belyaev const SourceManager &SMgr = path.front()->getLocation().getManager(); 2879c4b2225SAlexander Belyaev 2889c4b2225SAlexander Belyaev // Create a new rewriter to generate HTML. 2899c4b2225SAlexander Belyaev Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); 2909c4b2225SAlexander Belyaev 2919c4b2225SAlexander Belyaev // Get the function/method name 2929c4b2225SAlexander Belyaev SmallString<128> declName("unknown"); 2939c4b2225SAlexander Belyaev int offsetDecl = 0; 2949c4b2225SAlexander Belyaev if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { 2959c4b2225SAlexander Belyaev if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue)) 2969c4b2225SAlexander Belyaev declName = ND->getDeclName().getAsString(); 2979c4b2225SAlexander Belyaev 2989c4b2225SAlexander Belyaev if (const Stmt *Body = DeclWithIssue->getBody()) { 2999c4b2225SAlexander Belyaev // Retrieve the relative position of the declaration which will be used 3009c4b2225SAlexander Belyaev // for the file name 3019c4b2225SAlexander Belyaev FullSourceLoc L( 3029c4b2225SAlexander Belyaev SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), 3039c4b2225SAlexander Belyaev SMgr); 3049c4b2225SAlexander Belyaev FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr); 3059c4b2225SAlexander Belyaev offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); 3069c4b2225SAlexander Belyaev } 3079c4b2225SAlexander Belyaev } 3089c4b2225SAlexander Belyaev 309721dd3bcSArtem Dergachev SmallString<32> IssueHash = getIssueHash(D, PP); 310721dd3bcSArtem Dergachev auto [It, IsNew] = EmittedHashes.insert(IssueHash); 311721dd3bcSArtem Dergachev if (!IsNew) { 312721dd3bcSArtem Dergachev // We've already emitted a duplicate issue. It'll get overwritten anyway. 313721dd3bcSArtem Dergachev return; 314721dd3bcSArtem Dergachev } 315721dd3bcSArtem Dergachev 3169c4b2225SAlexander Belyaev std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str()); 3179c4b2225SAlexander Belyaev if (report.empty()) { 3189c4b2225SAlexander Belyaev llvm::errs() << "warning: no diagnostics generated for main file.\n"; 3199c4b2225SAlexander Belyaev return; 3209c4b2225SAlexander Belyaev } 3219c4b2225SAlexander Belyaev 3229c4b2225SAlexander Belyaev // Create a path for the target HTML file. 3239c4b2225SAlexander Belyaev int FD; 3249c4b2225SAlexander Belyaev 32573093599SArtem Dergachev SmallString<128> FileNameStr; 32673093599SArtem Dergachev llvm::raw_svector_ostream FileName(FileNameStr); 32773093599SArtem Dergachev FileName << "report-"; 32873093599SArtem Dergachev 32973093599SArtem Dergachev // Historically, neither the stable report filename nor the unstable report 33073093599SArtem Dergachev // filename were actually stable. That said, the stable report filename 33173093599SArtem Dergachev // was more stable because it was mostly composed of information 33273093599SArtem Dergachev // about the bug report instead of being completely random. 33373093599SArtem Dergachev // Now both stable and unstable report filenames are in fact stable 33473093599SArtem Dergachev // but the stable report filename is still more verbose. 33573093599SArtem Dergachev if (DiagOpts.ShouldWriteVerboseReportFilename) { 33673093599SArtem Dergachev // FIXME: This code relies on knowing what constitutes the issue hash. 33773093599SArtem Dergachev // Otherwise deduplication won't work correctly. 33873093599SArtem Dergachev FileID ReportFile = 33973093599SArtem Dergachev path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); 34073093599SArtem Dergachev 341523c4712SJan Svoboda OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(ReportFile); 34273093599SArtem Dergachev 34373093599SArtem Dergachev FileName << llvm::sys::path::filename(Entry->getName()).str() << "-" 34473093599SArtem Dergachev << declName.c_str() << "-" << offsetDecl << "-"; 34573093599SArtem Dergachev } 34673093599SArtem Dergachev 347721dd3bcSArtem Dergachev FileName << StringRef(IssueHash).substr(0, 6).str() << ".html"; 34873093599SArtem Dergachev 34973093599SArtem Dergachev SmallString<128> ResultPath; 35073093599SArtem Dergachev llvm::sys::path::append(ResultPath, Directory, FileName.str()); 35173093599SArtem Dergachev if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) { 35273093599SArtem Dergachev llvm::errs() << "warning: could not make '" << ResultPath 3539c4b2225SAlexander Belyaev << "' absolute: " << EC.message() << '\n'; 3549c4b2225SAlexander Belyaev return; 3559c4b2225SAlexander Belyaev } 35673093599SArtem Dergachev 35773093599SArtem Dergachev if (std::error_code EC = llvm::sys::fs::openFileForReadWrite( 35873093599SArtem Dergachev ResultPath, FD, llvm::sys::fs::CD_CreateNew, 359ae206db2SFanbo Meng llvm::sys::fs::OF_Text)) { 36073093599SArtem Dergachev // Existence of the file corresponds to the situation where a different 36173093599SArtem Dergachev // Clang instance has emitted a bug report with the same issue hash. 36273093599SArtem Dergachev // This is an entirely normal situation that does not deserve a warning, 36373093599SArtem Dergachev // as apart from hash collisions this can happen because the reports 36473093599SArtem Dergachev // are in fact similar enough to be considered duplicates of each other. 36573093599SArtem Dergachev if (EC != llvm::errc::file_exists) { 3669c4b2225SAlexander Belyaev llvm::errs() << "warning: could not create file in '" << Directory 3679c4b2225SAlexander Belyaev << "': " << EC.message() << '\n'; 3689c4b2225SAlexander Belyaev } 3699c4b2225SAlexander Belyaev return; 3709c4b2225SAlexander Belyaev } 3719c4b2225SAlexander Belyaev 3729c4b2225SAlexander Belyaev llvm::raw_fd_ostream os(FD, true); 3739c4b2225SAlexander Belyaev 3749c4b2225SAlexander Belyaev if (filesMade) 3759c4b2225SAlexander Belyaev filesMade->addDiagnostic(D, getName(), 3769c4b2225SAlexander Belyaev llvm::sys::path::filename(ResultPath)); 3779c4b2225SAlexander Belyaev 3789c4b2225SAlexander Belyaev // Emit the HTML to disk. 3799c4b2225SAlexander Belyaev os << report; 3809c4b2225SAlexander Belyaev } 3819c4b2225SAlexander Belyaev 3829c4b2225SAlexander Belyaev std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, 3839c4b2225SAlexander Belyaev const SourceManager& SMgr, const PathPieces& path, const char *declName) { 3849c4b2225SAlexander Belyaev // Rewrite source files as HTML for every new file the path crosses 3859c4b2225SAlexander Belyaev std::vector<FileID> FileIDs; 3869c4b2225SAlexander Belyaev for (auto I : path) { 3879c4b2225SAlexander Belyaev FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID(); 3889c4b2225SAlexander Belyaev if (llvm::is_contained(FileIDs, FID)) 3899c4b2225SAlexander Belyaev continue; 3909c4b2225SAlexander Belyaev 3919c4b2225SAlexander Belyaev FileIDs.push_back(FID); 3929c4b2225SAlexander Belyaev RewriteFile(R, path, FID); 3939c4b2225SAlexander Belyaev } 3949c4b2225SAlexander Belyaev 3959c4b2225SAlexander Belyaev if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) { 3969c4b2225SAlexander Belyaev // Prefix file names, anchor tags, and nav cursors to every file 3979c4b2225SAlexander Belyaev for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) { 3989c4b2225SAlexander Belyaev std::string s; 3999c4b2225SAlexander Belyaev llvm::raw_string_ostream os(s); 4009c4b2225SAlexander Belyaev 4019c4b2225SAlexander Belyaev if (I != FileIDs.begin()) 4029c4b2225SAlexander Belyaev os << "<hr class=divider>\n"; 4039c4b2225SAlexander Belyaev 4049c4b2225SAlexander Belyaev os << "<div id=File" << I->getHashValue() << ">\n"; 4059c4b2225SAlexander Belyaev 4069c4b2225SAlexander Belyaev // Left nav arrow 4079c4b2225SAlexander Belyaev if (I != FileIDs.begin()) 4089c4b2225SAlexander Belyaev os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue() 4099c4b2225SAlexander Belyaev << "\">←</a></div>"; 4109c4b2225SAlexander Belyaev 411523c4712SJan Svoboda os << "<h4 class=FileName>" << SMgr.getFileEntryRefForID(*I)->getName() 4129c4b2225SAlexander Belyaev << "</h4>\n"; 4139c4b2225SAlexander Belyaev 4149c4b2225SAlexander Belyaev // Right nav arrow 4159c4b2225SAlexander Belyaev if (I + 1 != E) 4169c4b2225SAlexander Belyaev os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue() 4179c4b2225SAlexander Belyaev << "\">→</a></div>"; 4189c4b2225SAlexander Belyaev 4199c4b2225SAlexander Belyaev os << "</div>\n"; 4209c4b2225SAlexander Belyaev 4219c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str()); 4229c4b2225SAlexander Belyaev } 4239c4b2225SAlexander Belyaev 4249c4b2225SAlexander Belyaev // Append files to the main report file in the order they appear in the path 425713ee230SKazu Hirata for (auto I : llvm::drop_begin(FileIDs)) { 4269c4b2225SAlexander Belyaev std::string s; 4279c4b2225SAlexander Belyaev llvm::raw_string_ostream os(s); 4289c4b2225SAlexander Belyaev 4299c4b2225SAlexander Belyaev const RewriteBuffer *Buf = R.getRewriteBufferFor(I); 4309c4b2225SAlexander Belyaev for (auto BI : *Buf) 4319c4b2225SAlexander Belyaev os << BI; 4329c4b2225SAlexander Belyaev 4339c4b2225SAlexander Belyaev R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str()); 4349c4b2225SAlexander Belyaev } 4359c4b2225SAlexander Belyaev } 4369c4b2225SAlexander Belyaev 4379c4b2225SAlexander Belyaev const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]); 4389c4b2225SAlexander Belyaev if (!Buf) 4399c4b2225SAlexander Belyaev return {}; 4409c4b2225SAlexander Belyaev 4419c4b2225SAlexander Belyaev // Add CSS, header, and footer. 4429c4b2225SAlexander Belyaev FileID FID = 4439c4b2225SAlexander Belyaev path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); 444523c4712SJan Svoboda OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(FID); 445523c4712SJan Svoboda FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName); 4469c4b2225SAlexander Belyaev 4479c4b2225SAlexander Belyaev std::string file; 4489c4b2225SAlexander Belyaev llvm::raw_string_ostream os(file); 4499c4b2225SAlexander Belyaev for (auto BI : *Buf) 4509c4b2225SAlexander Belyaev os << BI; 4519c4b2225SAlexander Belyaev 452715c72b4SLogan Smith return file; 4539c4b2225SAlexander Belyaev } 4549c4b2225SAlexander Belyaev 4559c4b2225SAlexander Belyaev void HTMLDiagnostics::dumpCoverageData( 4569c4b2225SAlexander Belyaev const PathDiagnostic &D, 4579c4b2225SAlexander Belyaev const PathPieces &path, 4589c4b2225SAlexander Belyaev llvm::raw_string_ostream &os) { 4599c4b2225SAlexander Belyaev 4609c4b2225SAlexander Belyaev const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines(); 4619c4b2225SAlexander Belyaev 4629c4b2225SAlexander Belyaev os << "var relevant_lines = {"; 4639c4b2225SAlexander Belyaev for (auto I = ExecutedLines.begin(), 4649c4b2225SAlexander Belyaev E = ExecutedLines.end(); I != E; ++I) { 4659c4b2225SAlexander Belyaev if (I != ExecutedLines.begin()) 4669c4b2225SAlexander Belyaev os << ", "; 4679c4b2225SAlexander Belyaev 4689c4b2225SAlexander Belyaev os << "\"" << I->first.getHashValue() << "\": {"; 4699c4b2225SAlexander Belyaev for (unsigned LineNo : I->second) { 4709c4b2225SAlexander Belyaev if (LineNo != *(I->second.begin())) 4719c4b2225SAlexander Belyaev os << ", "; 4729c4b2225SAlexander Belyaev 4739c4b2225SAlexander Belyaev os << "\"" << LineNo << "\": 1"; 4749c4b2225SAlexander Belyaev } 4759c4b2225SAlexander Belyaev os << "}"; 4769c4b2225SAlexander Belyaev } 4779c4b2225SAlexander Belyaev 4789c4b2225SAlexander Belyaev os << "};"; 4799c4b2225SAlexander Belyaev } 4809c4b2225SAlexander Belyaev 4819c4b2225SAlexander Belyaev std::string HTMLDiagnostics::showRelevantLinesJavascript( 4829c4b2225SAlexander Belyaev const PathDiagnostic &D, const PathPieces &path) { 4839c4b2225SAlexander Belyaev std::string s; 4849c4b2225SAlexander Belyaev llvm::raw_string_ostream os(s); 4859c4b2225SAlexander Belyaev os << "<script type='text/javascript'>\n"; 4869c4b2225SAlexander Belyaev dumpCoverageData(D, path, os); 4879c4b2225SAlexander Belyaev os << R"<<<( 4889c4b2225SAlexander Belyaev 4899c4b2225SAlexander Belyaev var filterCounterexample = function (hide) { 4909c4b2225SAlexander Belyaev var tables = document.getElementsByClassName("code"); 4919c4b2225SAlexander Belyaev for (var t=0; t<tables.length; t++) { 4929c4b2225SAlexander Belyaev var table = tables[t]; 4939c4b2225SAlexander Belyaev var file_id = table.getAttribute("data-fileid"); 4949c4b2225SAlexander Belyaev var lines_in_fid = relevant_lines[file_id]; 4959c4b2225SAlexander Belyaev if (!lines_in_fid) { 4969c4b2225SAlexander Belyaev lines_in_fid = {}; 4979c4b2225SAlexander Belyaev } 4989c4b2225SAlexander Belyaev var lines = table.getElementsByClassName("codeline"); 4999c4b2225SAlexander Belyaev for (var i=0; i<lines.length; i++) { 5009c4b2225SAlexander Belyaev var el = lines[i]; 5019c4b2225SAlexander Belyaev var lineNo = el.getAttribute("data-linenumber"); 5029c4b2225SAlexander Belyaev if (!lines_in_fid[lineNo]) { 5039c4b2225SAlexander Belyaev if (hide) { 5049c4b2225SAlexander Belyaev el.setAttribute("hidden", ""); 5059c4b2225SAlexander Belyaev } else { 5069c4b2225SAlexander Belyaev el.removeAttribute("hidden"); 5079c4b2225SAlexander Belyaev } 5089c4b2225SAlexander Belyaev } 5099c4b2225SAlexander Belyaev } 5109c4b2225SAlexander Belyaev } 5119c4b2225SAlexander Belyaev } 5129c4b2225SAlexander Belyaev 5139c4b2225SAlexander Belyaev window.addEventListener("keydown", function (event) { 5149c4b2225SAlexander Belyaev if (event.defaultPrevented) { 5159c4b2225SAlexander Belyaev return; 5169c4b2225SAlexander Belyaev } 51797bcafa2SValeriy Savchenko // SHIFT + S 51897bcafa2SValeriy Savchenko if (event.shiftKey && event.keyCode == 83) { 5199c4b2225SAlexander Belyaev var checked = document.getElementsByName("showCounterexample")[0].checked; 5209c4b2225SAlexander Belyaev filterCounterexample(!checked); 52197bcafa2SValeriy Savchenko document.getElementsByName("showCounterexample")[0].click(); 5229c4b2225SAlexander Belyaev } else { 5239c4b2225SAlexander Belyaev return; 5249c4b2225SAlexander Belyaev } 5259c4b2225SAlexander Belyaev event.preventDefault(); 5269c4b2225SAlexander Belyaev }, true); 5279c4b2225SAlexander Belyaev 5289c4b2225SAlexander Belyaev document.addEventListener("DOMContentLoaded", function() { 5299c4b2225SAlexander Belyaev document.querySelector('input[name="showCounterexample"]').onchange= 5309c4b2225SAlexander Belyaev function (event) { 5319c4b2225SAlexander Belyaev filterCounterexample(this.checked); 5329c4b2225SAlexander Belyaev }; 5339c4b2225SAlexander Belyaev }); 5349c4b2225SAlexander Belyaev </script> 5359c4b2225SAlexander Belyaev 5369c4b2225SAlexander Belyaev <form> 5379c4b2225SAlexander Belyaev <input type="checkbox" name="showCounterexample" id="showCounterexample" /> 5389c4b2225SAlexander Belyaev <label for="showCounterexample"> 5399c4b2225SAlexander Belyaev Show only relevant lines 5409c4b2225SAlexander Belyaev </label> 54197bcafa2SValeriy Savchenko <input type="checkbox" name="showArrows" 54297bcafa2SValeriy Savchenko id="showArrows" style="margin-left: 10px" /> 54397bcafa2SValeriy Savchenko <label for="showArrows"> 54497bcafa2SValeriy Savchenko Show control flow arrows 54597bcafa2SValeriy Savchenko </label> 5469c4b2225SAlexander Belyaev </form> 5479c4b2225SAlexander Belyaev )<<<"; 5489c4b2225SAlexander Belyaev 549715c72b4SLogan Smith return s; 5509c4b2225SAlexander Belyaev } 5519c4b2225SAlexander Belyaev 5529c4b2225SAlexander Belyaev void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic &D, Rewriter &R, 553523c4712SJan Svoboda const SourceManager &SMgr, 554523c4712SJan Svoboda const PathPieces &path, FileID FID, 555523c4712SJan Svoboda FileEntryRef Entry, const char *declName) { 5569c4b2225SAlexander Belyaev // This is a cludge; basically we want to append either the full 5579c4b2225SAlexander Belyaev // working directory if we have no directory information. This is 5589c4b2225SAlexander Belyaev // a work in progress. 5599c4b2225SAlexander Belyaev 5609c4b2225SAlexander Belyaev llvm::SmallString<0> DirName; 5619c4b2225SAlexander Belyaev 562523c4712SJan Svoboda if (llvm::sys::path::is_relative(Entry.getName())) { 5639c4b2225SAlexander Belyaev llvm::sys::fs::current_path(DirName); 5649c4b2225SAlexander Belyaev DirName += '/'; 5659c4b2225SAlexander Belyaev } 5669c4b2225SAlexander Belyaev 5679c4b2225SAlexander Belyaev int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); 5689c4b2225SAlexander Belyaev int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); 5699c4b2225SAlexander Belyaev 5709c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript()); 5719c4b2225SAlexander Belyaev 5729c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), 5739c4b2225SAlexander Belyaev generateKeyboardNavigationJavascript()); 5749c4b2225SAlexander Belyaev 57597bcafa2SValeriy Savchenko R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), 57697bcafa2SValeriy Savchenko generateArrowDrawingJavascript()); 57797bcafa2SValeriy Savchenko 5789c4b2225SAlexander Belyaev // Checkbox and javascript for filtering the output to the counterexample. 5799c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), 5809c4b2225SAlexander Belyaev showRelevantLinesJavascript(D, path)); 5819c4b2225SAlexander Belyaev 5829c4b2225SAlexander Belyaev // Add the name of the file as an <h1> tag. 5839c4b2225SAlexander Belyaev { 5849c4b2225SAlexander Belyaev std::string s; 5859c4b2225SAlexander Belyaev llvm::raw_string_ostream os(s); 5869c4b2225SAlexander Belyaev 5879c4b2225SAlexander Belyaev os << "<!-- REPORTHEADER -->\n" 5889c4b2225SAlexander Belyaev << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" 5899c4b2225SAlexander Belyaev "<tr><td class=\"rowname\">File:</td><td>" 5909c4b2225SAlexander Belyaev << html::EscapeText(DirName) 591523c4712SJan Svoboda << html::EscapeText(Entry.getName()) 5929c4b2225SAlexander Belyaev << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>" 5939c4b2225SAlexander Belyaev "<a href=\"#EndPath\">line " 5949c4b2225SAlexander Belyaev << LineNumber 5959c4b2225SAlexander Belyaev << ", column " 5969c4b2225SAlexander Belyaev << ColumnNumber 5979c4b2225SAlexander Belyaev << "</a><br />" 5989c4b2225SAlexander Belyaev << D.getVerboseDescription() << "</td></tr>\n"; 5999c4b2225SAlexander Belyaev 6009c4b2225SAlexander Belyaev // The navigation across the extra notes pieces. 6019c4b2225SAlexander Belyaev unsigned NumExtraPieces = 0; 6029c4b2225SAlexander Belyaev for (const auto &Piece : path) { 6039c4b2225SAlexander Belyaev if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) { 6049c4b2225SAlexander Belyaev int LineNumber = 6059c4b2225SAlexander Belyaev P->getLocation().asLocation().getExpansionLineNumber(); 6069c4b2225SAlexander Belyaev int ColumnNumber = 6079c4b2225SAlexander Belyaev P->getLocation().asLocation().getExpansionColumnNumber(); 608e73ae745SGuruprasad Hegde ++NumExtraPieces; 6099c4b2225SAlexander Belyaev os << "<tr><td class=\"rowname\">Note:</td><td>" 6109c4b2225SAlexander Belyaev << "<a href=\"#Note" << NumExtraPieces << "\">line " 6119c4b2225SAlexander Belyaev << LineNumber << ", column " << ColumnNumber << "</a><br />" 6129c4b2225SAlexander Belyaev << P->getString() << "</td></tr>"; 6139c4b2225SAlexander Belyaev } 6149c4b2225SAlexander Belyaev } 6159c4b2225SAlexander Belyaev 6169c4b2225SAlexander Belyaev // Output any other meta data. 6179c4b2225SAlexander Belyaev 6185c23e27bSBalazs Benics for (const std::string &Metadata : 6195c23e27bSBalazs Benics llvm::make_range(D.meta_begin(), D.meta_end())) { 6205c23e27bSBalazs Benics os << "<tr><td></td><td>" << html::EscapeText(Metadata) << "</td></tr>\n"; 6219c4b2225SAlexander Belyaev } 6229c4b2225SAlexander Belyaev 6239c4b2225SAlexander Belyaev os << R"<<<( 6249c4b2225SAlexander Belyaev </table> 6259c4b2225SAlexander Belyaev <!-- REPORTSUMMARYEXTRA --> 6269c4b2225SAlexander Belyaev <h3>Annotated Source Code</h3> 6279c4b2225SAlexander Belyaev <p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a> 6289c4b2225SAlexander Belyaev to see keyboard shortcuts</p> 6299c4b2225SAlexander Belyaev <input type="checkbox" class="spoilerhider" id="showinvocation" /> 6309c4b2225SAlexander Belyaev <label for="showinvocation" >Show analyzer invocation</label> 6319c4b2225SAlexander Belyaev <div class="spoiler">clang -cc1 )<<<"; 6329c4b2225SAlexander Belyaev os << html::EscapeText(DiagOpts.ToolInvocation); 6339c4b2225SAlexander Belyaev os << R"<<<( 6349c4b2225SAlexander Belyaev </div> 6359c4b2225SAlexander Belyaev <div id='tooltiphint' hidden="true"> 6369c4b2225SAlexander Belyaev <p>Keyboard shortcuts: </p> 6379c4b2225SAlexander Belyaev <ul> 6389c4b2225SAlexander Belyaev <li>Use 'j/k' keys for keyboard navigation</li> 6399c4b2225SAlexander Belyaev <li>Use 'Shift+S' to show/hide relevant lines</li> 6409c4b2225SAlexander Belyaev <li>Use '?' to toggle this window</li> 6419c4b2225SAlexander Belyaev </ul> 6429c4b2225SAlexander Belyaev <a href="#" onclick="toggleHelp(); return false;">Close</a> 6439c4b2225SAlexander Belyaev </div> 6449c4b2225SAlexander Belyaev )<<<"; 64597bcafa2SValeriy Savchenko 6469c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 6479c4b2225SAlexander Belyaev } 6489c4b2225SAlexander Belyaev 6499c4b2225SAlexander Belyaev // Embed meta-data tags. 6509c4b2225SAlexander Belyaev { 6519c4b2225SAlexander Belyaev std::string s; 6529c4b2225SAlexander Belyaev llvm::raw_string_ostream os(s); 6539c4b2225SAlexander Belyaev 6549c4b2225SAlexander Belyaev StringRef BugDesc = D.getVerboseDescription(); 6559c4b2225SAlexander Belyaev if (!BugDesc.empty()) 6569c4b2225SAlexander Belyaev os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; 6579c4b2225SAlexander Belyaev 6589c4b2225SAlexander Belyaev StringRef BugType = D.getBugType(); 6599c4b2225SAlexander Belyaev if (!BugType.empty()) 6609c4b2225SAlexander Belyaev os << "\n<!-- BUGTYPE " << BugType << " -->\n"; 6619c4b2225SAlexander Belyaev 6629c4b2225SAlexander Belyaev PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); 6639c4b2225SAlexander Belyaev FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() 6649c4b2225SAlexander Belyaev ? UPDLoc.asLocation() 6659c4b2225SAlexander Belyaev : D.getLocation().asLocation()), 6669c4b2225SAlexander Belyaev SMgr); 6679c4b2225SAlexander Belyaev 6689c4b2225SAlexander Belyaev StringRef BugCategory = D.getCategory(); 6699c4b2225SAlexander Belyaev if (!BugCategory.empty()) 6709c4b2225SAlexander Belyaev os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; 6719c4b2225SAlexander Belyaev 672523c4712SJan Svoboda os << "\n<!-- BUGFILE " << DirName << Entry.getName() << " -->\n"; 6739c4b2225SAlexander Belyaev 674523c4712SJan Svoboda os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry.getName()) << " -->\n"; 6759c4b2225SAlexander Belyaev 6769c4b2225SAlexander Belyaev os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; 6779c4b2225SAlexander Belyaev 67873093599SArtem Dergachev os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " << getIssueHash(D, PP) 6799c4b2225SAlexander Belyaev << " -->\n"; 6809c4b2225SAlexander Belyaev 6819c4b2225SAlexander Belyaev os << "\n<!-- BUGLINE " 6829c4b2225SAlexander Belyaev << LineNumber 6839c4b2225SAlexander Belyaev << " -->\n"; 6849c4b2225SAlexander Belyaev 6859c4b2225SAlexander Belyaev os << "\n<!-- BUGCOLUMN " 6869c4b2225SAlexander Belyaev << ColumnNumber 6879c4b2225SAlexander Belyaev << " -->\n"; 6889c4b2225SAlexander Belyaev 68997bcafa2SValeriy Savchenko os << "\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) << " -->\n"; 6909c4b2225SAlexander Belyaev 6919c4b2225SAlexander Belyaev // Mark the end of the tags. 6929c4b2225SAlexander Belyaev os << "\n<!-- BUGMETAEND -->\n"; 6939c4b2225SAlexander Belyaev 6949c4b2225SAlexander Belyaev // Insert the text. 6959c4b2225SAlexander Belyaev R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 6969c4b2225SAlexander Belyaev } 6979c4b2225SAlexander Belyaev 698523c4712SJan Svoboda html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry.getName()); 6999c4b2225SAlexander Belyaev } 7009c4b2225SAlexander Belyaev 7019c4b2225SAlexander Belyaev StringRef HTMLDiagnostics::showHelpJavascript() { 7029c4b2225SAlexander Belyaev return R"<<<( 7039c4b2225SAlexander Belyaev <script type='text/javascript'> 7049c4b2225SAlexander Belyaev 7059c4b2225SAlexander Belyaev var toggleHelp = function() { 7069c4b2225SAlexander Belyaev var hint = document.querySelector("#tooltiphint"); 7079c4b2225SAlexander Belyaev var attributeName = "hidden"; 7089c4b2225SAlexander Belyaev if (hint.hasAttribute(attributeName)) { 7099c4b2225SAlexander Belyaev hint.removeAttribute(attributeName); 7109c4b2225SAlexander Belyaev } else { 7119c4b2225SAlexander Belyaev hint.setAttribute("hidden", "true"); 7129c4b2225SAlexander Belyaev } 7139c4b2225SAlexander Belyaev }; 7149c4b2225SAlexander Belyaev window.addEventListener("keydown", function (event) { 7159c4b2225SAlexander Belyaev if (event.defaultPrevented) { 7169c4b2225SAlexander Belyaev return; 7179c4b2225SAlexander Belyaev } 7189c4b2225SAlexander Belyaev if (event.key == "?") { 7199c4b2225SAlexander Belyaev toggleHelp(); 7209c4b2225SAlexander Belyaev } else { 7219c4b2225SAlexander Belyaev return; 7229c4b2225SAlexander Belyaev } 7239c4b2225SAlexander Belyaev event.preventDefault(); 7249c4b2225SAlexander Belyaev }); 7259c4b2225SAlexander Belyaev </script> 7269c4b2225SAlexander Belyaev )<<<"; 7279c4b2225SAlexander Belyaev } 7289c4b2225SAlexander Belyaev 7299c4b2225SAlexander Belyaev static bool shouldDisplayPopUpRange(const SourceRange &Range) { 7309c4b2225SAlexander Belyaev return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID()); 7319c4b2225SAlexander Belyaev } 7329c4b2225SAlexander Belyaev 7339c4b2225SAlexander Belyaev static void 7349c4b2225SAlexander Belyaev HandlePopUpPieceStartTag(Rewriter &R, 7359c4b2225SAlexander Belyaev const std::vector<SourceRange> &PopUpRanges) { 7369c4b2225SAlexander Belyaev for (const auto &Range : PopUpRanges) { 7379c4b2225SAlexander Belyaev if (!shouldDisplayPopUpRange(Range)) 7389c4b2225SAlexander Belyaev continue; 7399c4b2225SAlexander Belyaev 7409c4b2225SAlexander Belyaev html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", 7419c4b2225SAlexander Belyaev "<table class='variable_popup'><tbody>", 7429c4b2225SAlexander Belyaev /*IsTokenRange=*/true); 7439c4b2225SAlexander Belyaev } 7449c4b2225SAlexander Belyaev } 7459c4b2225SAlexander Belyaev 7469c4b2225SAlexander Belyaev static void HandlePopUpPieceEndTag(Rewriter &R, 7479c4b2225SAlexander Belyaev const PathDiagnosticPopUpPiece &Piece, 7489c4b2225SAlexander Belyaev std::vector<SourceRange> &PopUpRanges, 7499c4b2225SAlexander Belyaev unsigned int LastReportedPieceIndex, 7509c4b2225SAlexander Belyaev unsigned int PopUpPieceIndex) { 7519c4b2225SAlexander Belyaev SmallString<256> Buf; 7529c4b2225SAlexander Belyaev llvm::raw_svector_ostream Out(Buf); 7539c4b2225SAlexander Belyaev 7549c4b2225SAlexander Belyaev SourceRange Range(Piece.getLocation().asRange()); 7559c4b2225SAlexander Belyaev if (!shouldDisplayPopUpRange(Range)) 7569c4b2225SAlexander Belyaev return; 7579c4b2225SAlexander Belyaev 7589c4b2225SAlexander Belyaev // Write out the path indices with a right arrow and the message as a row. 7599c4b2225SAlexander Belyaev Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>" 7609c4b2225SAlexander Belyaev << LastReportedPieceIndex; 7619c4b2225SAlexander Belyaev 7629c4b2225SAlexander Belyaev // Also annotate the state transition with extra indices. 7639c4b2225SAlexander Belyaev Out << '.' << PopUpPieceIndex; 7649c4b2225SAlexander Belyaev 7659c4b2225SAlexander Belyaev Out << "</div></td><td>" << Piece.getString() << "</td></tr>"; 7669c4b2225SAlexander Belyaev 7679c4b2225SAlexander Belyaev // If no report made at this range mark the variable and add the end tags. 768e567f37dSKazu Hirata if (!llvm::is_contained(PopUpRanges, Range)) { 7699c4b2225SAlexander Belyaev // Store that we create a report at this range. 7709c4b2225SAlexander Belyaev PopUpRanges.push_back(Range); 7719c4b2225SAlexander Belyaev 7729c4b2225SAlexander Belyaev Out << "</tbody></table></span>"; 7739c4b2225SAlexander Belyaev html::HighlightRange(R, Range.getBegin(), Range.getEnd(), 7749c4b2225SAlexander Belyaev "<span class='variable'>", Buf.c_str(), 7759c4b2225SAlexander Belyaev /*IsTokenRange=*/true); 7769c4b2225SAlexander Belyaev } else { 7779c4b2225SAlexander Belyaev // Otherwise inject just the new row at the end of the range. 7789c4b2225SAlexander Belyaev html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(), 7799c4b2225SAlexander Belyaev /*IsTokenRange=*/true); 7809c4b2225SAlexander Belyaev } 7819c4b2225SAlexander Belyaev } 7829c4b2225SAlexander Belyaev 78397bcafa2SValeriy Savchenko void HTMLDiagnostics::RewriteFile(Rewriter &R, const PathPieces &path, 78497bcafa2SValeriy Savchenko FileID FID) { 78597bcafa2SValeriy Savchenko 7869c4b2225SAlexander Belyaev // Process the path. 7879c4b2225SAlexander Belyaev // Maintain the counts of extra note pieces separately. 78897bcafa2SValeriy Savchenko unsigned TotalPieces = getPathSizeWithoutArrows(path); 78997bcafa2SValeriy Savchenko unsigned TotalNotePieces = 79097bcafa2SValeriy Savchenko llvm::count_if(path, [](const PathDiagnosticPieceRef &p) { 7919c4b2225SAlexander Belyaev return isa<PathDiagnosticNotePiece>(*p); 7929c4b2225SAlexander Belyaev }); 79397bcafa2SValeriy Savchenko unsigned PopUpPieceCount = 79497bcafa2SValeriy Savchenko llvm::count_if(path, [](const PathDiagnosticPieceRef &p) { 7959c4b2225SAlexander Belyaev return isa<PathDiagnosticPopUpPiece>(*p); 7969c4b2225SAlexander Belyaev }); 7979c4b2225SAlexander Belyaev 7989c4b2225SAlexander Belyaev unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount; 7999c4b2225SAlexander Belyaev unsigned NumRegularPieces = TotalRegularPieces; 8009c4b2225SAlexander Belyaev unsigned NumNotePieces = TotalNotePieces; 80197bcafa2SValeriy Savchenko unsigned NumberOfArrows = 0; 8029c4b2225SAlexander Belyaev // Stores the count of the regular piece indices. 8039c4b2225SAlexander Belyaev std::map<int, int> IndexMap; 8049e02f587SValeriy Savchenko ArrowMap ArrowIndices(TotalRegularPieces + 1); 8059c4b2225SAlexander Belyaev 8069c4b2225SAlexander Belyaev // Stores the different ranges where we have reported something. 8079c4b2225SAlexander Belyaev std::vector<SourceRange> PopUpRanges; 80874115602SKazu Hirata for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) { 80974115602SKazu Hirata const auto &Piece = *I.get(); 8109c4b2225SAlexander Belyaev 8119c4b2225SAlexander Belyaev if (isa<PathDiagnosticPopUpPiece>(Piece)) { 8129c4b2225SAlexander Belyaev ++IndexMap[NumRegularPieces]; 8139c4b2225SAlexander Belyaev } else if (isa<PathDiagnosticNotePiece>(Piece)) { 8149c4b2225SAlexander Belyaev // This adds diagnostic bubbles, but not navigation. 8159c4b2225SAlexander Belyaev // Navigation through note pieces would be added later, 8169c4b2225SAlexander Belyaev // as a separate pass through the piece list. 8179c4b2225SAlexander Belyaev HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces); 8189c4b2225SAlexander Belyaev --NumNotePieces; 81997bcafa2SValeriy Savchenko 82097bcafa2SValeriy Savchenko } else if (isArrowPiece(Piece)) { 82197bcafa2SValeriy Savchenko NumberOfArrows = ProcessControlFlowPiece( 82297bcafa2SValeriy Savchenko R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows); 8239e02f587SValeriy Savchenko ArrowIndices[NumRegularPieces] = NumberOfArrows; 82497bcafa2SValeriy Savchenko 8259c4b2225SAlexander Belyaev } else { 8269c4b2225SAlexander Belyaev HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces, 8279c4b2225SAlexander Belyaev TotalRegularPieces); 8289c4b2225SAlexander Belyaev --NumRegularPieces; 8299e02f587SValeriy Savchenko ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1]; 8309c4b2225SAlexander Belyaev } 8319c4b2225SAlexander Belyaev } 8329e02f587SValeriy Savchenko ArrowIndices[0] = NumberOfArrows; 8339e02f587SValeriy Savchenko 8349e02f587SValeriy Savchenko // At this point ArrowIndices represent the following data structure: 8359e02f587SValeriy Savchenko // [a_0, a_1, ..., a_N] 8369e02f587SValeriy Savchenko // where N is the number of events in the path. 8379e02f587SValeriy Savchenko // 8389e02f587SValeriy Savchenko // Then for every event with index i \in [0, N - 1], we can say that 8399e02f587SValeriy Savchenko // arrows with indices \in [a_(i+1), a_i) correspond to that event. 8409e02f587SValeriy Savchenko // We can say that because arrows with these indices appeared in the 8419e02f587SValeriy Savchenko // path in between the i-th and the (i+1)-th events. 8429e02f587SValeriy Savchenko assert(ArrowIndices.back() == 0 && 8439e02f587SValeriy Savchenko "No arrows should be after the last event"); 8449e02f587SValeriy Savchenko // This assertion also guarantees that all indices in are <= NumberOfArrows. 8459e02f587SValeriy Savchenko assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) && 8469e02f587SValeriy Savchenko "Incorrect arrow indices map"); 8479c4b2225SAlexander Belyaev 8489c4b2225SAlexander Belyaev // Secondary indexing if we are having multiple pop-ups between two notes. 8499c4b2225SAlexander Belyaev // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...) 8509c4b2225SAlexander Belyaev NumRegularPieces = TotalRegularPieces; 85174115602SKazu Hirata for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) { 85274115602SKazu Hirata const auto &Piece = *I.get(); 8539c4b2225SAlexander Belyaev 8549c4b2225SAlexander Belyaev if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) { 8559c4b2225SAlexander Belyaev int PopUpPieceIndex = IndexMap[NumRegularPieces]; 8569c4b2225SAlexander Belyaev 8579c4b2225SAlexander Belyaev // Pop-up pieces needs the index of the last reported piece and its count 8589c4b2225SAlexander Belyaev // how many times we report to handle multiple reports on the same range. 8599c4b2225SAlexander Belyaev // This marks the variable, adds the </table> end tag and the message 8609c4b2225SAlexander Belyaev // (list element) as a row. The <table> start tag will be added after the 8619c4b2225SAlexander Belyaev // rows has been written out. Note: It stores every different range. 8629c4b2225SAlexander Belyaev HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces, 8639c4b2225SAlexander Belyaev PopUpPieceIndex); 8649c4b2225SAlexander Belyaev 8659c4b2225SAlexander Belyaev if (PopUpPieceIndex > 0) 8669c4b2225SAlexander Belyaev --IndexMap[NumRegularPieces]; 8679c4b2225SAlexander Belyaev 86897bcafa2SValeriy Savchenko } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) { 8699c4b2225SAlexander Belyaev --NumRegularPieces; 8709c4b2225SAlexander Belyaev } 8719c4b2225SAlexander Belyaev } 8729c4b2225SAlexander Belyaev 8739c4b2225SAlexander Belyaev // Add the <table> start tag of pop-up pieces based on the stored ranges. 8749c4b2225SAlexander Belyaev HandlePopUpPieceStartTag(R, PopUpRanges); 8759c4b2225SAlexander Belyaev 8769c4b2225SAlexander Belyaev // Add line numbers, header, footer, etc. 8779c4b2225SAlexander Belyaev html::EscapeText(R, FID); 8789c4b2225SAlexander Belyaev html::AddLineNumbers(R, FID); 8799c4b2225SAlexander Belyaev 8809e02f587SValeriy Savchenko addArrowSVGs(R, FID, ArrowIndices); 88197bcafa2SValeriy Savchenko 8829c4b2225SAlexander Belyaev // If we have a preprocessor, relex the file and syntax highlight. 8839c4b2225SAlexander Belyaev // We might not have a preprocessor if we come from a deserialized AST file, 8849c4b2225SAlexander Belyaev // for example. 885243bfed6SArtem Dergachev html::SyntaxHighlight(R, FID, PP, RewriterCache); 886243bfed6SArtem Dergachev html::HighlightMacros(R, FID, PP, RewriterCache); 8879c4b2225SAlexander Belyaev } 8889c4b2225SAlexander Belyaev 8899c4b2225SAlexander Belyaev void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, 8909c4b2225SAlexander Belyaev const PathDiagnosticPiece &P, 8919c4b2225SAlexander Belyaev const std::vector<SourceRange> &PopUpRanges, 8929c4b2225SAlexander Belyaev unsigned num, unsigned max) { 8939c4b2225SAlexander Belyaev // For now, just draw a box above the line in question, and emit the 8949c4b2225SAlexander Belyaev // warning. 8959c4b2225SAlexander Belyaev FullSourceLoc Pos = P.getLocation().asLocation(); 8969c4b2225SAlexander Belyaev 8979c4b2225SAlexander Belyaev if (!Pos.isValid()) 8989c4b2225SAlexander Belyaev return; 8999c4b2225SAlexander Belyaev 9009c4b2225SAlexander Belyaev SourceManager &SM = R.getSourceMgr(); 9019c4b2225SAlexander Belyaev assert(&Pos.getManager() == &SM && "SourceManagers are different!"); 9029c4b2225SAlexander Belyaev std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); 9039c4b2225SAlexander Belyaev 9049c4b2225SAlexander Belyaev if (LPosInfo.first != BugFileID) 9059c4b2225SAlexander Belyaev return; 9069c4b2225SAlexander Belyaev 9079c4b2225SAlexander Belyaev llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first); 9089c4b2225SAlexander Belyaev const char *FileStart = Buf.getBufferStart(); 9099c4b2225SAlexander Belyaev 9109c4b2225SAlexander Belyaev // Compute the column number. Rewind from the current position to the start 9119c4b2225SAlexander Belyaev // of the line. 9129c4b2225SAlexander Belyaev unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); 9139c4b2225SAlexander Belyaev const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); 9149c4b2225SAlexander Belyaev const char *LineStart = TokInstantiationPtr-ColNo; 9159c4b2225SAlexander Belyaev 9169c4b2225SAlexander Belyaev // Compute LineEnd. 9179c4b2225SAlexander Belyaev const char *LineEnd = TokInstantiationPtr; 9189c4b2225SAlexander Belyaev const char *FileEnd = Buf.getBufferEnd(); 9199c4b2225SAlexander Belyaev while (*LineEnd != '\n' && LineEnd != FileEnd) 9209c4b2225SAlexander Belyaev ++LineEnd; 9219c4b2225SAlexander Belyaev 9229c4b2225SAlexander Belyaev // Compute the margin offset by counting tabs and non-tabs. 9239c4b2225SAlexander Belyaev unsigned PosNo = 0; 9249c4b2225SAlexander Belyaev for (const char* c = LineStart; c != TokInstantiationPtr; ++c) 9259c4b2225SAlexander Belyaev PosNo += *c == '\t' ? 8 : 1; 9269c4b2225SAlexander Belyaev 9279c4b2225SAlexander Belyaev // Create the html for the message. 9289c4b2225SAlexander Belyaev 9299c4b2225SAlexander Belyaev const char *Kind = nullptr; 9309c4b2225SAlexander Belyaev bool IsNote = false; 9319c4b2225SAlexander Belyaev bool SuppressIndex = (max == 1); 9329c4b2225SAlexander Belyaev switch (P.getKind()) { 9339c4b2225SAlexander Belyaev case PathDiagnosticPiece::Event: Kind = "Event"; break; 9349c4b2225SAlexander Belyaev case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break; 9359c4b2225SAlexander Belyaev // Setting Kind to "Control" is intentional. 9369c4b2225SAlexander Belyaev case PathDiagnosticPiece::Macro: Kind = "Control"; break; 9379c4b2225SAlexander Belyaev case PathDiagnosticPiece::Note: 9389c4b2225SAlexander Belyaev Kind = "Note"; 9399c4b2225SAlexander Belyaev IsNote = true; 9409c4b2225SAlexander Belyaev SuppressIndex = true; 9419c4b2225SAlexander Belyaev break; 9429c4b2225SAlexander Belyaev case PathDiagnosticPiece::Call: 9439c4b2225SAlexander Belyaev case PathDiagnosticPiece::PopUp: 9449c4b2225SAlexander Belyaev llvm_unreachable("Calls and extra notes should already be handled"); 9459c4b2225SAlexander Belyaev } 9469c4b2225SAlexander Belyaev 9479c4b2225SAlexander Belyaev std::string sbuf; 9489c4b2225SAlexander Belyaev llvm::raw_string_ostream os(sbuf); 9499c4b2225SAlexander Belyaev 9509c4b2225SAlexander Belyaev os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\""; 9519c4b2225SAlexander Belyaev 9529c4b2225SAlexander Belyaev if (IsNote) 9539c4b2225SAlexander Belyaev os << "Note" << num; 9549c4b2225SAlexander Belyaev else if (num == max) 9559c4b2225SAlexander Belyaev os << "EndPath"; 9569c4b2225SAlexander Belyaev else 9579c4b2225SAlexander Belyaev os << "Path" << num; 9589c4b2225SAlexander Belyaev 9599c4b2225SAlexander Belyaev os << "\" class=\"msg"; 9609c4b2225SAlexander Belyaev if (Kind) 9619c4b2225SAlexander Belyaev os << " msg" << Kind; 9629c4b2225SAlexander Belyaev os << "\" style=\"margin-left:" << PosNo << "ex"; 9639c4b2225SAlexander Belyaev 9649c4b2225SAlexander Belyaev // Output a maximum size. 9659c4b2225SAlexander Belyaev if (!isa<PathDiagnosticMacroPiece>(P)) { 9669c4b2225SAlexander Belyaev // Get the string and determining its maximum substring. 9679c4b2225SAlexander Belyaev const auto &Msg = P.getString(); 9689c4b2225SAlexander Belyaev unsigned max_token = 0; 9699c4b2225SAlexander Belyaev unsigned cnt = 0; 9709c4b2225SAlexander Belyaev unsigned len = Msg.size(); 9719c4b2225SAlexander Belyaev 9729c4b2225SAlexander Belyaev for (char C : Msg) 9739c4b2225SAlexander Belyaev switch (C) { 9749c4b2225SAlexander Belyaev default: 9759c4b2225SAlexander Belyaev ++cnt; 9769c4b2225SAlexander Belyaev continue; 9779c4b2225SAlexander Belyaev case ' ': 9789c4b2225SAlexander Belyaev case '\t': 9799c4b2225SAlexander Belyaev case '\n': 9809c4b2225SAlexander Belyaev if (cnt > max_token) max_token = cnt; 9819c4b2225SAlexander Belyaev cnt = 0; 9829c4b2225SAlexander Belyaev } 9839c4b2225SAlexander Belyaev 9849c4b2225SAlexander Belyaev if (cnt > max_token) 9859c4b2225SAlexander Belyaev max_token = cnt; 9869c4b2225SAlexander Belyaev 9879c4b2225SAlexander Belyaev // Determine the approximate size of the message bubble in em. 9889c4b2225SAlexander Belyaev unsigned em; 9899c4b2225SAlexander Belyaev const unsigned max_line = 120; 9909c4b2225SAlexander Belyaev 9919c4b2225SAlexander Belyaev if (max_token >= max_line) 9929c4b2225SAlexander Belyaev em = max_token / 2; 9939c4b2225SAlexander Belyaev else { 9949c4b2225SAlexander Belyaev unsigned characters = max_line; 9959c4b2225SAlexander Belyaev unsigned lines = len / max_line; 9969c4b2225SAlexander Belyaev 9979c4b2225SAlexander Belyaev if (lines > 0) { 9989c4b2225SAlexander Belyaev for (; characters > max_token; --characters) 9999c4b2225SAlexander Belyaev if (len / characters > lines) { 10009c4b2225SAlexander Belyaev ++characters; 10019c4b2225SAlexander Belyaev break; 10029c4b2225SAlexander Belyaev } 10039c4b2225SAlexander Belyaev } 10049c4b2225SAlexander Belyaev 10059c4b2225SAlexander Belyaev em = characters / 2; 10069c4b2225SAlexander Belyaev } 10079c4b2225SAlexander Belyaev 10089c4b2225SAlexander Belyaev if (em < max_line/2) 10099c4b2225SAlexander Belyaev os << "; max-width:" << em << "em"; 10109c4b2225SAlexander Belyaev } 10119c4b2225SAlexander Belyaev else 10129c4b2225SAlexander Belyaev os << "; max-width:100em"; 10139c4b2225SAlexander Belyaev 10149c4b2225SAlexander Belyaev os << "\">"; 10159c4b2225SAlexander Belyaev 10169c4b2225SAlexander Belyaev if (!SuppressIndex) { 10179c4b2225SAlexander Belyaev os << "<table class=\"msgT\"><tr><td valign=\"top\">"; 10189c4b2225SAlexander Belyaev os << "<div class=\"PathIndex"; 10199c4b2225SAlexander Belyaev if (Kind) os << " PathIndex" << Kind; 10209c4b2225SAlexander Belyaev os << "\">" << num << "</div>"; 10219c4b2225SAlexander Belyaev 10229c4b2225SAlexander Belyaev if (num > 1) { 10239c4b2225SAlexander Belyaev os << "</td><td><div class=\"PathNav\"><a href=\"#Path" 10249c4b2225SAlexander Belyaev << (num - 1) 10259c4b2225SAlexander Belyaev << "\" title=\"Previous event (" 10269c4b2225SAlexander Belyaev << (num - 1) 10279c4b2225SAlexander Belyaev << ")\">←</a></div>"; 10289c4b2225SAlexander Belyaev } 10299c4b2225SAlexander Belyaev 10309c4b2225SAlexander Belyaev os << "</td><td>"; 10319c4b2225SAlexander Belyaev } 10329c4b2225SAlexander Belyaev 10339c4b2225SAlexander Belyaev if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) { 10349c4b2225SAlexander Belyaev os << "Within the expansion of the macro '"; 10359c4b2225SAlexander Belyaev 10369c4b2225SAlexander Belyaev // Get the name of the macro by relexing it. 10379c4b2225SAlexander Belyaev { 10389c4b2225SAlexander Belyaev FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); 10399c4b2225SAlexander Belyaev assert(L.isFileID()); 10409c4b2225SAlexander Belyaev StringRef BufferInfo = L.getBufferData(); 10419c4b2225SAlexander Belyaev std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); 10429c4b2225SAlexander Belyaev const char* MacroName = LocInfo.second + BufferInfo.data(); 10439c4b2225SAlexander Belyaev Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(), 10449c4b2225SAlexander Belyaev BufferInfo.begin(), MacroName, BufferInfo.end()); 10459c4b2225SAlexander Belyaev 10469c4b2225SAlexander Belyaev Token TheTok; 10479c4b2225SAlexander Belyaev rawLexer.LexFromRawLexer(TheTok); 10489c4b2225SAlexander Belyaev for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i) 10499c4b2225SAlexander Belyaev os << MacroName[i]; 10509c4b2225SAlexander Belyaev } 10519c4b2225SAlexander Belyaev 10529c4b2225SAlexander Belyaev os << "':\n"; 10539c4b2225SAlexander Belyaev 10549c4b2225SAlexander Belyaev if (!SuppressIndex) { 10559c4b2225SAlexander Belyaev os << "</td>"; 10569c4b2225SAlexander Belyaev if (num < max) { 10579c4b2225SAlexander Belyaev os << "<td><div class=\"PathNav\"><a href=\"#"; 10589c4b2225SAlexander Belyaev if (num == max - 1) 10599c4b2225SAlexander Belyaev os << "EndPath"; 10609c4b2225SAlexander Belyaev else 10619c4b2225SAlexander Belyaev os << "Path" << (num + 1); 10629c4b2225SAlexander Belyaev os << "\" title=\"Next event (" 10639c4b2225SAlexander Belyaev << (num + 1) 10649c4b2225SAlexander Belyaev << ")\">→</a></div></td>"; 10659c4b2225SAlexander Belyaev } 10669c4b2225SAlexander Belyaev 10679c4b2225SAlexander Belyaev os << "</tr></table>"; 10689c4b2225SAlexander Belyaev } 10699c4b2225SAlexander Belyaev 10709c4b2225SAlexander Belyaev // Within a macro piece. Write out each event. 10719c4b2225SAlexander Belyaev ProcessMacroPiece(os, *MP, 0); 10729c4b2225SAlexander Belyaev } 10739c4b2225SAlexander Belyaev else { 10749c4b2225SAlexander Belyaev os << html::EscapeText(P.getString()); 10759c4b2225SAlexander Belyaev 10769c4b2225SAlexander Belyaev if (!SuppressIndex) { 10779c4b2225SAlexander Belyaev os << "</td>"; 10789c4b2225SAlexander Belyaev if (num < max) { 10799c4b2225SAlexander Belyaev os << "<td><div class=\"PathNav\"><a href=\"#"; 10809c4b2225SAlexander Belyaev if (num == max - 1) 10819c4b2225SAlexander Belyaev os << "EndPath"; 10829c4b2225SAlexander Belyaev else 10839c4b2225SAlexander Belyaev os << "Path" << (num + 1); 10849c4b2225SAlexander Belyaev os << "\" title=\"Next event (" 10859c4b2225SAlexander Belyaev << (num + 1) 10869c4b2225SAlexander Belyaev << ")\">→</a></div></td>"; 10879c4b2225SAlexander Belyaev } 10889c4b2225SAlexander Belyaev 10899c4b2225SAlexander Belyaev os << "</tr></table>"; 10909c4b2225SAlexander Belyaev } 10919c4b2225SAlexander Belyaev } 10929c4b2225SAlexander Belyaev 10939c4b2225SAlexander Belyaev os << "</div></td></tr>"; 10949c4b2225SAlexander Belyaev 10959c4b2225SAlexander Belyaev // Insert the new html. 10969c4b2225SAlexander Belyaev unsigned DisplayPos = LineEnd - FileStart; 10979c4b2225SAlexander Belyaev SourceLocation Loc = 10989c4b2225SAlexander Belyaev SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); 10999c4b2225SAlexander Belyaev 11009c4b2225SAlexander Belyaev R.InsertTextBefore(Loc, os.str()); 11019c4b2225SAlexander Belyaev 11029c4b2225SAlexander Belyaev // Now highlight the ranges. 11039c4b2225SAlexander Belyaev ArrayRef<SourceRange> Ranges = P.getRanges(); 11049c4b2225SAlexander Belyaev for (const auto &Range : Ranges) { 11059c4b2225SAlexander Belyaev // If we have already highlighted the range as a pop-up there is no work. 1106e567f37dSKazu Hirata if (llvm::is_contained(PopUpRanges, Range)) 11079c4b2225SAlexander Belyaev continue; 11089c4b2225SAlexander Belyaev 11099c4b2225SAlexander Belyaev HighlightRange(R, LPosInfo.first, Range); 11109c4b2225SAlexander Belyaev } 11119c4b2225SAlexander Belyaev } 11129c4b2225SAlexander Belyaev 11139c4b2225SAlexander Belyaev static void EmitAlphaCounter(raw_ostream &os, unsigned n) { 11149c4b2225SAlexander Belyaev unsigned x = n % ('z' - 'a'); 11159c4b2225SAlexander Belyaev n /= 'z' - 'a'; 11169c4b2225SAlexander Belyaev 11179c4b2225SAlexander Belyaev if (n > 0) 11189c4b2225SAlexander Belyaev EmitAlphaCounter(os, n); 11199c4b2225SAlexander Belyaev 11209c4b2225SAlexander Belyaev os << char('a' + x); 11219c4b2225SAlexander Belyaev } 11229c4b2225SAlexander Belyaev 11239c4b2225SAlexander Belyaev unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, 11249c4b2225SAlexander Belyaev const PathDiagnosticMacroPiece& P, 11259c4b2225SAlexander Belyaev unsigned num) { 11269c4b2225SAlexander Belyaev for (const auto &subPiece : P.subPieces) { 11279c4b2225SAlexander Belyaev if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) { 11289c4b2225SAlexander Belyaev num = ProcessMacroPiece(os, *MP, num); 11299c4b2225SAlexander Belyaev continue; 11309c4b2225SAlexander Belyaev } 11319c4b2225SAlexander Belyaev 11329c4b2225SAlexander Belyaev if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) { 11339c4b2225SAlexander Belyaev os << "<div class=\"msg msgEvent\" style=\"width:94%; " 11349c4b2225SAlexander Belyaev "margin-left:5px\">" 11359c4b2225SAlexander Belyaev "<table class=\"msgT\"><tr>" 11369c4b2225SAlexander Belyaev "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">"; 11379c4b2225SAlexander Belyaev EmitAlphaCounter(os, num++); 11389c4b2225SAlexander Belyaev os << "</div></td><td valign=\"top\">" 11399c4b2225SAlexander Belyaev << html::EscapeText(EP->getString()) 11409c4b2225SAlexander Belyaev << "</td></tr></table></div>\n"; 11419c4b2225SAlexander Belyaev } 11429c4b2225SAlexander Belyaev } 11439c4b2225SAlexander Belyaev 11449c4b2225SAlexander Belyaev return num; 11459c4b2225SAlexander Belyaev } 11469c4b2225SAlexander Belyaev 114797bcafa2SValeriy Savchenko void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID, 11489e02f587SValeriy Savchenko const ArrowMap &ArrowIndices) { 114997bcafa2SValeriy Savchenko std::string S; 115097bcafa2SValeriy Savchenko llvm::raw_string_ostream OS(S); 115197bcafa2SValeriy Savchenko 115297bcafa2SValeriy Savchenko OS << R"<<<( 115397bcafa2SValeriy Savchenko <style type="text/css"> 115497bcafa2SValeriy Savchenko svg { 115597bcafa2SValeriy Savchenko position:absolute; 115697bcafa2SValeriy Savchenko top:0; 115797bcafa2SValeriy Savchenko left:0; 115897bcafa2SValeriy Savchenko height:100%; 115997bcafa2SValeriy Savchenko width:100%; 116097bcafa2SValeriy Savchenko pointer-events: none; 116197bcafa2SValeriy Savchenko overflow: visible 116297bcafa2SValeriy Savchenko } 11639e02f587SValeriy Savchenko .arrow { 11649e02f587SValeriy Savchenko stroke-opacity: 0.2; 11659e02f587SValeriy Savchenko stroke-width: 1; 11669e02f587SValeriy Savchenko marker-end: url(#arrowhead); 11679e02f587SValeriy Savchenko } 11689e02f587SValeriy Savchenko 11699e02f587SValeriy Savchenko .arrow.selected { 11709e02f587SValeriy Savchenko stroke-opacity: 0.6; 11719e02f587SValeriy Savchenko stroke-width: 2; 11729e02f587SValeriy Savchenko marker-end: url(#arrowheadSelected); 11739e02f587SValeriy Savchenko } 11749e02f587SValeriy Savchenko 11759e02f587SValeriy Savchenko .arrowhead { 11769e02f587SValeriy Savchenko orient: auto; 11779e02f587SValeriy Savchenko stroke: none; 11789e02f587SValeriy Savchenko opacity: 0.6; 11799e02f587SValeriy Savchenko fill: blue; 11809e02f587SValeriy Savchenko } 118197bcafa2SValeriy Savchenko </style> 118297bcafa2SValeriy Savchenko <svg xmlns="http://www.w3.org/2000/svg"> 118397bcafa2SValeriy Savchenko <defs> 11849e02f587SValeriy Savchenko <marker id="arrowheadSelected" class="arrowhead" opacity="0.6" 11859e02f587SValeriy Savchenko viewBox="0 0 10 10" refX="3" refY="5" 11869e02f587SValeriy Savchenko markerWidth="4" markerHeight="4"> 11879e02f587SValeriy Savchenko <path d="M 0 0 L 10 5 L 0 10 z" /> 11889e02f587SValeriy Savchenko </marker> 11899e02f587SValeriy Savchenko <marker id="arrowhead" class="arrowhead" opacity="0.2" 11909e02f587SValeriy Savchenko viewBox="0 0 10 10" refX="3" refY="5" 11919e02f587SValeriy Savchenko markerWidth="4" markerHeight="4"> 119297bcafa2SValeriy Savchenko <path d="M 0 0 L 10 5 L 0 10 z" /> 119397bcafa2SValeriy Savchenko </marker> 119497bcafa2SValeriy Savchenko </defs> 11959e02f587SValeriy Savchenko <g id="arrows" fill="none" stroke="blue" visibility="hidden"> 119697bcafa2SValeriy Savchenko )<<<"; 119797bcafa2SValeriy Savchenko 11989e02f587SValeriy Savchenko for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) { 11999e02f587SValeriy Savchenko OS << " <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n"; 120097bcafa2SValeriy Savchenko } 120197bcafa2SValeriy Savchenko 120297bcafa2SValeriy Savchenko OS << R"<<<( 120397bcafa2SValeriy Savchenko </g> 120497bcafa2SValeriy Savchenko </svg> 12059e02f587SValeriy Savchenko <script type='text/javascript'> 12069e02f587SValeriy Savchenko const arrowIndices = )<<<"; 12079e02f587SValeriy Savchenko 12089e02f587SValeriy Savchenko OS << ArrowIndices << "\n</script>\n"; 120997bcafa2SValeriy Savchenko 121097bcafa2SValeriy Savchenko R.InsertTextBefore(R.getSourceMgr().getLocForStartOfFile(BugFileID), 121197bcafa2SValeriy Savchenko OS.str()); 121297bcafa2SValeriy Savchenko } 121397bcafa2SValeriy Savchenko 1214*b3470c3dSCongcong Cai static std::string getSpanBeginForControl(const char *ClassName, 1215*b3470c3dSCongcong Cai unsigned Index) { 121697bcafa2SValeriy Savchenko std::string Result; 121797bcafa2SValeriy Savchenko llvm::raw_string_ostream OS(Result); 121897bcafa2SValeriy Savchenko OS << "<span id=\"" << ClassName << Index << "\">"; 1219715c72b4SLogan Smith return Result; 122097bcafa2SValeriy Savchenko } 122197bcafa2SValeriy Savchenko 1222*b3470c3dSCongcong Cai static std::string getSpanBeginForControlStart(unsigned Index) { 122397bcafa2SValeriy Savchenko return getSpanBeginForControl("start", Index); 122497bcafa2SValeriy Savchenko } 122597bcafa2SValeriy Savchenko 1226*b3470c3dSCongcong Cai static std::string getSpanBeginForControlEnd(unsigned Index) { 122797bcafa2SValeriy Savchenko return getSpanBeginForControl("end", Index); 122897bcafa2SValeriy Savchenko } 122997bcafa2SValeriy Savchenko 123097bcafa2SValeriy Savchenko unsigned HTMLDiagnostics::ProcessControlFlowPiece( 123197bcafa2SValeriy Savchenko Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P, 123297bcafa2SValeriy Savchenko unsigned Number) { 123397bcafa2SValeriy Savchenko for (const PathDiagnosticLocationPair &LPair : P) { 123497bcafa2SValeriy Savchenko std::string Start = getSpanBeginForControlStart(Number), 123597bcafa2SValeriy Savchenko End = getSpanBeginForControlEnd(Number++); 123697bcafa2SValeriy Savchenko 123797bcafa2SValeriy Savchenko HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(), 123897bcafa2SValeriy Savchenko Start.c_str()); 123997bcafa2SValeriy Savchenko HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(), 124097bcafa2SValeriy Savchenko End.c_str()); 124197bcafa2SValeriy Savchenko } 124297bcafa2SValeriy Savchenko 124397bcafa2SValeriy Savchenko return Number; 124497bcafa2SValeriy Savchenko } 124597bcafa2SValeriy Savchenko 12469c4b2225SAlexander Belyaev void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, 12479c4b2225SAlexander Belyaev SourceRange Range, 12489c4b2225SAlexander Belyaev const char *HighlightStart, 12499c4b2225SAlexander Belyaev const char *HighlightEnd) { 12509c4b2225SAlexander Belyaev SourceManager &SM = R.getSourceMgr(); 12519c4b2225SAlexander Belyaev const LangOptions &LangOpts = R.getLangOpts(); 12529c4b2225SAlexander Belyaev 12539c4b2225SAlexander Belyaev SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin()); 12549c4b2225SAlexander Belyaev unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart); 12559c4b2225SAlexander Belyaev 12569c4b2225SAlexander Belyaev SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd()); 12579c4b2225SAlexander Belyaev unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd); 12589c4b2225SAlexander Belyaev 12599c4b2225SAlexander Belyaev if (EndLineNo < StartLineNo) 12609c4b2225SAlexander Belyaev return; 12619c4b2225SAlexander Belyaev 12629c4b2225SAlexander Belyaev if (SM.getFileID(InstantiationStart) != BugFileID || 12639c4b2225SAlexander Belyaev SM.getFileID(InstantiationEnd) != BugFileID) 12649c4b2225SAlexander Belyaev return; 12659c4b2225SAlexander Belyaev 12669c4b2225SAlexander Belyaev // Compute the column number of the end. 12679c4b2225SAlexander Belyaev unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); 12689c4b2225SAlexander Belyaev unsigned OldEndColNo = EndColNo; 12699c4b2225SAlexander Belyaev 12709c4b2225SAlexander Belyaev if (EndColNo) { 12719c4b2225SAlexander Belyaev // Add in the length of the token, so that we cover multi-char tokens. 12729c4b2225SAlexander Belyaev EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; 12739c4b2225SAlexander Belyaev } 12749c4b2225SAlexander Belyaev 12759c4b2225SAlexander Belyaev // Highlight the range. Make the span tag the outermost tag for the 12769c4b2225SAlexander Belyaev // selected range. 12779c4b2225SAlexander Belyaev 12789c4b2225SAlexander Belyaev SourceLocation E = 12799c4b2225SAlexander Belyaev InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); 12809c4b2225SAlexander Belyaev 12819c4b2225SAlexander Belyaev html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); 12829c4b2225SAlexander Belyaev } 12839c4b2225SAlexander Belyaev 12849c4b2225SAlexander Belyaev StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { 12859c4b2225SAlexander Belyaev return R"<<<( 12869c4b2225SAlexander Belyaev <script type='text/javascript'> 12879c4b2225SAlexander Belyaev var digitMatcher = new RegExp("[0-9]+"); 12889c4b2225SAlexander Belyaev 12899c4b2225SAlexander Belyaev var querySelectorAllArray = function(selector) { 12909c4b2225SAlexander Belyaev return Array.prototype.slice.call( 12919c4b2225SAlexander Belyaev document.querySelectorAll(selector)); 12929c4b2225SAlexander Belyaev } 12939c4b2225SAlexander Belyaev 12949c4b2225SAlexander Belyaev document.addEventListener("DOMContentLoaded", function() { 12959c4b2225SAlexander Belyaev querySelectorAllArray(".PathNav > a").forEach( 12969c4b2225SAlexander Belyaev function(currentValue, currentIndex) { 12979c4b2225SAlexander Belyaev var hrefValue = currentValue.getAttribute("href"); 12989c4b2225SAlexander Belyaev currentValue.onclick = function() { 12999c4b2225SAlexander Belyaev scrollTo(document.querySelector(hrefValue)); 13009c4b2225SAlexander Belyaev return false; 13019c4b2225SAlexander Belyaev }; 13029c4b2225SAlexander Belyaev }); 13039c4b2225SAlexander Belyaev }); 13049c4b2225SAlexander Belyaev 13059c4b2225SAlexander Belyaev var findNum = function() { 13069e02f587SValeriy Savchenko var s = document.querySelector(".msg.selected"); 13079c4b2225SAlexander Belyaev if (!s || s.id == "EndPath") { 13089c4b2225SAlexander Belyaev return 0; 13099c4b2225SAlexander Belyaev } 13109c4b2225SAlexander Belyaev var out = parseInt(digitMatcher.exec(s.id)[0]); 13119c4b2225SAlexander Belyaev return out; 13129c4b2225SAlexander Belyaev }; 13139c4b2225SAlexander Belyaev 13149dabacd0SDenys Petrov var classListAdd = function(el, theClass) { 13159dabacd0SDenys Petrov if(!el.className.baseVal) 13169dabacd0SDenys Petrov el.className += " " + theClass; 13179dabacd0SDenys Petrov else 13189dabacd0SDenys Petrov el.className.baseVal += " " + theClass; 13199dabacd0SDenys Petrov }; 13209dabacd0SDenys Petrov 13219dabacd0SDenys Petrov var classListRemove = function(el, theClass) { 13229dabacd0SDenys Petrov var className = (!el.className.baseVal) ? 13239dabacd0SDenys Petrov el.className : el.className.baseVal; 13249dabacd0SDenys Petrov className = className.replace(" " + theClass, ""); 13259dabacd0SDenys Petrov if(!el.className.baseVal) 13269dabacd0SDenys Petrov el.className = className; 13279dabacd0SDenys Petrov else 13289dabacd0SDenys Petrov el.className.baseVal = className; 13299dabacd0SDenys Petrov }; 13309dabacd0SDenys Petrov 13319c4b2225SAlexander Belyaev var scrollTo = function(el) { 13329c4b2225SAlexander Belyaev querySelectorAllArray(".selected").forEach(function(s) { 13339dabacd0SDenys Petrov classListRemove(s, "selected"); 13349c4b2225SAlexander Belyaev }); 13359dabacd0SDenys Petrov classListAdd(el, "selected"); 13369c4b2225SAlexander Belyaev window.scrollBy(0, el.getBoundingClientRect().top - 13379c4b2225SAlexander Belyaev (window.innerHeight / 2)); 13389e02f587SValeriy Savchenko highlightArrowsForSelectedEvent(); 13399dabacd0SDenys Petrov }; 13409c4b2225SAlexander Belyaev 13419c4b2225SAlexander Belyaev var move = function(num, up, numItems) { 13429c4b2225SAlexander Belyaev if (num == 1 && up || num == numItems - 1 && !up) { 13439c4b2225SAlexander Belyaev return 0; 13449c4b2225SAlexander Belyaev } else if (num == 0 && up) { 13459c4b2225SAlexander Belyaev return numItems - 1; 13469c4b2225SAlexander Belyaev } else if (num == 0 && !up) { 13479c4b2225SAlexander Belyaev return 1 % numItems; 13489c4b2225SAlexander Belyaev } 13499c4b2225SAlexander Belyaev return up ? num - 1 : num + 1; 13509c4b2225SAlexander Belyaev } 13519c4b2225SAlexander Belyaev 13529c4b2225SAlexander Belyaev var numToId = function(num) { 13539c4b2225SAlexander Belyaev if (num == 0) { 13549c4b2225SAlexander Belyaev return document.getElementById("EndPath") 13559c4b2225SAlexander Belyaev } 13569c4b2225SAlexander Belyaev return document.getElementById("Path" + num); 13579c4b2225SAlexander Belyaev }; 13589c4b2225SAlexander Belyaev 13599c4b2225SAlexander Belyaev var navigateTo = function(up) { 13609c4b2225SAlexander Belyaev var numItems = document.querySelectorAll( 13619c4b2225SAlexander Belyaev ".line > .msgEvent, .line > .msgControl").length; 13629c4b2225SAlexander Belyaev var currentSelected = findNum(); 13639c4b2225SAlexander Belyaev var newSelected = move(currentSelected, up, numItems); 13649c4b2225SAlexander Belyaev var newEl = numToId(newSelected, numItems); 13659c4b2225SAlexander Belyaev 13669c4b2225SAlexander Belyaev // Scroll element into center. 13679c4b2225SAlexander Belyaev scrollTo(newEl); 13689c4b2225SAlexander Belyaev }; 13699c4b2225SAlexander Belyaev 13709c4b2225SAlexander Belyaev window.addEventListener("keydown", function (event) { 13719c4b2225SAlexander Belyaev if (event.defaultPrevented) { 13729c4b2225SAlexander Belyaev return; 13739c4b2225SAlexander Belyaev } 13749dabacd0SDenys Petrov // key 'j' 13759dabacd0SDenys Petrov if (event.keyCode == 74) { 13769c4b2225SAlexander Belyaev navigateTo(/*up=*/false); 13779dabacd0SDenys Petrov // key 'k' 13789dabacd0SDenys Petrov } else if (event.keyCode == 75) { 13799c4b2225SAlexander Belyaev navigateTo(/*up=*/true); 13809c4b2225SAlexander Belyaev } else { 13819c4b2225SAlexander Belyaev return; 13829c4b2225SAlexander Belyaev } 13839c4b2225SAlexander Belyaev event.preventDefault(); 13849c4b2225SAlexander Belyaev }, true); 13859c4b2225SAlexander Belyaev </script> 13869c4b2225SAlexander Belyaev )<<<"; 13879c4b2225SAlexander Belyaev } 138897bcafa2SValeriy Savchenko 138997bcafa2SValeriy Savchenko StringRef HTMLDiagnostics::generateArrowDrawingJavascript() { 139097bcafa2SValeriy Savchenko return R"<<<( 139197bcafa2SValeriy Savchenko <script type='text/javascript'> 13929e02f587SValeriy Savchenko // Return range of numbers from a range [lower, upper). 13939e02f587SValeriy Savchenko function range(lower, upper) { 13949dabacd0SDenys Petrov var array = []; 13959dabacd0SDenys Petrov for (var i = lower; i <= upper; ++i) { 13969dabacd0SDenys Petrov array.push(i); 13979dabacd0SDenys Petrov } 13989dabacd0SDenys Petrov return array; 13999e02f587SValeriy Savchenko } 14009e02f587SValeriy Savchenko 14019e02f587SValeriy Savchenko var getRelatedArrowIndices = function(pathId) { 14029e02f587SValeriy Savchenko // HTML numeration of events is a bit different than it is in the path. 14039e02f587SValeriy Savchenko // Everything is rotated one step to the right, so the last element 14049e02f587SValeriy Savchenko // (error diagnostic) has index 0. 14059e02f587SValeriy Savchenko if (pathId == 0) { 14069e02f587SValeriy Savchenko // arrowIndices has at least 2 elements 14079e02f587SValeriy Savchenko pathId = arrowIndices.length - 1; 14089e02f587SValeriy Savchenko } 14099e02f587SValeriy Savchenko 14109e02f587SValeriy Savchenko return range(arrowIndices[pathId], arrowIndices[pathId - 1]); 14119e02f587SValeriy Savchenko } 14129e02f587SValeriy Savchenko 14139e02f587SValeriy Savchenko var highlightArrowsForSelectedEvent = function() { 14149e02f587SValeriy Savchenko const selectedNum = findNum(); 14159e02f587SValeriy Savchenko const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum); 14169e02f587SValeriy Savchenko arrowIndicesToHighlight.forEach((index) => { 14179e02f587SValeriy Savchenko var arrow = document.querySelector("#arrow" + index); 14189dabacd0SDenys Petrov if(arrow) { 14199dabacd0SDenys Petrov classListAdd(arrow, "selected") 14209dabacd0SDenys Petrov } 14219e02f587SValeriy Savchenko }); 14229e02f587SValeriy Savchenko } 14239e02f587SValeriy Savchenko 142497bcafa2SValeriy Savchenko var getAbsoluteBoundingRect = function(element) { 142597bcafa2SValeriy Savchenko const relative = element.getBoundingClientRect(); 142697bcafa2SValeriy Savchenko return { 142797bcafa2SValeriy Savchenko left: relative.left + window.pageXOffset, 142897bcafa2SValeriy Savchenko right: relative.right + window.pageXOffset, 142997bcafa2SValeriy Savchenko top: relative.top + window.pageYOffset, 143097bcafa2SValeriy Savchenko bottom: relative.bottom + window.pageYOffset, 143197bcafa2SValeriy Savchenko height: relative.height, 143297bcafa2SValeriy Savchenko width: relative.width 143397bcafa2SValeriy Savchenko }; 143497bcafa2SValeriy Savchenko } 143597bcafa2SValeriy Savchenko 143697bcafa2SValeriy Savchenko var drawArrow = function(index) { 143797bcafa2SValeriy Savchenko // This function is based on the great answer from SO: 143897bcafa2SValeriy Savchenko // https://stackoverflow.com/a/39575674/11582326 143997bcafa2SValeriy Savchenko var start = document.querySelector("#start" + index); 144097bcafa2SValeriy Savchenko var end = document.querySelector("#end" + index); 144197bcafa2SValeriy Savchenko var arrow = document.querySelector("#arrow" + index); 144297bcafa2SValeriy Savchenko 144397bcafa2SValeriy Savchenko var startRect = getAbsoluteBoundingRect(start); 144497bcafa2SValeriy Savchenko var endRect = getAbsoluteBoundingRect(end); 144597bcafa2SValeriy Savchenko 144697bcafa2SValeriy Savchenko // It is an arrow from a token to itself, no need to visualize it. 144797bcafa2SValeriy Savchenko if (startRect.top == endRect.top && 144897bcafa2SValeriy Savchenko startRect.left == endRect.left) 144997bcafa2SValeriy Savchenko return; 145097bcafa2SValeriy Savchenko 145197bcafa2SValeriy Savchenko // Each arrow is a very simple Bézier curve, with two nodes and 145297bcafa2SValeriy Savchenko // two handles. So, we need to calculate four points in the window: 145397bcafa2SValeriy Savchenko // * start node 145497bcafa2SValeriy Savchenko var posStart = { x: 0, y: 0 }; 145597bcafa2SValeriy Savchenko // * end node 145697bcafa2SValeriy Savchenko var posEnd = { x: 0, y: 0 }; 145797bcafa2SValeriy Savchenko // * handle for the start node 145897bcafa2SValeriy Savchenko var startHandle = { x: 0, y: 0 }; 145997bcafa2SValeriy Savchenko // * handle for the end node 146097bcafa2SValeriy Savchenko var endHandle = { x: 0, y: 0 }; 146197bcafa2SValeriy Savchenko // One can visualize it as follows: 146297bcafa2SValeriy Savchenko // 146397bcafa2SValeriy Savchenko // start handle 146497bcafa2SValeriy Savchenko // / 146597bcafa2SValeriy Savchenko // X"""_.-""""X 146697bcafa2SValeriy Savchenko // .' \ 146797bcafa2SValeriy Savchenko // / start node 146897bcafa2SValeriy Savchenko // | 146997bcafa2SValeriy Savchenko // | 147097bcafa2SValeriy Savchenko // | end node 147197bcafa2SValeriy Savchenko // \ / 147297bcafa2SValeriy Savchenko // `->X 147397bcafa2SValeriy Savchenko // X-' 147497bcafa2SValeriy Savchenko // \ 147597bcafa2SValeriy Savchenko // end handle 147697bcafa2SValeriy Savchenko // 147797bcafa2SValeriy Savchenko // NOTE: (0, 0) is the top left corner of the window. 147897bcafa2SValeriy Savchenko 147997bcafa2SValeriy Savchenko // We have 3 similar, but still different scenarios to cover: 148097bcafa2SValeriy Savchenko // 148197bcafa2SValeriy Savchenko // 1. Two tokens on different lines. 148297bcafa2SValeriy Savchenko // -xxx 148397bcafa2SValeriy Savchenko // / 148497bcafa2SValeriy Savchenko // \ 148597bcafa2SValeriy Savchenko // -> xxx 148697bcafa2SValeriy Savchenko // In this situation, we draw arrow on the left curving to the left. 148797bcafa2SValeriy Savchenko // 2. Two tokens on the same line, and the destination is on the right. 148897bcafa2SValeriy Savchenko // ____ 148997bcafa2SValeriy Savchenko // / \ 149097bcafa2SValeriy Savchenko // / V 149197bcafa2SValeriy Savchenko // xxx xxx 149297bcafa2SValeriy Savchenko // In this situation, we draw arrow above curving upwards. 149397bcafa2SValeriy Savchenko // 3. Two tokens on the same line, and the destination is on the left. 149497bcafa2SValeriy Savchenko // xxx xxx 149597bcafa2SValeriy Savchenko // ^ / 149697bcafa2SValeriy Savchenko // \____/ 149797bcafa2SValeriy Savchenko // In this situation, we draw arrow below curving downwards. 149897bcafa2SValeriy Savchenko const onDifferentLines = startRect.top <= endRect.top - 5 || 149997bcafa2SValeriy Savchenko startRect.top >= endRect.top + 5; 150097bcafa2SValeriy Savchenko const leftToRight = startRect.left < endRect.left; 150197bcafa2SValeriy Savchenko 150297bcafa2SValeriy Savchenko // NOTE: various magic constants are chosen empirically for 150397bcafa2SValeriy Savchenko // better positioning and look 150497bcafa2SValeriy Savchenko if (onDifferentLines) { 150597bcafa2SValeriy Savchenko // Case #1 150697bcafa2SValeriy Savchenko const topToBottom = startRect.top < endRect.top; 150797bcafa2SValeriy Savchenko posStart.x = startRect.left - 1; 150897bcafa2SValeriy Savchenko // We don't want to start it at the top left corner of the token, 150997bcafa2SValeriy Savchenko // it doesn't feel like this is where the arrow comes from. 151097bcafa2SValeriy Savchenko // For this reason, we start it in the middle of the left side 151197bcafa2SValeriy Savchenko // of the token. 151297bcafa2SValeriy Savchenko posStart.y = startRect.top + startRect.height / 2; 151397bcafa2SValeriy Savchenko 151497bcafa2SValeriy Savchenko // End node has arrow head and we give it a bit more space. 151597bcafa2SValeriy Savchenko posEnd.x = endRect.left - 4; 151697bcafa2SValeriy Savchenko posEnd.y = endRect.top; 151797bcafa2SValeriy Savchenko 151897bcafa2SValeriy Savchenko // Utility object with x and y offsets for handles. 151997bcafa2SValeriy Savchenko var curvature = { 152097bcafa2SValeriy Savchenko // We want bottom-to-top arrow to curve a bit more, so it doesn't 152197bcafa2SValeriy Savchenko // overlap much with top-to-bottom curves (much more frequent). 152297bcafa2SValeriy Savchenko x: topToBottom ? 15 : 25, 152397bcafa2SValeriy Savchenko y: Math.min((posEnd.y - posStart.y) / 3, 10) 152497bcafa2SValeriy Savchenko } 152597bcafa2SValeriy Savchenko 152697bcafa2SValeriy Savchenko // When destination is on the different line, we can make a 152797bcafa2SValeriy Savchenko // curvier arrow because we have space for it. 152897bcafa2SValeriy Savchenko // So, instead of using 152997bcafa2SValeriy Savchenko // 153097bcafa2SValeriy Savchenko // startHandle.x = posStart.x - curvature.x 153197bcafa2SValeriy Savchenko // endHandle.x = posEnd.x - curvature.x 153297bcafa2SValeriy Savchenko // 153397bcafa2SValeriy Savchenko // We use the leftmost of these two values for both handles. 153497bcafa2SValeriy Savchenko startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x; 153597bcafa2SValeriy Savchenko endHandle.x = startHandle.x; 153697bcafa2SValeriy Savchenko 153797bcafa2SValeriy Savchenko // Curving downwards from the start node... 153897bcafa2SValeriy Savchenko startHandle.y = posStart.y + curvature.y; 153997bcafa2SValeriy Savchenko // ... and upwards from the end node. 154097bcafa2SValeriy Savchenko endHandle.y = posEnd.y - curvature.y; 154197bcafa2SValeriy Savchenko 154297bcafa2SValeriy Savchenko } else if (leftToRight) { 154397bcafa2SValeriy Savchenko // Case #2 154497bcafa2SValeriy Savchenko // Starting from the top right corner... 154597bcafa2SValeriy Savchenko posStart.x = startRect.right - 1; 154697bcafa2SValeriy Savchenko posStart.y = startRect.top; 154797bcafa2SValeriy Savchenko 154897bcafa2SValeriy Savchenko // ...and ending at the top left corner of the end token. 154997bcafa2SValeriy Savchenko posEnd.x = endRect.left + 1; 155097bcafa2SValeriy Savchenko posEnd.y = endRect.top - 1; 155197bcafa2SValeriy Savchenko 155297bcafa2SValeriy Savchenko // Utility object with x and y offsets for handles. 155397bcafa2SValeriy Savchenko var curvature = { 155497bcafa2SValeriy Savchenko x: Math.min((posEnd.x - posStart.x) / 3, 15), 155597bcafa2SValeriy Savchenko y: 5 155697bcafa2SValeriy Savchenko } 155797bcafa2SValeriy Savchenko 155897bcafa2SValeriy Savchenko // Curving to the right... 155997bcafa2SValeriy Savchenko startHandle.x = posStart.x + curvature.x; 156097bcafa2SValeriy Savchenko // ... and upwards from the start node. 156197bcafa2SValeriy Savchenko startHandle.y = posStart.y - curvature.y; 156297bcafa2SValeriy Savchenko 156397bcafa2SValeriy Savchenko // And to the left... 156497bcafa2SValeriy Savchenko endHandle.x = posEnd.x - curvature.x; 156597bcafa2SValeriy Savchenko // ... and upwards from the end node. 156697bcafa2SValeriy Savchenko endHandle.y = posEnd.y - curvature.y; 156797bcafa2SValeriy Savchenko 156897bcafa2SValeriy Savchenko } else { 156997bcafa2SValeriy Savchenko // Case #3 157097bcafa2SValeriy Savchenko // Starting from the bottom right corner... 157197bcafa2SValeriy Savchenko posStart.x = startRect.right; 157297bcafa2SValeriy Savchenko posStart.y = startRect.bottom; 157397bcafa2SValeriy Savchenko 157497bcafa2SValeriy Savchenko // ...and ending also at the bottom right corner, but of the end token. 157597bcafa2SValeriy Savchenko posEnd.x = endRect.right - 1; 157697bcafa2SValeriy Savchenko posEnd.y = endRect.bottom + 1; 157797bcafa2SValeriy Savchenko 157897bcafa2SValeriy Savchenko // Utility object with x and y offsets for handles. 157997bcafa2SValeriy Savchenko var curvature = { 158097bcafa2SValeriy Savchenko x: Math.min((posStart.x - posEnd.x) / 3, 15), 158197bcafa2SValeriy Savchenko y: 5 158297bcafa2SValeriy Savchenko } 158397bcafa2SValeriy Savchenko 158497bcafa2SValeriy Savchenko // Curving to the left... 158597bcafa2SValeriy Savchenko startHandle.x = posStart.x - curvature.x; 158697bcafa2SValeriy Savchenko // ... and downwards from the start node. 158797bcafa2SValeriy Savchenko startHandle.y = posStart.y + curvature.y; 158897bcafa2SValeriy Savchenko 158997bcafa2SValeriy Savchenko // And to the right... 159097bcafa2SValeriy Savchenko endHandle.x = posEnd.x + curvature.x; 159197bcafa2SValeriy Savchenko // ... and downwards from the end node. 159297bcafa2SValeriy Savchenko endHandle.y = posEnd.y + curvature.y; 159397bcafa2SValeriy Savchenko } 159497bcafa2SValeriy Savchenko 159597bcafa2SValeriy Savchenko // Put it all together into a path. 159697bcafa2SValeriy Savchenko // More information on the format: 159797bcafa2SValeriy Savchenko // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths 159897bcafa2SValeriy Savchenko var pathStr = "M" + posStart.x + "," + posStart.y + " " + 159997bcafa2SValeriy Savchenko "C" + startHandle.x + "," + startHandle.y + " " + 160097bcafa2SValeriy Savchenko endHandle.x + "," + endHandle.y + " " + 160197bcafa2SValeriy Savchenko posEnd.x + "," + posEnd.y; 160297bcafa2SValeriy Savchenko 160397bcafa2SValeriy Savchenko arrow.setAttribute("d", pathStr); 160497bcafa2SValeriy Savchenko }; 160597bcafa2SValeriy Savchenko 160697bcafa2SValeriy Savchenko var drawArrows = function() { 160797bcafa2SValeriy Savchenko const numOfArrows = document.querySelectorAll("path[id^=arrow]").length; 160897bcafa2SValeriy Savchenko for (var i = 0; i < numOfArrows; ++i) { 160997bcafa2SValeriy Savchenko drawArrow(i); 161097bcafa2SValeriy Savchenko } 161197bcafa2SValeriy Savchenko } 161297bcafa2SValeriy Savchenko 161397bcafa2SValeriy Savchenko var toggleArrows = function(event) { 161497bcafa2SValeriy Savchenko const arrows = document.querySelector("#arrows"); 161597bcafa2SValeriy Savchenko if (event.target.checked) { 161697bcafa2SValeriy Savchenko arrows.setAttribute("visibility", "visible"); 161797bcafa2SValeriy Savchenko } else { 161897bcafa2SValeriy Savchenko arrows.setAttribute("visibility", "hidden"); 161997bcafa2SValeriy Savchenko } 162097bcafa2SValeriy Savchenko } 162197bcafa2SValeriy Savchenko 162297bcafa2SValeriy Savchenko window.addEventListener("resize", drawArrows); 162397bcafa2SValeriy Savchenko document.addEventListener("DOMContentLoaded", function() { 162497bcafa2SValeriy Savchenko // Whenever we show invocation, locations change, i.e. we 162597bcafa2SValeriy Savchenko // need to redraw arrows. 162697bcafa2SValeriy Savchenko document 162797bcafa2SValeriy Savchenko .querySelector('input[id="showinvocation"]') 162897bcafa2SValeriy Savchenko .addEventListener("click", drawArrows); 162997bcafa2SValeriy Savchenko // Hiding irrelevant lines also should cause arrow rerender. 163097bcafa2SValeriy Savchenko document 163197bcafa2SValeriy Savchenko .querySelector('input[name="showCounterexample"]') 163297bcafa2SValeriy Savchenko .addEventListener("change", drawArrows); 163397bcafa2SValeriy Savchenko document 163497bcafa2SValeriy Savchenko .querySelector('input[name="showArrows"]') 163597bcafa2SValeriy Savchenko .addEventListener("change", toggleArrows); 163697bcafa2SValeriy Savchenko drawArrows(); 16379e02f587SValeriy Savchenko // Default highlighting for the last event. 16389e02f587SValeriy Savchenko highlightArrowsForSelectedEvent(); 163997bcafa2SValeriy Savchenko }); 164097bcafa2SValeriy Savchenko </script> 164197bcafa2SValeriy Savchenko )<<<"; 164297bcafa2SValeriy Savchenko } 1643