Home | History | Annotate | Line # | Download | only in Core
      1 //===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===//
      2 //
      3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4 // See https://llvm.org/LICENSE.txt for license information.
      5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 //
      9 //  This file defines the HTMLDiagnostics object.
     10 //
     11 //===----------------------------------------------------------------------===//
     12 
     13 #include "clang/AST/Decl.h"
     14 #include "clang/AST/DeclBase.h"
     15 #include "clang/AST/Stmt.h"
     16 #include "clang/Analysis/IssueHash.h"
     17 #include "clang/Analysis/MacroExpansionContext.h"
     18 #include "clang/Analysis/PathDiagnostic.h"
     19 #include "clang/Basic/FileManager.h"
     20 #include "clang/Basic/LLVM.h"
     21 #include "clang/Basic/SourceLocation.h"
     22 #include "clang/Basic/SourceManager.h"
     23 #include "clang/Lex/Lexer.h"
     24 #include "clang/Lex/Preprocessor.h"
     25 #include "clang/Lex/Token.h"
     26 #include "clang/Rewrite/Core/HTMLRewrite.h"
     27 #include "clang/Rewrite/Core/Rewriter.h"
     28 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
     29 #include "llvm/ADT/ArrayRef.h"
     30 #include "llvm/ADT/SmallString.h"
     31 #include "llvm/ADT/StringRef.h"
     32 #include "llvm/ADT/iterator_range.h"
     33 #include "llvm/Support/Casting.h"
     34 #include "llvm/Support/Errc.h"
     35 #include "llvm/Support/ErrorHandling.h"
     36 #include "llvm/Support/FileSystem.h"
     37 #include "llvm/Support/MemoryBuffer.h"
     38 #include "llvm/Support/Path.h"
     39 #include "llvm/Support/raw_ostream.h"
     40 #include <algorithm>
     41 #include <cassert>
     42 #include <map>
     43 #include <memory>
     44 #include <set>
     45 #include <sstream>
     46 #include <string>
     47 #include <system_error>
     48 #include <utility>
     49 #include <vector>
     50 
     51 using namespace clang;
     52 using namespace ento;
     53 
     54 //===----------------------------------------------------------------------===//
     55 // Boilerplate.
     56 //===----------------------------------------------------------------------===//
     57 
     58 namespace {
     59 
     60 class HTMLDiagnostics : public PathDiagnosticConsumer {
     61   PathDiagnosticConsumerOptions DiagOpts;
     62   std::string Directory;
     63   bool createdDir = false;
     64   bool noDir = false;
     65   const Preprocessor &PP;
     66   const bool SupportsCrossFileDiagnostics;
     67 
     68 public:
     69   HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts,
     70                   const std::string &OutputDir, const Preprocessor &pp,
     71                   bool supportsMultipleFiles)
     72       : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp),
     73         SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
     74 
     75   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
     76 
     77   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
     78                             FilesMade *filesMade) override;
     79 
     80   StringRef getName() const override {
     81     return "HTMLDiagnostics";
     82   }
     83 
     84   bool supportsCrossFileDiagnostics() const override {
     85     return SupportsCrossFileDiagnostics;
     86   }
     87 
     88   unsigned ProcessMacroPiece(raw_ostream &os,
     89                              const PathDiagnosticMacroPiece& P,
     90                              unsigned num);
     91 
     92   void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P,
     93                    const std::vector<SourceRange> &PopUpRanges, unsigned num,
     94                    unsigned max);
     95 
     96   void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range,
     97                       const char *HighlightStart = "<span class=\"mrange\">",
     98                       const char *HighlightEnd = "</span>");
     99 
    100   void ReportDiag(const PathDiagnostic& D,
    101                   FilesMade *filesMade);
    102 
    103   // Generate the full HTML report
    104   std::string GenerateHTML(const PathDiagnostic& D, Rewriter &R,
    105                            const SourceManager& SMgr, const PathPieces& path,
    106                            const char *declName);
    107 
    108   // Add HTML header/footers to file specified by FID
    109   void FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
    110                     const SourceManager& SMgr, const PathPieces& path,
    111                     FileID FID, const FileEntry *Entry, const char *declName);
    112 
    113   // Rewrite the file specified by FID with HTML formatting.
    114   void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID);
    115 
    116 
    117 private:
    118   /// \return Javascript for displaying shortcuts help;
    119   StringRef showHelpJavascript();
    120 
    121   /// \return Javascript for navigating the HTML report using j/k keys.
    122   StringRef generateKeyboardNavigationJavascript();
    123 
    124   /// \return JavaScript for an option to only show relevant lines.
    125   std::string showRelevantLinesJavascript(
    126     const PathDiagnostic &D, const PathPieces &path);
    127 
    128   /// Write executed lines from \p D in JSON format into \p os.
    129   void dumpCoverageData(const PathDiagnostic &D,
    130                         const PathPieces &path,
    131                         llvm::raw_string_ostream &os);
    132 };
    133 
    134 } // namespace
    135 
    136 void ento::createHTMLDiagnosticConsumer(
    137     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
    138     const std::string &OutputDir, const Preprocessor &PP,
    139     const cross_tu::CrossTranslationUnitContext &CTU,
    140     const MacroExpansionContext &MacroExpansions) {
    141 
    142   // FIXME: HTML is currently our default output type, but if the output
    143   // directory isn't specified, it acts like if it was in the minimal text
    144   // output mode. This doesn't make much sense, we should have the minimal text
    145   // as our default. In the case of backward compatibility concerns, this could
    146   // be preserved with -analyzer-config-compatibility-mode=true.
    147   createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
    148                                           MacroExpansions);
    149 
    150   // TODO: Emit an error here.
    151   if (OutputDir.empty())
    152     return;
    153 
    154   C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, true));
    155 }
    156 
    157 void ento::createHTMLSingleFileDiagnosticConsumer(
    158     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
    159     const std::string &OutputDir, const Preprocessor &PP,
    160     const cross_tu::CrossTranslationUnitContext &CTU,
    161     const clang::MacroExpansionContext &MacroExpansions) {
    162   createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU,
    163                                           MacroExpansions);
    164 
    165   // TODO: Emit an error here.
    166   if (OutputDir.empty())
    167     return;
    168 
    169   C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, false));
    170 }
    171 
    172 void ento::createPlistHTMLDiagnosticConsumer(
    173     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
    174     const std::string &prefix, const Preprocessor &PP,
    175     const cross_tu::CrossTranslationUnitContext &CTU,
    176     const MacroExpansionContext &MacroExpansions) {
    177   createHTMLDiagnosticConsumer(
    178       DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, CTU,
    179       MacroExpansions);
    180   createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU,
    181                                          MacroExpansions);
    182   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP,
    183                                           CTU, MacroExpansions);
    184 }
    185 
    186 void ento::createSarifHTMLDiagnosticConsumer(
    187     PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C,
    188     const std::string &sarif_file, const Preprocessor &PP,
    189     const cross_tu::CrossTranslationUnitContext &CTU,
    190     const MacroExpansionContext &MacroExpansions) {
    191   createHTMLDiagnosticConsumer(
    192       DiagOpts, C, std::string(llvm::sys::path::parent_path(sarif_file)), PP,
    193       CTU, MacroExpansions);
    194   createSarifDiagnosticConsumer(DiagOpts, C, sarif_file, PP, CTU,
    195                                 MacroExpansions);
    196   createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, sarif_file,
    197                                           PP, CTU, MacroExpansions);
    198 }
    199 
    200 //===----------------------------------------------------------------------===//
    201 // Report processing.
    202 //===----------------------------------------------------------------------===//
    203 
    204 void HTMLDiagnostics::FlushDiagnosticsImpl(
    205   std::vector<const PathDiagnostic *> &Diags,
    206   FilesMade *filesMade) {
    207   for (const auto Diag : Diags)
    208     ReportDiag(*Diag, filesMade);
    209 }
    210 
    211 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
    212                                  FilesMade *filesMade) {
    213   // Create the HTML directory if it is missing.
    214   if (!createdDir) {
    215     createdDir = true;
    216     if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
    217       llvm::errs() << "warning: could not create directory '"
    218                    << Directory << "': " << ec.message() << '\n';
    219       noDir = true;
    220       return;
    221     }
    222   }
    223 
    224   if (noDir)
    225     return;
    226 
    227   // First flatten out the entire path to make it easier to use.
    228   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
    229 
    230   // The path as already been prechecked that the path is non-empty.
    231   assert(!path.empty());
    232   const SourceManager &SMgr = path.front()->getLocation().getManager();
    233 
    234   // Create a new rewriter to generate HTML.
    235   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
    236 
    237   // The file for the first path element is considered the main report file, it
    238   // will usually be equivalent to SMgr.getMainFileID(); however, it might be a
    239   // header when -analyzer-opt-analyze-headers is used.
    240   FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
    241 
    242   // Get the function/method name
    243   SmallString<128> declName("unknown");
    244   int offsetDecl = 0;
    245   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
    246       if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
    247           declName = ND->getDeclName().getAsString();
    248 
    249       if (const Stmt *Body = DeclWithIssue->getBody()) {
    250           // Retrieve the relative position of the declaration which will be used
    251           // for the file name
    252           FullSourceLoc L(
    253               SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),
    254               SMgr);
    255           FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);
    256           offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
    257       }
    258   }
    259 
    260   std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
    261   if (report.empty()) {
    262     llvm::errs() << "warning: no diagnostics generated for main file.\n";
    263     return;
    264   }
    265 
    266   // Create a path for the target HTML file.
    267   int FD;
    268   SmallString<128> Model, ResultPath;
    269 
    270   if (!DiagOpts.ShouldWriteStableReportFilename) {
    271       llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
    272       if (std::error_code EC =
    273           llvm::sys::fs::make_absolute(Model)) {
    274           llvm::errs() << "warning: could not make '" << Model
    275                        << "' absolute: " << EC.message() << '\n';
    276         return;
    277       }
    278       if (std::error_code EC = llvm::sys::fs::createUniqueFile(
    279               Model, FD, ResultPath, llvm::sys::fs::OF_Text)) {
    280         llvm::errs() << "warning: could not create file in '" << Directory
    281                      << "': " << EC.message() << '\n';
    282         return;
    283       }
    284   } else {
    285       int i = 1;
    286       std::error_code EC;
    287       do {
    288           // Find a filename which is not already used
    289           const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile);
    290           std::stringstream filename;
    291           Model = "";
    292           filename << "report-"
    293                    << llvm::sys::path::filename(Entry->getName()).str()
    294                    << "-" << declName.c_str()
    295                    << "-" << offsetDecl
    296                    << "-" << i << ".html";
    297           llvm::sys::path::append(Model, Directory,
    298                                   filename.str());
    299           EC = llvm::sys::fs::openFileForReadWrite(
    300               Model, FD, llvm::sys::fs::CD_CreateNew, llvm::sys::fs::OF_None);
    301           if (EC && EC != llvm::errc::file_exists) {
    302               llvm::errs() << "warning: could not create file '" << Model
    303                            << "': " << EC.message() << '\n';
    304               return;
    305           }
    306           i++;
    307       } while (EC);
    308   }
    309 
    310   llvm::raw_fd_ostream os(FD, true);
    311 
    312   if (filesMade)
    313     filesMade->addDiagnostic(D, getName(),
    314                              llvm::sys::path::filename(ResultPath));
    315 
    316   // Emit the HTML to disk.
    317   os << report;
    318 }
    319 
    320 std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
    321     const SourceManager& SMgr, const PathPieces& path, const char *declName) {
    322   // Rewrite source files as HTML for every new file the path crosses
    323   std::vector<FileID> FileIDs;
    324   for (auto I : path) {
    325     FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
    326     if (llvm::is_contained(FileIDs, FID))
    327       continue;
    328 
    329     FileIDs.push_back(FID);
    330     RewriteFile(R, path, FID);
    331   }
    332 
    333   if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
    334     // Prefix file names, anchor tags, and nav cursors to every file
    335     for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
    336       std::string s;
    337       llvm::raw_string_ostream os(s);
    338 
    339       if (I != FileIDs.begin())
    340         os << "<hr class=divider>\n";
    341 
    342       os << "<div id=File" << I->getHashValue() << ">\n";
    343 
    344       // Left nav arrow
    345       if (I != FileIDs.begin())
    346         os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
    347            << "\">&#x2190;</a></div>";
    348 
    349       os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName()
    350          << "</h4>\n";
    351 
    352       // Right nav arrow
    353       if (I + 1 != E)
    354         os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
    355            << "\">&#x2192;</a></div>";
    356 
    357       os << "</div>\n";
    358 
    359       R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str());
    360     }
    361 
    362     // Append files to the main report file in the order they appear in the path
    363     for (auto I : llvm::make_range(FileIDs.begin() + 1, FileIDs.end())) {
    364       std::string s;
    365       llvm::raw_string_ostream os(s);
    366 
    367       const RewriteBuffer *Buf = R.getRewriteBufferFor(I);
    368       for (auto BI : *Buf)
    369         os << BI;
    370 
    371       R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str());
    372     }
    373   }
    374 
    375   const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]);
    376   if (!Buf)
    377     return {};
    378 
    379   // Add CSS, header, and footer.
    380   FileID FID =
    381       path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
    382   const FileEntry* Entry = SMgr.getFileEntryForID(FID);
    383   FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName);
    384 
    385   std::string file;
    386   llvm::raw_string_ostream os(file);
    387   for (auto BI : *Buf)
    388     os << BI;
    389 
    390   return os.str();
    391 }
    392 
    393 void HTMLDiagnostics::dumpCoverageData(
    394     const PathDiagnostic &D,
    395     const PathPieces &path,
    396     llvm::raw_string_ostream &os) {
    397 
    398   const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines();
    399 
    400   os << "var relevant_lines = {";
    401   for (auto I = ExecutedLines.begin(),
    402             E = ExecutedLines.end(); I != E; ++I) {
    403     if (I != ExecutedLines.begin())
    404       os << ", ";
    405 
    406     os << "\"" << I->first.getHashValue() << "\": {";
    407     for (unsigned LineNo : I->second) {
    408       if (LineNo != *(I->second.begin()))
    409         os << ", ";
    410 
    411       os << "\"" << LineNo << "\": 1";
    412     }
    413     os << "}";
    414   }
    415 
    416   os << "};";
    417 }
    418 
    419 std::string HTMLDiagnostics::showRelevantLinesJavascript(
    420       const PathDiagnostic &D, const PathPieces &path) {
    421   std::string s;
    422   llvm::raw_string_ostream os(s);
    423   os << "<script type='text/javascript'>\n";
    424   dumpCoverageData(D, path, os);
    425   os << R"<<<(
    426 
    427 var filterCounterexample = function (hide) {
    428   var tables = document.getElementsByClassName("code");
    429   for (var t=0; t<tables.length; t++) {
    430     var table = tables[t];
    431     var file_id = table.getAttribute("data-fileid");
    432     var lines_in_fid = relevant_lines[file_id];
    433     if (!lines_in_fid) {
    434       lines_in_fid = {};
    435     }
    436     var lines = table.getElementsByClassName("codeline");
    437     for (var i=0; i<lines.length; i++) {
    438         var el = lines[i];
    439         var lineNo = el.getAttribute("data-linenumber");
    440         if (!lines_in_fid[lineNo]) {
    441           if (hide) {
    442             el.setAttribute("hidden", "");
    443           } else {
    444             el.removeAttribute("hidden");
    445           }
    446         }
    447     }
    448   }
    449 }
    450 
    451 window.addEventListener("keydown", function (event) {
    452   if (event.defaultPrevented) {
    453     return;
    454   }
    455   if (event.key == "S") {
    456     var checked = document.getElementsByName("showCounterexample")[0].checked;
    457     filterCounterexample(!checked);
    458     document.getElementsByName("showCounterexample")[0].checked = !checked;
    459   } else {
    460     return;
    461   }
    462   event.preventDefault();
    463 }, true);
    464 
    465 document.addEventListener("DOMContentLoaded", function() {
    466     document.querySelector('input[name="showCounterexample"]').onchange=
    467         function (event) {
    468       filterCounterexample(this.checked);
    469     };
    470 });
    471 </script>
    472 
    473 <form>
    474     <input type="checkbox" name="showCounterexample" id="showCounterexample" />
    475     <label for="showCounterexample">
    476        Show only relevant lines
    477     </label>
    478 </form>
    479 )<<<";
    480 
    481   return os.str();
    482 }
    483 
    484 void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
    485     const SourceManager& SMgr, const PathPieces& path, FileID FID,
    486     const FileEntry *Entry, const char *declName) {
    487   // This is a cludge; basically we want to append either the full
    488   // working directory if we have no directory information.  This is
    489   // a work in progress.
    490 
    491   llvm::SmallString<0> DirName;
    492 
    493   if (llvm::sys::path::is_relative(Entry->getName())) {
    494     llvm::sys::fs::current_path(DirName);
    495     DirName += '/';
    496   }
    497 
    498   int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
    499   int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
    500 
    501   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript());
    502 
    503   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
    504                      generateKeyboardNavigationJavascript());
    505 
    506   // Checkbox and javascript for filtering the output to the counterexample.
    507   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
    508                      showRelevantLinesJavascript(D, path));
    509 
    510   // Add the name of the file as an <h1> tag.
    511   {
    512     std::string s;
    513     llvm::raw_string_ostream os(s);
    514 
    515     os << "<!-- REPORTHEADER -->\n"
    516        << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
    517           "<tr><td class=\"rowname\">File:</td><td>"
    518        << html::EscapeText(DirName)
    519        << html::EscapeText(Entry->getName())
    520        << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
    521           "<a href=\"#EndPath\">line "
    522        << LineNumber
    523        << ", column "
    524        << ColumnNumber
    525        << "</a><br />"
    526        << D.getVerboseDescription() << "</td></tr>\n";
    527 
    528     // The navigation across the extra notes pieces.
    529     unsigned NumExtraPieces = 0;
    530     for (const auto &Piece : path) {
    531       if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
    532         int LineNumber =
    533             P->getLocation().asLocation().getExpansionLineNumber();
    534         int ColumnNumber =
    535             P->getLocation().asLocation().getExpansionColumnNumber();
    536         os << "<tr><td class=\"rowname\">Note:</td><td>"
    537            << "<a href=\"#Note" << NumExtraPieces << "\">line "
    538            << LineNumber << ", column " << ColumnNumber << "</a><br />"
    539            << P->getString() << "</td></tr>";
    540         ++NumExtraPieces;
    541       }
    542     }
    543 
    544     // Output any other meta data.
    545 
    546     for (PathDiagnostic::meta_iterator I = D.meta_begin(), E = D.meta_end();
    547          I != E; ++I) {
    548       os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n";
    549     }
    550 
    551     os << R"<<<(
    552 </table>
    553 <!-- REPORTSUMMARYEXTRA -->
    554 <h3>Annotated Source Code</h3>
    555 <p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
    556    to see keyboard shortcuts</p>
    557 <input type="checkbox" class="spoilerhider" id="showinvocation" />
    558 <label for="showinvocation" >Show analyzer invocation</label>
    559 <div class="spoiler">clang -cc1 )<<<";
    560     os << html::EscapeText(DiagOpts.ToolInvocation);
    561     os << R"<<<(
    562 </div>
    563 <div id='tooltiphint' hidden="true">
    564   <p>Keyboard shortcuts: </p>
    565   <ul>
    566     <li>Use 'j/k' keys for keyboard navigation</li>
    567     <li>Use 'Shift+S' to show/hide relevant lines</li>
    568     <li>Use '?' to toggle this window</li>
    569   </ul>
    570   <a href="#" onclick="toggleHelp(); return false;">Close</a>
    571 </div>
    572 )<<<";
    573     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
    574   }
    575 
    576   // Embed meta-data tags.
    577   {
    578     std::string s;
    579     llvm::raw_string_ostream os(s);
    580 
    581     StringRef BugDesc = D.getVerboseDescription();
    582     if (!BugDesc.empty())
    583       os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
    584 
    585     StringRef BugType = D.getBugType();
    586     if (!BugType.empty())
    587       os << "\n<!-- BUGTYPE " << BugType << " -->\n";
    588 
    589     PathDiagnosticLocation UPDLoc = D.getUniqueingLoc();
    590     FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()
    591                                              ? UPDLoc.asLocation()
    592                                              : D.getLocation().asLocation()),
    593                     SMgr);
    594     const Decl *DeclWithIssue = D.getDeclWithIssue();
    595 
    596     StringRef BugCategory = D.getCategory();
    597     if (!BugCategory.empty())
    598       os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
    599 
    600     os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n";
    601 
    602     os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n";
    603 
    604     os  << "\n<!-- FUNCTIONNAME " <<  declName << " -->\n";
    605 
    606     os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT "
    607        << getIssueHash(L, D.getCheckerName(), D.getBugType(), DeclWithIssue,
    608                        PP.getLangOpts())
    609        << " -->\n";
    610 
    611     os << "\n<!-- BUGLINE "
    612        << LineNumber
    613        << " -->\n";
    614 
    615     os << "\n<!-- BUGCOLUMN "
    616       << ColumnNumber
    617       << " -->\n";
    618 
    619     os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n";
    620 
    621     // Mark the end of the tags.
    622     os << "\n<!-- BUGMETAEND -->\n";
    623 
    624     // Insert the text.
    625     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
    626   }
    627 
    628   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
    629 }
    630 
    631 StringRef HTMLDiagnostics::showHelpJavascript() {
    632   return R"<<<(
    633 <script type='text/javascript'>
    634 
    635 var toggleHelp = function() {
    636     var hint = document.querySelector("#tooltiphint");
    637     var attributeName = "hidden";
    638     if (hint.hasAttribute(attributeName)) {
    639       hint.removeAttribute(attributeName);
    640     } else {
    641       hint.setAttribute("hidden", "true");
    642     }
    643 };
    644 window.addEventListener("keydown", function (event) {
    645   if (event.defaultPrevented) {
    646     return;
    647   }
    648   if (event.key == "?") {
    649     toggleHelp();
    650   } else {
    651     return;
    652   }
    653   event.preventDefault();
    654 });
    655 </script>
    656 )<<<";
    657 }
    658 
    659 static bool shouldDisplayPopUpRange(const SourceRange &Range) {
    660   return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID());
    661 }
    662 
    663 static void
    664 HandlePopUpPieceStartTag(Rewriter &R,
    665                          const std::vector<SourceRange> &PopUpRanges) {
    666   for (const auto &Range : PopUpRanges) {
    667     if (!shouldDisplayPopUpRange(Range))
    668       continue;
    669 
    670     html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "",
    671                          "<table class='variable_popup'><tbody>",
    672                          /*IsTokenRange=*/true);
    673   }
    674 }
    675 
    676 static void HandlePopUpPieceEndTag(Rewriter &R,
    677                                    const PathDiagnosticPopUpPiece &Piece,
    678                                    std::vector<SourceRange> &PopUpRanges,
    679                                    unsigned int LastReportedPieceIndex,
    680                                    unsigned int PopUpPieceIndex) {
    681   SmallString<256> Buf;
    682   llvm::raw_svector_ostream Out(Buf);
    683 
    684   SourceRange Range(Piece.getLocation().asRange());
    685   if (!shouldDisplayPopUpRange(Range))
    686     return;
    687 
    688   // Write out the path indices with a right arrow and the message as a row.
    689   Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>"
    690       << LastReportedPieceIndex;
    691 
    692   // Also annotate the state transition with extra indices.
    693   Out << '.' << PopUpPieceIndex;
    694 
    695   Out << "</div></td><td>" << Piece.getString() << "</td></tr>";
    696 
    697   // If no report made at this range mark the variable and add the end tags.
    698   if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) ==
    699       PopUpRanges.end()) {
    700     // Store that we create a report at this range.
    701     PopUpRanges.push_back(Range);
    702 
    703     Out << "</tbody></table></span>";
    704     html::HighlightRange(R, Range.getBegin(), Range.getEnd(),
    705                          "<span class='variable'>", Buf.c_str(),
    706                          /*IsTokenRange=*/true);
    707   } else {
    708     // Otherwise inject just the new row at the end of the range.
    709     html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", Buf.c_str(),
    710                          /*IsTokenRange=*/true);
    711   }
    712 }
    713 
    714 void HTMLDiagnostics::RewriteFile(Rewriter &R,
    715                                   const PathPieces& path, FileID FID) {
    716   // Process the path.
    717   // Maintain the counts of extra note pieces separately.
    718   unsigned TotalPieces = path.size();
    719   unsigned TotalNotePieces = std::count_if(
    720       path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) {
    721         return isa<PathDiagnosticNotePiece>(*p);
    722       });
    723   unsigned PopUpPieceCount = std::count_if(
    724       path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) {
    725         return isa<PathDiagnosticPopUpPiece>(*p);
    726       });
    727 
    728   unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount;
    729   unsigned NumRegularPieces = TotalRegularPieces;
    730   unsigned NumNotePieces = TotalNotePieces;
    731   // Stores the count of the regular piece indices.
    732   std::map<int, int> IndexMap;
    733 
    734   // Stores the different ranges where we have reported something.
    735   std::vector<SourceRange> PopUpRanges;
    736   for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
    737     const auto &Piece = *I->get();
    738 
    739     if (isa<PathDiagnosticPopUpPiece>(Piece)) {
    740       ++IndexMap[NumRegularPieces];
    741     } else if (isa<PathDiagnosticNotePiece>(Piece)) {
    742       // This adds diagnostic bubbles, but not navigation.
    743       // Navigation through note pieces would be added later,
    744       // as a separate pass through the piece list.
    745       HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces);
    746       --NumNotePieces;
    747     } else {
    748       HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces,
    749                   TotalRegularPieces);
    750       --NumRegularPieces;
    751     }
    752   }
    753 
    754   // Secondary indexing if we are having multiple pop-ups between two notes.
    755   // (e.g. [(13) 'a' is 'true'];  [(13.1) 'b' is 'false'];  [(13.2) 'c' is...)
    756   NumRegularPieces = TotalRegularPieces;
    757   for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
    758     const auto &Piece = *I->get();
    759 
    760     if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) {
    761       int PopUpPieceIndex = IndexMap[NumRegularPieces];
    762 
    763       // Pop-up pieces needs the index of the last reported piece and its count
    764       // how many times we report to handle multiple reports on the same range.
    765       // This marks the variable, adds the </table> end tag and the message
    766       // (list element) as a row. The <table> start tag will be added after the
    767       // rows has been written out. Note: It stores every different range.
    768       HandlePopUpPieceEndTag(R, *PopUpP, PopUpRanges, NumRegularPieces,
    769                              PopUpPieceIndex);
    770 
    771       if (PopUpPieceIndex > 0)
    772         --IndexMap[NumRegularPieces];
    773 
    774     } else if (!isa<PathDiagnosticNotePiece>(Piece)) {
    775       --NumRegularPieces;
    776     }
    777   }
    778 
    779   // Add the <table> start tag of pop-up pieces based on the stored ranges.
    780   HandlePopUpPieceStartTag(R, PopUpRanges);
    781 
    782   // Add line numbers, header, footer, etc.
    783   html::EscapeText(R, FID);
    784   html::AddLineNumbers(R, FID);
    785 
    786   // If we have a preprocessor, relex the file and syntax highlight.
    787   // We might not have a preprocessor if we come from a deserialized AST file,
    788   // for example.
    789   html::SyntaxHighlight(R, FID, PP);
    790   html::HighlightMacros(R, FID, PP);
    791 }
    792 
    793 void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID,
    794                                   const PathDiagnosticPiece &P,
    795                                   const std::vector<SourceRange> &PopUpRanges,
    796                                   unsigned num, unsigned max) {
    797   // For now, just draw a box above the line in question, and emit the
    798   // warning.
    799   FullSourceLoc Pos = P.getLocation().asLocation();
    800 
    801   if (!Pos.isValid())
    802     return;
    803 
    804   SourceManager &SM = R.getSourceMgr();
    805   assert(&Pos.getManager() == &SM && "SourceManagers are different!");
    806   std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos);
    807 
    808   if (LPosInfo.first != BugFileID)
    809     return;
    810 
    811   llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first);
    812   const char *FileStart = Buf.getBufferStart();
    813 
    814   // Compute the column number.  Rewind from the current position to the start
    815   // of the line.
    816   unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
    817   const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData();
    818   const char *LineStart = TokInstantiationPtr-ColNo;
    819 
    820   // Compute LineEnd.
    821   const char *LineEnd = TokInstantiationPtr;
    822   const char *FileEnd = Buf.getBufferEnd();
    823   while (*LineEnd != '\n' && LineEnd != FileEnd)
    824     ++LineEnd;
    825 
    826   // Compute the margin offset by counting tabs and non-tabs.
    827   unsigned PosNo = 0;
    828   for (const char* c = LineStart; c != TokInstantiationPtr; ++c)
    829     PosNo += *c == '\t' ? 8 : 1;
    830 
    831   // Create the html for the message.
    832 
    833   const char *Kind = nullptr;
    834   bool IsNote = false;
    835   bool SuppressIndex = (max == 1);
    836   switch (P.getKind()) {
    837   case PathDiagnosticPiece::Event: Kind = "Event"; break;
    838   case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break;
    839     // Setting Kind to "Control" is intentional.
    840   case PathDiagnosticPiece::Macro: Kind = "Control"; break;
    841   case PathDiagnosticPiece::Note:
    842     Kind = "Note";
    843     IsNote = true;
    844     SuppressIndex = true;
    845     break;
    846   case PathDiagnosticPiece::Call:
    847   case PathDiagnosticPiece::PopUp:
    848     llvm_unreachable("Calls and extra notes should already be handled");
    849   }
    850 
    851   std::string sbuf;
    852   llvm::raw_string_ostream os(sbuf);
    853 
    854   os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
    855 
    856   if (IsNote)
    857     os << "Note" << num;
    858   else if (num == max)
    859     os << "EndPath";
    860   else
    861     os << "Path" << num;
    862 
    863   os << "\" class=\"msg";
    864   if (Kind)
    865     os << " msg" << Kind;
    866   os << "\" style=\"margin-left:" << PosNo << "ex";
    867 
    868   // Output a maximum size.
    869   if (!isa<PathDiagnosticMacroPiece>(P)) {
    870     // Get the string and determining its maximum substring.
    871     const auto &Msg = P.getString();
    872     unsigned max_token = 0;
    873     unsigned cnt = 0;
    874     unsigned len = Msg.size();
    875 
    876     for (char C : Msg)
    877       switch (C) {
    878       default:
    879         ++cnt;
    880         continue;
    881       case ' ':
    882       case '\t':
    883       case '\n':
    884         if (cnt > max_token) max_token = cnt;
    885         cnt = 0;
    886       }
    887 
    888     if (cnt > max_token)
    889       max_token = cnt;
    890 
    891     // Determine the approximate size of the message bubble in em.
    892     unsigned em;
    893     const unsigned max_line = 120;
    894 
    895     if (max_token >= max_line)
    896       em = max_token / 2;
    897     else {
    898       unsigned characters = max_line;
    899       unsigned lines = len / max_line;
    900 
    901       if (lines > 0) {
    902         for (; characters > max_token; --characters)
    903           if (len / characters > lines) {
    904             ++characters;
    905             break;
    906           }
    907       }
    908 
    909       em = characters / 2;
    910     }
    911 
    912     if (em < max_line/2)
    913       os << "; max-width:" << em << "em";
    914   }
    915   else
    916     os << "; max-width:100em";
    917 
    918   os << "\">";
    919 
    920   if (!SuppressIndex) {
    921     os << "<table class=\"msgT\"><tr><td valign=\"top\">";
    922     os << "<div class=\"PathIndex";
    923     if (Kind) os << " PathIndex" << Kind;
    924     os << "\">" << num << "</div>";
    925 
    926     if (num > 1) {
    927       os << "</td><td><div class=\"PathNav\"><a href=\"#Path"
    928          << (num - 1)
    929          << "\" title=\"Previous event ("
    930          << (num - 1)
    931          << ")\">&#x2190;</a></div>";
    932     }
    933 
    934     os << "</td><td>";
    935   }
    936 
    937   if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
    938     os << "Within the expansion of the macro '";
    939 
    940     // Get the name of the macro by relexing it.
    941     {
    942       FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
    943       assert(L.isFileID());
    944       StringRef BufferInfo = L.getBufferData();
    945       std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc();
    946       const char* MacroName = LocInfo.second + BufferInfo.data();
    947       Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(),
    948                      BufferInfo.begin(), MacroName, BufferInfo.end());
    949 
    950       Token TheTok;
    951       rawLexer.LexFromRawLexer(TheTok);
    952       for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i)
    953         os << MacroName[i];
    954     }
    955 
    956     os << "':\n";
    957 
    958     if (!SuppressIndex) {
    959       os << "</td>";
    960       if (num < max) {
    961         os << "<td><div class=\"PathNav\"><a href=\"#";
    962         if (num == max - 1)
    963           os << "EndPath";
    964         else
    965           os << "Path" << (num + 1);
    966         os << "\" title=\"Next event ("
    967         << (num + 1)
    968         << ")\">&#x2192;</a></div></td>";
    969       }
    970 
    971       os << "</tr></table>";
    972     }
    973 
    974     // Within a macro piece.  Write out each event.
    975     ProcessMacroPiece(os, *MP, 0);
    976   }
    977   else {
    978     os << html::EscapeText(P.getString());
    979 
    980     if (!SuppressIndex) {
    981       os << "</td>";
    982       if (num < max) {
    983         os << "<td><div class=\"PathNav\"><a href=\"#";
    984         if (num == max - 1)
    985           os << "EndPath";
    986         else
    987           os << "Path" << (num + 1);
    988         os << "\" title=\"Next event ("
    989            << (num + 1)
    990            << ")\">&#x2192;</a></div></td>";
    991       }
    992 
    993       os << "</tr></table>";
    994     }
    995   }
    996 
    997   os << "</div></td></tr>";
    998 
    999   // Insert the new html.
   1000   unsigned DisplayPos = LineEnd - FileStart;
   1001   SourceLocation Loc =
   1002     SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
   1003 
   1004   R.InsertTextBefore(Loc, os.str());
   1005 
   1006   // Now highlight the ranges.
   1007   ArrayRef<SourceRange> Ranges = P.getRanges();
   1008   for (const auto &Range : Ranges) {
   1009     // If we have already highlighted the range as a pop-up there is no work.
   1010     if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) !=
   1011         PopUpRanges.end())
   1012       continue;
   1013 
   1014     HighlightRange(R, LPosInfo.first, Range);
   1015   }
   1016 }
   1017 
   1018 static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
   1019   unsigned x = n % ('z' - 'a');
   1020   n /= 'z' - 'a';
   1021 
   1022   if (n > 0)
   1023     EmitAlphaCounter(os, n);
   1024 
   1025   os << char('a' + x);
   1026 }
   1027 
   1028 unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
   1029                                             const PathDiagnosticMacroPiece& P,
   1030                                             unsigned num) {
   1031   for (const auto &subPiece : P.subPieces) {
   1032     if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
   1033       num = ProcessMacroPiece(os, *MP, num);
   1034       continue;
   1035     }
   1036 
   1037     if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
   1038       os << "<div class=\"msg msgEvent\" style=\"width:94%; "
   1039             "margin-left:5px\">"
   1040             "<table class=\"msgT\"><tr>"
   1041             "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
   1042       EmitAlphaCounter(os, num++);
   1043       os << "</div></td><td valign=\"top\">"
   1044          << html::EscapeText(EP->getString())
   1045          << "</td></tr></table></div>\n";
   1046     }
   1047   }
   1048 
   1049   return num;
   1050 }
   1051 
   1052 void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
   1053                                      SourceRange Range,
   1054                                      const char *HighlightStart,
   1055                                      const char *HighlightEnd) {
   1056   SourceManager &SM = R.getSourceMgr();
   1057   const LangOptions &LangOpts = R.getLangOpts();
   1058 
   1059   SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
   1060   unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
   1061 
   1062   SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
   1063   unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
   1064 
   1065   if (EndLineNo < StartLineNo)
   1066     return;
   1067 
   1068   if (SM.getFileID(InstantiationStart) != BugFileID ||
   1069       SM.getFileID(InstantiationEnd) != BugFileID)
   1070     return;
   1071 
   1072   // Compute the column number of the end.
   1073   unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
   1074   unsigned OldEndColNo = EndColNo;
   1075 
   1076   if (EndColNo) {
   1077     // Add in the length of the token, so that we cover multi-char tokens.
   1078     EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
   1079   }
   1080 
   1081   // Highlight the range.  Make the span tag the outermost tag for the
   1082   // selected range.
   1083 
   1084   SourceLocation E =
   1085     InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
   1086 
   1087   html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
   1088 }
   1089 
   1090 StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
   1091   return R"<<<(
   1092 <script type='text/javascript'>
   1093 var digitMatcher = new RegExp("[0-9]+");
   1094 
   1095 var querySelectorAllArray = function(selector) {
   1096   return Array.prototype.slice.call(
   1097     document.querySelectorAll(selector));
   1098 }
   1099 
   1100 document.addEventListener("DOMContentLoaded", function() {
   1101     querySelectorAllArray(".PathNav > a").forEach(
   1102         function(currentValue, currentIndex) {
   1103             var hrefValue = currentValue.getAttribute("href");
   1104             currentValue.onclick = function() {
   1105                 scrollTo(document.querySelector(hrefValue));
   1106                 return false;
   1107             };
   1108         });
   1109 });
   1110 
   1111 var findNum = function() {
   1112     var s = document.querySelector(".selected");
   1113     if (!s || s.id == "EndPath") {
   1114         return 0;
   1115     }
   1116     var out = parseInt(digitMatcher.exec(s.id)[0]);
   1117     return out;
   1118 };
   1119 
   1120 var scrollTo = function(el) {
   1121     querySelectorAllArray(".selected").forEach(function(s) {
   1122         s.classList.remove("selected");
   1123     });
   1124     el.classList.add("selected");
   1125     window.scrollBy(0, el.getBoundingClientRect().top -
   1126         (window.innerHeight / 2));
   1127 }
   1128 
   1129 var move = function(num, up, numItems) {
   1130   if (num == 1 && up || num == numItems - 1 && !up) {
   1131     return 0;
   1132   } else if (num == 0 && up) {
   1133     return numItems - 1;
   1134   } else if (num == 0 && !up) {
   1135     return 1 % numItems;
   1136   }
   1137   return up ? num - 1 : num + 1;
   1138 }
   1139 
   1140 var numToId = function(num) {
   1141   if (num == 0) {
   1142     return document.getElementById("EndPath")
   1143   }
   1144   return document.getElementById("Path" + num);
   1145 };
   1146 
   1147 var navigateTo = function(up) {
   1148   var numItems = document.querySelectorAll(
   1149       ".line > .msgEvent, .line > .msgControl").length;
   1150   var currentSelected = findNum();
   1151   var newSelected = move(currentSelected, up, numItems);
   1152   var newEl = numToId(newSelected, numItems);
   1153 
   1154   // Scroll element into center.
   1155   scrollTo(newEl);
   1156 };
   1157 
   1158 window.addEventListener("keydown", function (event) {
   1159   if (event.defaultPrevented) {
   1160     return;
   1161   }
   1162   if (event.key == "j") {
   1163     navigateTo(/*up=*/false);
   1164   } else if (event.key == "k") {
   1165     navigateTo(/*up=*/true);
   1166   } else {
   1167     return;
   1168   }
   1169   event.preventDefault();
   1170 }, true);
   1171 </script>
   1172   )<<<";
   1173 }
   1174