Coverage Report

Created: 2020-02-18 08:44

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