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/Basic/FileManager.h"
17 #include "clang/Basic/LLVM.h"
18 #include "clang/Basic/SourceLocation.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Lex/Lexer.h"
21 #include "clang/Lex/Preprocessor.h"
22 #include "clang/Lex/Token.h"
23 #include "clang/Rewrite/Core/HTMLRewrite.h"
24 #include "clang/Rewrite/Core/Rewriter.h"
25 #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
26 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
27 #include "clang/StaticAnalyzer/Core/IssueHash.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   std::string Directory;
62   bool createdDir = false;
63   bool noDir = false;
64   const Preprocessor &PP;
65   AnalyzerOptions &AnalyzerOpts;
66   const bool SupportsCrossFileDiagnostics;
67 
68 public:
69   HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
70                   const std::string& prefix,
71                   const Preprocessor &pp,
72                   bool supportsMultipleFiles)
73       : Directory(prefix), PP(pp), AnalyzerOpts(AnalyzerOpts),
74         SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
75 
76   ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); }
77 
78   void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
79                             FilesMade *filesMade) override;
80 
81   StringRef getName() const override {
82     return "HTMLDiagnostics";
83   }
84 
85   bool supportsCrossFileDiagnostics() const override {
86     return SupportsCrossFileDiagnostics;
87   }
88 
89   unsigned ProcessMacroPiece(raw_ostream &os,
90                              const PathDiagnosticMacroPiece& P,
91                              unsigned num);
92 
93   void HandlePiece(Rewriter& R, FileID BugFileID,
94                    const PathDiagnosticPiece& P, unsigned num, 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(AnalyzerOptions &AnalyzerOpts,
137                                         PathDiagnosticConsumers &C,
138                                         const std::string& prefix,
139                                         const Preprocessor &PP) {
140   C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true));
141 }
142 
143 void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
144                                                   PathDiagnosticConsumers &C,
145                                                   const std::string& prefix,
146                                                   const Preprocessor &PP) {
147   C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false));
148 }
149 
150 //===----------------------------------------------------------------------===//
151 // Report processing.
152 //===----------------------------------------------------------------------===//
153 
154 void HTMLDiagnostics::FlushDiagnosticsImpl(
155   std::vector<const PathDiagnostic *> &Diags,
156   FilesMade *filesMade) {
157   for (const auto Diag : Diags)
158     ReportDiag(*Diag, filesMade);
159 }
160 
161 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
162                                  FilesMade *filesMade) {
163   // Create the HTML directory if it is missing.
164   if (!createdDir) {
165     createdDir = true;
166     if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) {
167       llvm::errs() << "warning: could not create directory '"
168                    << Directory << "': " << ec.message() << '\n';
169       noDir = true;
170       return;
171     }
172   }
173 
174   if (noDir)
175     return;
176 
177   // First flatten out the entire path to make it easier to use.
178   PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false);
179 
180   // The path as already been prechecked that the path is non-empty.
181   assert(!path.empty());
182   const SourceManager &SMgr = path.front()->getLocation().getManager();
183 
184   // Create a new rewriter to generate HTML.
185   Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts());
186 
187   // The file for the first path element is considered the main report file, it
188   // will usually be equivalent to SMgr.getMainFileID(); however, it might be a
189   // header when -analyzer-opt-analyze-headers is used.
190   FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID();
191 
192   // Get the function/method name
193   SmallString<128> declName("unknown");
194   int offsetDecl = 0;
195   if (const Decl *DeclWithIssue = D.getDeclWithIssue()) {
196       if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue))
197           declName = ND->getDeclName().getAsString();
198 
199       if (const Stmt *Body = DeclWithIssue->getBody()) {
200           // Retrieve the relative position of the declaration which will be used
201           // for the file name
202           FullSourceLoc L(
203               SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),
204               SMgr);
205           FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);
206           offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
207       }
208   }
209 
210   std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str());
211   if (report.empty()) {
212     llvm::errs() << "warning: no diagnostics generated for main file.\n";
213     return;
214   }
215 
216   // Create a path for the target HTML file.
217   int FD;
218   SmallString<128> Model, ResultPath;
219 
220   if (!AnalyzerOpts.ShouldWriteStableReportFilename) {
221       llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
222       if (std::error_code EC =
223           llvm::sys::fs::make_absolute(Model)) {
224           llvm::errs() << "warning: could not make '" << Model
225                        << "' absolute: " << EC.message() << '\n';
226         return;
227       }
228       if (std::error_code EC =
229           llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) {
230           llvm::errs() << "warning: could not create file in '" << Directory
231                        << "': " << EC.message() << '\n';
232           return;
233       }
234   } else {
235       int i = 1;
236       std::error_code EC;
237       do {
238           // Find a filename which is not already used
239           const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile);
240           std::stringstream filename;
241           Model = "";
242           filename << "report-"
243                    << llvm::sys::path::filename(Entry->getName()).str()
244                    << "-" << declName.c_str()
245                    << "-" << offsetDecl
246                    << "-" << i << ".html";
247           llvm::sys::path::append(Model, Directory,
248                                   filename.str());
249           EC = llvm::sys::fs::openFileForReadWrite(
250               Model, FD, llvm::sys::fs::CD_CreateNew, llvm::sys::fs::OF_None);
251           if (EC && EC != llvm::errc::file_exists) {
252               llvm::errs() << "warning: could not create file '" << Model
253                            << "': " << EC.message() << '\n';
254               return;
255           }
256           i++;
257       } while (EC);
258   }
259 
260   llvm::raw_fd_ostream os(FD, true);
261 
262   if (filesMade)
263     filesMade->addDiagnostic(D, getName(),
264                              llvm::sys::path::filename(ResultPath));
265 
266   // Emit the HTML to disk.
267   os << report;
268 }
269 
270 std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
271     const SourceManager& SMgr, const PathPieces& path, const char *declName) {
272   // Rewrite source files as HTML for every new file the path crosses
273   std::vector<FileID> FileIDs;
274   for (auto I : path) {
275     FileID FID = I->getLocation().asLocation().getExpansionLoc().getFileID();
276     if (llvm::is_contained(FileIDs, FID))
277       continue;
278 
279     FileIDs.push_back(FID);
280     RewriteFile(R, path, FID);
281   }
282 
283   if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
284     // Prefix file names, anchor tags, and nav cursors to every file
285     for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) {
286       std::string s;
287       llvm::raw_string_ostream os(s);
288 
289       if (I != FileIDs.begin())
290         os << "<hr class=divider>\n";
291 
292       os << "<div id=File" << I->getHashValue() << ">\n";
293 
294       // Left nav arrow
295       if (I != FileIDs.begin())
296         os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue()
297            << "\">&#x2190;</a></div>";
298 
299       os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName()
300          << "</h4>\n";
301 
302       // Right nav arrow
303       if (I + 1 != E)
304         os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue()
305            << "\">&#x2192;</a></div>";
306 
307       os << "</div>\n";
308 
309       R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str());
310     }
311 
312     // Append files to the main report file in the order they appear in the path
313     for (auto I : llvm::make_range(FileIDs.begin() + 1, FileIDs.end())) {
314       std::string s;
315       llvm::raw_string_ostream os(s);
316 
317       const RewriteBuffer *Buf = R.getRewriteBufferFor(I);
318       for (auto BI : *Buf)
319         os << BI;
320 
321       R.InsertTextAfter(SMgr.getLocForEndOfFile(FileIDs[0]), os.str());
322     }
323   }
324 
325   const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]);
326   if (!Buf)
327     return {};
328 
329   // Add CSS, header, and footer.
330   FileID FID =
331       path.back()->getLocation().asLocation().getExpansionLoc().getFileID();
332   const FileEntry* Entry = SMgr.getFileEntryForID(FID);
333   FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName);
334 
335   std::string file;
336   llvm::raw_string_ostream os(file);
337   for (auto BI : *Buf)
338     os << BI;
339 
340   return os.str();
341 }
342 
343 void HTMLDiagnostics::dumpCoverageData(
344     const PathDiagnostic &D,
345     const PathPieces &path,
346     llvm::raw_string_ostream &os) {
347 
348   const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines();
349 
350   os << "var relevant_lines = {";
351   for (auto I = ExecutedLines.begin(),
352             E = ExecutedLines.end(); I != E; ++I) {
353     if (I != ExecutedLines.begin())
354       os << ", ";
355 
356     os << "\"" << I->first.getHashValue() << "\": {";
357     for (unsigned LineNo : I->second) {
358       if (LineNo != *(I->second.begin()))
359         os << ", ";
360 
361       os << "\"" << LineNo << "\": 1";
362     }
363     os << "}";
364   }
365 
366   os << "};";
367 }
368 
369 std::string HTMLDiagnostics::showRelevantLinesJavascript(
370       const PathDiagnostic &D, const PathPieces &path) {
371   std::string s;
372   llvm::raw_string_ostream os(s);
373   os << "<script type='text/javascript'>\n";
374   dumpCoverageData(D, path, os);
375   os << R"<<<(
376 
377 var filterCounterexample = function (hide) {
378   var tables = document.getElementsByClassName("code");
379   for (var t=0; t<tables.length; t++) {
380     var table = tables[t];
381     var file_id = table.getAttribute("data-fileid");
382     var lines_in_fid = relevant_lines[file_id];
383     if (!lines_in_fid) {
384       lines_in_fid = {};
385     }
386     var lines = table.getElementsByClassName("codeline");
387     for (var i=0; i<lines.length; i++) {
388         var el = lines[i];
389         var lineNo = el.getAttribute("data-linenumber");
390         if (!lines_in_fid[lineNo]) {
391           if (hide) {
392             el.setAttribute("hidden", "");
393           } else {
394             el.removeAttribute("hidden");
395           }
396         }
397     }
398   }
399 }
400 
401 window.addEventListener("keydown", function (event) {
402   if (event.defaultPrevented) {
403     return;
404   }
405   if (event.key == "S") {
406     var checked = document.getElementsByName("showCounterexample")[0].checked;
407     filterCounterexample(!checked);
408     document.getElementsByName("showCounterexample")[0].checked = !checked;
409   } else {
410     return;
411   }
412   event.preventDefault();
413 }, true);
414 
415 document.addEventListener("DOMContentLoaded", function() {
416     document.querySelector('input[name="showCounterexample"]').onchange=
417         function (event) {
418       filterCounterexample(this.checked);
419     };
420 });
421 </script>
422 
423 <form>
424     <input type="checkbox" name="showCounterexample" id="showCounterexample" />
425     <label for="showCounterexample">
426        Show only relevant lines
427     </label>
428 </form>
429 )<<<";
430 
431   return os.str();
432 }
433 
434 void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
435     const SourceManager& SMgr, const PathPieces& path, FileID FID,
436     const FileEntry *Entry, const char *declName) {
437   // This is a cludge; basically we want to append either the full
438   // working directory if we have no directory information.  This is
439   // a work in progress.
440 
441   llvm::SmallString<0> DirName;
442 
443   if (llvm::sys::path::is_relative(Entry->getName())) {
444     llvm::sys::fs::current_path(DirName);
445     DirName += '/';
446   }
447 
448   int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber();
449   int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber();
450 
451   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript());
452 
453   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
454                      generateKeyboardNavigationJavascript());
455 
456   // Checkbox and javascript for filtering the output to the counterexample.
457   R.InsertTextBefore(SMgr.getLocForStartOfFile(FID),
458                      showRelevantLinesJavascript(D, path));
459 
460   // Add the name of the file as an <h1> tag.
461   {
462     std::string s;
463     llvm::raw_string_ostream os(s);
464 
465     os << "<!-- REPORTHEADER -->\n"
466        << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
467           "<tr><td class=\"rowname\">File:</td><td>"
468        << html::EscapeText(DirName)
469        << html::EscapeText(Entry->getName())
470        << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>"
471           "<a href=\"#EndPath\">line "
472        << LineNumber
473        << ", column "
474        << ColumnNumber
475        << "</a><br />"
476        << D.getVerboseDescription() << "</td></tr>\n";
477 
478     // The navigation across the extra notes pieces.
479     unsigned NumExtraPieces = 0;
480     for (const auto &Piece : path) {
481       if (const auto *P = dyn_cast<PathDiagnosticNotePiece>(Piece.get())) {
482         int LineNumber =
483             P->getLocation().asLocation().getExpansionLineNumber();
484         int ColumnNumber =
485             P->getLocation().asLocation().getExpansionColumnNumber();
486         os << "<tr><td class=\"rowname\">Note:</td><td>"
487            << "<a href=\"#Note" << NumExtraPieces << "\">line "
488            << LineNumber << ", column " << ColumnNumber << "</a><br />"
489            << P->getString() << "</td></tr>";
490         ++NumExtraPieces;
491       }
492     }
493 
494     // Output any other meta data.
495 
496     for (PathDiagnostic::meta_iterator I = D.meta_begin(), E = D.meta_end();
497          I != E; ++I) {
498       os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n";
499     }
500 
501     os << R"<<<(
502 </table>
503 <!-- REPORTSUMMARYEXTRA -->
504 <h3>Annotated Source Code</h3>
505 <p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a>
506    to see keyboard shortcuts</p>
507 <input type="checkbox" class="spoilerhider" id="showinvocation" />
508 <label for="showinvocation" >Show analyzer invocation</label>
509 <div class="spoiler">clang -cc1 )<<<";
510     os << html::EscapeText(AnalyzerOpts.FullCompilerInvocation);
511     os << R"<<<(
512 </div>
513 <div id='tooltiphint' hidden="true">
514   <p>Keyboard shortcuts: </p>
515   <ul>
516     <li>Use 'j/k' keys for keyboard navigation</li>
517     <li>Use 'Shift+S' to show/hide relevant lines</li>
518     <li>Use '?' to toggle this window</li>
519   </ul>
520   <a href="#" onclick="toggleHelp(); return false;">Close</a>
521 </div>
522 )<<<";
523     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
524   }
525 
526   // Embed meta-data tags.
527   {
528     std::string s;
529     llvm::raw_string_ostream os(s);
530 
531     StringRef BugDesc = D.getVerboseDescription();
532     if (!BugDesc.empty())
533       os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
534 
535     StringRef BugType = D.getBugType();
536     if (!BugType.empty())
537       os << "\n<!-- BUGTYPE " << BugType << " -->\n";
538 
539     PathDiagnosticLocation UPDLoc = D.getUniqueingLoc();
540     FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid()
541                                              ? UPDLoc.asLocation()
542                                              : D.getLocation().asLocation()),
543                     SMgr);
544     const Decl *DeclWithIssue = D.getDeclWithIssue();
545 
546     StringRef BugCategory = D.getCategory();
547     if (!BugCategory.empty())
548       os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n";
549 
550     os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n";
551 
552     os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n";
553 
554     os  << "\n<!-- FUNCTIONNAME " <<  declName << " -->\n";
555 
556     os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT "
557        << GetIssueHash(SMgr, L, D.getCheckName(), D.getBugType(), DeclWithIssue,
558                        PP.getLangOpts()) << " -->\n";
559 
560     os << "\n<!-- BUGLINE "
561        << LineNumber
562        << " -->\n";
563 
564     os << "\n<!-- BUGCOLUMN "
565       << ColumnNumber
566       << " -->\n";
567 
568     os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n";
569 
570     // Mark the end of the tags.
571     os << "\n<!-- BUGMETAEND -->\n";
572 
573     // Insert the text.
574     R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str());
575   }
576 
577   html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
578 }
579 
580 StringRef HTMLDiagnostics::showHelpJavascript() {
581   return R"<<<(
582 <script type='text/javascript'>
583 
584 var toggleHelp = function() {
585     var hint = document.querySelector("#tooltiphint");
586     var attributeName = "hidden";
587     if (hint.hasAttribute(attributeName)) {
588       hint.removeAttribute(attributeName);
589     } else {
590       hint.setAttribute("hidden", "true");
591     }
592 };
593 window.addEventListener("keydown", function (event) {
594   if (event.defaultPrevented) {
595     return;
596   }
597   if (event.key == "?") {
598     toggleHelp();
599   } else {
600     return;
601   }
602   event.preventDefault();
603 });
604 </script>
605 )<<<";
606 }
607 
608 void HTMLDiagnostics::RewriteFile(Rewriter &R,
609                                   const PathPieces& path, FileID FID) {
610   // Process the path.
611   // Maintain the counts of extra note pieces separately.
612   unsigned TotalPieces = path.size();
613   unsigned TotalNotePieces =
614       std::count_if(path.begin(), path.end(),
615                     [](const std::shared_ptr<PathDiagnosticPiece> &p) {
616                       return isa<PathDiagnosticNotePiece>(*p);
617                     });
618 
619   unsigned TotalRegularPieces = TotalPieces - TotalNotePieces;
620   unsigned NumRegularPieces = TotalRegularPieces;
621   unsigned NumNotePieces = TotalNotePieces;
622 
623   for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) {
624     if (isa<PathDiagnosticNotePiece>(I->get())) {
625       // This adds diagnostic bubbles, but not navigation.
626       // Navigation through note pieces would be added later,
627       // as a separate pass through the piece list.
628       HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces);
629       --NumNotePieces;
630     } else {
631       HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces);
632       --NumRegularPieces;
633     }
634   }
635 
636   // Add line numbers, header, footer, etc.
637 
638   html::EscapeText(R, FID);
639   html::AddLineNumbers(R, FID);
640 
641   // If we have a preprocessor, relex the file and syntax highlight.
642   // We might not have a preprocessor if we come from a deserialized AST file,
643   // for example.
644 
645   html::SyntaxHighlight(R, FID, PP);
646   html::HighlightMacros(R, FID, PP);
647 }
648 
649 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID,
650                                   const PathDiagnosticPiece& P,
651                                   unsigned num, unsigned max) {
652   // For now, just draw a box above the line in question, and emit the
653   // warning.
654   FullSourceLoc Pos = P.getLocation().asLocation();
655 
656   if (!Pos.isValid())
657     return;
658 
659   SourceManager &SM = R.getSourceMgr();
660   assert(&Pos.getManager() == &SM && "SourceManagers are different!");
661   std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos);
662 
663   if (LPosInfo.first != BugFileID)
664     return;
665 
666   const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first);
667   const char* FileStart = Buf->getBufferStart();
668 
669   // Compute the column number.  Rewind from the current position to the start
670   // of the line.
671   unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second);
672   const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData();
673   const char *LineStart = TokInstantiationPtr-ColNo;
674 
675   // Compute LineEnd.
676   const char *LineEnd = TokInstantiationPtr;
677   const char* FileEnd = Buf->getBufferEnd();
678   while (*LineEnd != '\n' && LineEnd != FileEnd)
679     ++LineEnd;
680 
681   // Compute the margin offset by counting tabs and non-tabs.
682   unsigned PosNo = 0;
683   for (const char* c = LineStart; c != TokInstantiationPtr; ++c)
684     PosNo += *c == '\t' ? 8 : 1;
685 
686   // Create the html for the message.
687 
688   const char *Kind = nullptr;
689   bool IsNote = false;
690   bool SuppressIndex = (max == 1);
691   switch (P.getKind()) {
692   case PathDiagnosticPiece::Call:
693       llvm_unreachable("Calls and extra notes should already be handled");
694   case PathDiagnosticPiece::Event:  Kind = "Event"; break;
695   case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break;
696     // Setting Kind to "Control" is intentional.
697   case PathDiagnosticPiece::Macro: Kind = "Control"; break;
698   case PathDiagnosticPiece::Note:
699     Kind = "Note";
700     IsNote = true;
701     SuppressIndex = true;
702     break;
703   }
704 
705   std::string sbuf;
706   llvm::raw_string_ostream os(sbuf);
707 
708   os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
709 
710   if (IsNote)
711     os << "Note" << num;
712   else if (num == max)
713     os << "EndPath";
714   else
715     os << "Path" << num;
716 
717   os << "\" class=\"msg";
718   if (Kind)
719     os << " msg" << Kind;
720   os << "\" style=\"margin-left:" << PosNo << "ex";
721 
722   // Output a maximum size.
723   if (!isa<PathDiagnosticMacroPiece>(P)) {
724     // Get the string and determining its maximum substring.
725     const auto &Msg = P.getString();
726     unsigned max_token = 0;
727     unsigned cnt = 0;
728     unsigned len = Msg.size();
729 
730     for (char C : Msg)
731       switch (C) {
732       default:
733         ++cnt;
734         continue;
735       case ' ':
736       case '\t':
737       case '\n':
738         if (cnt > max_token) max_token = cnt;
739         cnt = 0;
740       }
741 
742     if (cnt > max_token)
743       max_token = cnt;
744 
745     // Determine the approximate size of the message bubble in em.
746     unsigned em;
747     const unsigned max_line = 120;
748 
749     if (max_token >= max_line)
750       em = max_token / 2;
751     else {
752       unsigned characters = max_line;
753       unsigned lines = len / max_line;
754 
755       if (lines > 0) {
756         for (; characters > max_token; --characters)
757           if (len / characters > lines) {
758             ++characters;
759             break;
760           }
761       }
762 
763       em = characters / 2;
764     }
765 
766     if (em < max_line/2)
767       os << "; max-width:" << em << "em";
768   }
769   else
770     os << "; max-width:100em";
771 
772   os << "\">";
773 
774   if (!SuppressIndex) {
775     os << "<table class=\"msgT\"><tr><td valign=\"top\">";
776     os << "<div class=\"PathIndex";
777     if (Kind) os << " PathIndex" << Kind;
778     os << "\">" << num << "</div>";
779 
780     if (num > 1) {
781       os << "</td><td><div class=\"PathNav\"><a href=\"#Path"
782          << (num - 1)
783          << "\" title=\"Previous event ("
784          << (num - 1)
785          << ")\">&#x2190;</a></div></td>";
786     }
787 
788     os << "</td><td>";
789   }
790 
791   if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) {
792     os << "Within the expansion of the macro '";
793 
794     // Get the name of the macro by relexing it.
795     {
796       FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc();
797       assert(L.isFileID());
798       StringRef BufferInfo = L.getBufferData();
799       std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc();
800       const char* MacroName = LocInfo.second + BufferInfo.data();
801       Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(),
802                      BufferInfo.begin(), MacroName, BufferInfo.end());
803 
804       Token TheTok;
805       rawLexer.LexFromRawLexer(TheTok);
806       for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i)
807         os << MacroName[i];
808     }
809 
810     os << "':\n";
811 
812     if (!SuppressIndex) {
813       os << "</td>";
814       if (num < max) {
815         os << "<td><div class=\"PathNav\"><a href=\"#";
816         if (num == max - 1)
817           os << "EndPath";
818         else
819           os << "Path" << (num + 1);
820         os << "\" title=\"Next event ("
821         << (num + 1)
822         << ")\">&#x2192;</a></div></td>";
823       }
824 
825       os << "</tr></table>";
826     }
827 
828     // Within a macro piece.  Write out each event.
829     ProcessMacroPiece(os, *MP, 0);
830   }
831   else {
832     os << html::EscapeText(P.getString());
833 
834     if (!SuppressIndex) {
835       os << "</td>";
836       if (num < max) {
837         os << "<td><div class=\"PathNav\"><a href=\"#";
838         if (num == max - 1)
839           os << "EndPath";
840         else
841           os << "Path" << (num + 1);
842         os << "\" title=\"Next event ("
843            << (num + 1)
844            << ")\">&#x2192;</a></div></td>";
845       }
846 
847       os << "</tr></table>";
848     }
849   }
850 
851   os << "</div></td></tr>";
852 
853   // Insert the new html.
854   unsigned DisplayPos = LineEnd - FileStart;
855   SourceLocation Loc =
856     SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos);
857 
858   R.InsertTextBefore(Loc, os.str());
859 
860   // Now highlight the ranges.
861   ArrayRef<SourceRange> Ranges = P.getRanges();
862   for (const auto &Range : Ranges)
863     HighlightRange(R, LPosInfo.first, Range);
864 }
865 
866 static void EmitAlphaCounter(raw_ostream &os, unsigned n) {
867   unsigned x = n % ('z' - 'a');
868   n /= 'z' - 'a';
869 
870   if (n > 0)
871     EmitAlphaCounter(os, n);
872 
873   os << char('a' + x);
874 }
875 
876 unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
877                                             const PathDiagnosticMacroPiece& P,
878                                             unsigned num) {
879   for (const auto &subPiece : P.subPieces) {
880     if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) {
881       num = ProcessMacroPiece(os, *MP, num);
882       continue;
883     }
884 
885     if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) {
886       os << "<div class=\"msg msgEvent\" style=\"width:94%; "
887             "margin-left:5px\">"
888             "<table class=\"msgT\"><tr>"
889             "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
890       EmitAlphaCounter(os, num++);
891       os << "</div></td><td valign=\"top\">"
892          << html::EscapeText(EP->getString())
893          << "</td></tr></table></div>\n";
894     }
895   }
896 
897   return num;
898 }
899 
900 void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
901                                      SourceRange Range,
902                                      const char *HighlightStart,
903                                      const char *HighlightEnd) {
904   SourceManager &SM = R.getSourceMgr();
905   const LangOptions &LangOpts = R.getLangOpts();
906 
907   SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin());
908   unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart);
909 
910   SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd());
911   unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd);
912 
913   if (EndLineNo < StartLineNo)
914     return;
915 
916   if (SM.getFileID(InstantiationStart) != BugFileID ||
917       SM.getFileID(InstantiationEnd) != BugFileID)
918     return;
919 
920   // Compute the column number of the end.
921   unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd);
922   unsigned OldEndColNo = EndColNo;
923 
924   if (EndColNo) {
925     // Add in the length of the token, so that we cover multi-char tokens.
926     EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1;
927   }
928 
929   // Highlight the range.  Make the span tag the outermost tag for the
930   // selected range.
931 
932   SourceLocation E =
933     InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo);
934 
935   html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
936 }
937 
938 StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
939   return R"<<<(
940 <script type='text/javascript'>
941 var digitMatcher = new RegExp("[0-9]+");
942 
943 document.addEventListener("DOMContentLoaded", function() {
944     document.querySelectorAll(".PathNav > a").forEach(
945         function(currentValue, currentIndex) {
946             var hrefValue = currentValue.getAttribute("href");
947             currentValue.onclick = function() {
948                 scrollTo(document.querySelector(hrefValue));
949                 return false;
950             };
951         });
952 });
953 
954 var findNum = function() {
955     var s = document.querySelector(".selected");
956     if (!s || s.id == "EndPath") {
957         return 0;
958     }
959     var out = parseInt(digitMatcher.exec(s.id)[0]);
960     return out;
961 };
962 
963 var scrollTo = function(el) {
964     document.querySelectorAll(".selected").forEach(function(s) {
965         s.classList.remove("selected");
966     });
967     el.classList.add("selected");
968     window.scrollBy(0, el.getBoundingClientRect().top -
969         (window.innerHeight / 2));
970 }
971 
972 var move = function(num, up, numItems) {
973   if (num == 1 && up || num == numItems - 1 && !up) {
974     return 0;
975   } else if (num == 0 && up) {
976     return numItems - 1;
977   } else if (num == 0 && !up) {
978     return 1 % numItems;
979   }
980   return up ? num - 1 : num + 1;
981 }
982 
983 var numToId = function(num) {
984   if (num == 0) {
985     return document.getElementById("EndPath")
986   }
987   return document.getElementById("Path" + num);
988 };
989 
990 var navigateTo = function(up) {
991   var numItems = document.querySelectorAll(
992       ".line > .msgEvent, .line > .msgControl").length;
993   var currentSelected = findNum();
994   var newSelected = move(currentSelected, up, numItems);
995   var newEl = numToId(newSelected, numItems);
996 
997   // Scroll element into center.
998   scrollTo(newEl);
999 };
1000 
1001 window.addEventListener("keydown", function (event) {
1002   if (event.defaultPrevented) {
1003     return;
1004   }
1005   if (event.key == "j") {
1006     navigateTo(/*up=*/false);
1007   } else if (event.key == "k") {
1008     navigateTo(/*up=*/true);
1009   } else {
1010     return;
1011   }
1012   event.preventDefault();
1013 }, true);
1014 </script>
1015   )<<<";
1016 }
1017