xref: /llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp (revision b3470c3d7ab078b201bd65afc3b902d1ed41bc21)
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            << "\">&#x2190;</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            << "\">&#x2192;</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          << ")\">&#x2190;</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         << ")\">&#x2192;</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            << ")\">&#x2192;</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