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