Clang Project

clang_source_code/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
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
51using namespace clang;
52using namespace ento;
53
54//===----------------------------------------------------------------------===//
55// Boilerplate.
56//===----------------------------------------------------------------------===//
57
58namespace {
59
60class 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
68public:
69  HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts,
70                  const std::stringprefix,
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 PathDiagnosticMacroPieceP,
91                             unsigned num);
92
93  void HandlePiece(RewriterRFileID BugFileID,
94                   const PathDiagnosticPiecePunsigned numunsigned max);
95
96  void ", const char * HighlightEnd = "")" file_link="#_ZN12_GLOBAL__N_115HTMLDiagnostics14HighlightRangeERN5clang8RewriterENS1_6FileIDENS1_11SourceRangeEPKcS7_" func-decl-type="fun" qual_name="(anonymous namespace)::HTMLDiagnostics::HighlightRange" ref_name="_ZN12_GLOBAL__N_115HTMLDiagnostics14HighlightRangeERN5clang8RewriterENS1_6FileIDENS1_11SourceRangeEPKcS7_">HighlightRange(RewriterRFileID BugFileIDSourceRange Range,
97                      const char *HighlightStart = "<span class=\"mrange\">",
98                      const char *HighlightEnd = "</span>");
99
100  void ReportDiag(const PathDiagnosticD,
101                  FilesMade *filesMade);
102
103  // Generate the full HTML report
104  std::string GenerateHTML(const PathDiagnosticDRewriter &R,
105                           const SourceManagerSMgrconst PathPiecespath,
106                           const char *declName);
107
108  // Add HTML header/footers to file specified by FID
109  void FinalizeHTML(const PathDiagnosticDRewriter &R,
110                    const SourceManagerSMgrconst PathPiecespath,
111                    FileID FIDconst FileEntry *Entryconst char *declName);
112
113  // Rewrite the file specified by FID with HTML formatting.
114  void RewriteFile(Rewriter &Rconst PathPiecespathFileID FID);
115
116
117private:
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 &Dconst 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
136void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
137                                        PathDiagnosticConsumers &C,
138                                        const std::stringprefix,
139                                        const Preprocessor &PP) {
140  C.push_back(new HTMLDiagnostics(AnalyzerOptsprefixPPtrue));
141}
142
143void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
144                                                  PathDiagnosticConsumers &C,
145                                                  const std::stringprefix,
146                                                  const Preprocessor &PP) {
147  C.push_back(new HTMLDiagnostics(AnalyzerOptsprefixPPfalse));
148}
149
150//===----------------------------------------------------------------------===//
151// Report processing.
152//===----------------------------------------------------------------------===//
153
154void HTMLDiagnostics::FlushDiagnosticsImpl(
155  std::vector<const PathDiagnostic *> &Diags,
156  FilesMade *filesMade) {
157  for (const auto Diag : Diags)
158    ReportDiag(*DiagfilesMade);
159}
160
161void HTMLDiagnostics::ReportDiag(const PathDiagnosticD,
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<128declName("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<128ModelResultPath;
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 FileEntryEntry = 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
270std::string HTMLDiagnostics::GenerateHTML(const PathDiagnosticDRewriter &R,
271    const SourceManagerSMgrconst PathPiecespathconst char *declName) {
272  // Rewrite source files as HTML for every new file the path crosses
273  std::vector<FileIDFileIDs;
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(RpathFID);
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 != EI++) {
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 FileEntryEntry = SMgr.getFileEntryForID(FID);
333  FinalizeHTML(DRSMgrpathFileIDs[0], EntrydeclName);
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
343void 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
369std::string HTMLDiagnostics::showRelevantLinesJavascript(
370      const PathDiagnostic &Dconst 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
377var 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
401window.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
415document.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
434void HTMLDiagnostics::FinalizeHTML(const PathDiagnosticDRewriter &R,
435    const SourceManagerSMgrconst PathPiecespathFileID FID,
436    const FileEntry *Entryconst 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<0DirName;
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(Dpath));
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(RFIDEntry->getName());
578}
579
580StringRef HTMLDiagnostics::showHelpJavascript() {
581  return R"<<<(
582<script type='text/javascript'>
583
584var 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};
593window.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
608void HTMLDiagnostics::RewriteFile(Rewriter &R,
609                                  const PathPiecespathFileID 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(RFID, **INumNotePiecesTotalNotePieces);
629      --NumNotePieces;
630    } else {
631      HandlePiece(RFID, **INumRegularPiecesTotalRegularPieces);
632      --NumRegularPieces;
633    }
634  }
635
636  // Add line numbers, header, footer, etc.
637
638  html::EscapeText(RFID);
639  html::AddLineNumbers(RFID);
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(RFIDPP);
646  html::HighlightMacros(RFIDPP);
647}
648
649void HTMLDiagnostics::HandlePiece(RewriterRFileID BugFileID,
650                                  const PathDiagnosticPieceP,
651                                  unsigned numunsigned 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   (0) . __assert_fail ("&Pos.getManager() == &SM && \"SourceManagers are different!\"", "/home/seafit/code_projects/clang_source/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp", 660, __PRETTY_FUNCTION__))" file_link="../../../../include/assert.h.html#88" macro="true">assert(&Pos.getManager() == &SM && "SourceManagers are different!");
661  std::pair<FileIDunsignedLPosInfo = SM.getDecomposedExpansionLoc(Pos);
662
663  if (LPosInfo.first != BugFileID)
664    return;
665
666  const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first);
667  const charFileStart = 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.firstLPosInfo.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 charFileEnd = 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 charc = LineStartc != 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::ControlFlowKind = "Control"break;
696    // Setting Kind to "Control" is intentional.
697  case PathDiagnosticPiece::MacroKind = "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<FileIDunsignedLocInfo = L.getDecomposedLoc();
800      const charMacroName = 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<SourceRangeRanges = P.getRanges();
862  for (const auto &Range : Ranges)
863    HighlightRange(R, LPosInfo.first, Range);
864}
865
866static void EmitAlphaCounter(raw_ostream &osunsigned n) {
867  unsigned x = n % ('z' - 'a');
868  n /= 'z' - 'a';
869
870  if (n > 0)
871    EmitAlphaCounter(osn);
872
873  os << char('a' + x);
874}
875
876unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os,
877                                            const PathDiagnosticMacroPieceP,
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(osnum++);
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
900void HTMLDiagnostics::", const char * HighlightEnd = "")" func-decl-type="fun" id="_ZN12_GLOBAL__N_115HTMLDiagnostics14HighlightRangeERN5clang8RewriterENS1_6FileIDENS1_11SourceRangeEPKcS7_" qual_name="(anonymous namespace)::HTMLDiagnostics::HighlightRange" ref_name="_ZN12_GLOBAL__N_115HTMLDiagnostics14HighlightRangeERN5clang8RewriterENS1_6FileIDENS1_11SourceRangeEPKcS7_">HighlightRange(RewriterRFileID 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(), SMLangOpts)-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(RInstantiationStartEHighlightStartHighlightEnd);
936}
937
938StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
939  return R"<<<(
940<script type='text/javascript'>
941var digitMatcher = new RegExp("[0-9]+");
942
943document.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
954var 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
963var 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
972var 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
983var numToId = function(num) {
984  if (num == 0) {
985    return document.getElementById("EndPath")
986  }
987  return document.getElementById("Path" + num);
988};
989
990var 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
1001window.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