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