Coverage Report

Created: 2019-07-24 05:18

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