1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
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 | |
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 | 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 PathDiagnosticMacroPiece& P, |
91 | unsigned num); |
92 | |
93 | void HandlePiece(Rewriter& R, FileID BugFileID, |
94 | const PathDiagnosticPiece& P, unsigned num, unsigned 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(Rewriter& R, FileID BugFileID, SourceRange Range, |
97 | const char *HighlightStart = "<span class=\"mrange\">", |
98 | const char *HighlightEnd = "</span>"); |
99 | |
100 | void ReportDiag(const PathDiagnostic& D, |
101 | FilesMade *filesMade); |
102 | |
103 | |
104 | std::string GenerateHTML(const PathDiagnostic& D, Rewriter &R, |
105 | const SourceManager& SMgr, const PathPieces& path, |
106 | const char *declName); |
107 | |
108 | |
109 | void FinalizeHTML(const PathDiagnostic& D, Rewriter &R, |
110 | const SourceManager& SMgr, const PathPieces& path, |
111 | FileID FID, const FileEntry *Entry, const char *declName); |
112 | |
113 | |
114 | void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID); |
115 | |
116 | |
117 | private: |
118 | |
119 | StringRef showHelpJavascript(); |
120 | |
121 | |
122 | StringRef generateKeyboardNavigationJavascript(); |
123 | |
124 | |
125 | std::string showRelevantLinesJavascript( |
126 | const PathDiagnostic &D, const PathPieces &path); |
127 | |
128 | |
129 | void dumpCoverageData(const PathDiagnostic &D, |
130 | const PathPieces &path, |
131 | llvm::raw_string_ostream &os); |
132 | }; |
133 | |
134 | } |
135 | |
136 | void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, |
137 | PathDiagnosticConsumers &C, |
138 | const std::string& prefix, |
139 | const Preprocessor &PP) { |
140 | C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); |
141 | } |
142 | |
143 | void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, |
144 | PathDiagnosticConsumers &C, |
145 | const std::string& prefix, |
146 | const Preprocessor &PP) { |
147 | C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); |
148 | } |
149 | |
150 | |
151 | |
152 | |
153 | |
154 | void HTMLDiagnostics::FlushDiagnosticsImpl( |
155 | std::vector<const PathDiagnostic *> &Diags, |
156 | FilesMade *filesMade) { |
157 | for (const auto Diag : Diags) |
158 | ReportDiag(*Diag, filesMade); |
159 | } |
160 | |
161 | void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, |
162 | FilesMade *filesMade) { |
163 | |
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 | |
178 | PathPieces path = D.path.flatten(); |
179 | |
180 | |
181 | assert(!path.empty()); |
182 | const SourceManager &SMgr = path.front()->getLocation().getManager(); |
183 | |
184 | |
185 | Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); |
186 | |
187 | |
188 | |
189 | |
190 | FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); |
191 | |
192 | |
193 | SmallString<128> declName("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 | |
201 | |
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 | |
217 | int FD; |
218 | SmallString<128> Model, ResultPath; |
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 | |
239 | const FileEntry* Entry = 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 | |
267 | os << report; |
268 | } |
269 | |
270 | std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, |
271 | const SourceManager& SMgr, const PathPieces& path, const char *declName) { |
272 | |
273 | std::vector<FileID> FileIDs; |
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(R, path, FID); |
281 | } |
282 | |
283 | if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) { |
284 | |
285 | for (auto I = FileIDs.begin(), E = FileIDs.end(); I != E; I++) { |
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 | |
295 | if (I != FileIDs.begin()) |
296 | os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue() |
297 | << "\">←</a></div>"; |
298 | |
299 | os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName() |
300 | << "</h4>\n"; |
301 | |
302 | |
303 | if (I + 1 != E) |
304 | os << "<div class=FileNav><a href=\"#File" << (I + 1)->getHashValue() |
305 | << "\">→</a></div>"; |
306 | |
307 | os << "</div>\n"; |
308 | |
309 | R.InsertTextBefore(SMgr.getLocForStartOfFile(*I), os.str()); |
310 | } |
311 | |
312 | |
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 | |
330 | FileID FID = |
331 | path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); |
332 | const FileEntry* Entry = SMgr.getFileEntryForID(FID); |
333 | FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName); |
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 | |
343 | void 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 | |
369 | std::string HTMLDiagnostics::showRelevantLinesJavascript( |
370 | const PathDiagnostic &D, const 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 | |
377 | var 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 | |
401 | window.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 | |
415 | document.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 | |
434 | void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, |
435 | const SourceManager& SMgr, const PathPieces& path, FileID FID, |
436 | const FileEntry *Entry, const char *declName) { |
437 | |
438 | |
439 | |
440 | |
441 | llvm::SmallString<0> DirName; |
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 | |
457 | R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), |
458 | showRelevantLinesJavascript(D, path)); |
459 | |
460 | |
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 | |
479 | unsigned = 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 | |
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 | |
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 | |
571 | os << "\n<!-- BUGMETAEND -->\n"; |
572 | |
573 | |
574 | R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); |
575 | } |
576 | |
577 | html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); |
578 | } |
579 | |
580 | StringRef HTMLDiagnostics::showHelpJavascript() { |
581 | return R"<<<( |
582 | <script type='text/javascript'> |
583 | |
584 | var 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 | }; |
593 | window.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 | |
608 | void HTMLDiagnostics::RewriteFile(Rewriter &R, |
609 | const PathPieces& path, FileID FID) { |
610 | |
611 | |
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 | |
626 | |
627 | |
628 | HandlePiece(R, FID, **I, NumNotePieces, TotalNotePieces); |
629 | --NumNotePieces; |
630 | } else { |
631 | HandlePiece(R, FID, **I, NumRegularPieces, TotalRegularPieces); |
632 | --NumRegularPieces; |
633 | } |
634 | } |
635 | |
636 | |
637 | |
638 | html::EscapeText(R, FID); |
639 | html::AddLineNumbers(R, FID); |
640 | |
641 | |
642 | |
643 | |
644 | |
645 | html::SyntaxHighlight(R, FID, PP); |
646 | html::HighlightMacros(R, FID, PP); |
647 | } |
648 | |
649 | void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, |
650 | const PathDiagnosticPiece& P, |
651 | unsigned num, unsigned max) { |
652 | |
653 | |
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<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); |
662 | |
663 | if (LPosInfo.first != BugFileID) |
664 | return; |
665 | |
666 | const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); |
667 | const char* FileStart = Buf->getBufferStart(); |
668 | |
669 | |
670 | |
671 | unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); |
672 | const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); |
673 | const char *LineStart = TokInstantiationPtr-ColNo; |
674 | |
675 | |
676 | const char *LineEnd = TokInstantiationPtr; |
677 | const char* FileEnd = Buf->getBufferEnd(); |
678 | while (*LineEnd != '\n' && LineEnd != FileEnd) |
679 | ++LineEnd; |
680 | |
681 | |
682 | unsigned PosNo = 0; |
683 | for (const char* c = LineStart; c != TokInstantiationPtr; ++c) |
684 | PosNo += *c == '\t' ? 8 : 1; |
685 | |
686 | |
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::ControlFlow: Kind = "Control"; break; |
696 | |
697 | case PathDiagnosticPiece::Macro: Kind = "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 | |
723 | if (!isa<PathDiagnosticMacroPiece>(P)) { |
724 | |
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 | |
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 | << ")\">←</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 | |
795 | { |
796 | FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); |
797 | assert(L.isFileID()); |
798 | StringRef BufferInfo = L.getBufferData(); |
799 | std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); |
800 | const char* MacroName = 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 | << ")\">→</a></div></td>"; |
823 | } |
824 | |
825 | os << "</tr></table>"; |
826 | } |
827 | |
828 | |
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 | << ")\">→</a></div></td>"; |
845 | } |
846 | |
847 | os << "</tr></table>"; |
848 | } |
849 | } |
850 | |
851 | os << "</div></td></tr>"; |
852 | |
853 | |
854 | unsigned DisplayPos = LineEnd - FileStart; |
855 | SourceLocation Loc = |
856 | SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); |
857 | |
858 | R.InsertTextBefore(Loc, os.str()); |
859 | |
860 | |
861 | ArrayRef<SourceRange> Ranges = P.getRanges(); |
862 | for (const auto &Range : Ranges) |
863 | HighlightRange(R, LPosInfo.first, Range); |
864 | } |
865 | |
866 | static void EmitAlphaCounter(raw_ostream &os, unsigned n) { |
867 | unsigned x = n % ('z' - 'a'); |
868 | n /= 'z' - 'a'; |
869 | |
870 | if (n > 0) |
871 | EmitAlphaCounter(os, n); |
872 | |
873 | os << char('a' + x); |
874 | } |
875 | |
876 | unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, |
877 | const PathDiagnosticMacroPiece& P, |
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(os, num++); |
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 | |
900 | void 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(Rewriter& R, FileID 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 | |
921 | unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); |
922 | unsigned OldEndColNo = EndColNo; |
923 | |
924 | if (EndColNo) { |
925 | |
926 | EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; |
927 | } |
928 | |
929 | |
930 | |
931 | |
932 | SourceLocation E = |
933 | InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); |
934 | |
935 | html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); |
936 | } |
937 | |
938 | StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { |
939 | return R"<<<( |
940 | <script type='text/javascript'> |
941 | var digitMatcher = new RegExp("[0-9]+"); |
942 | |
943 | document.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 | |
954 | var 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 | |
963 | var 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 | |
972 | var 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 | |
983 | var numToId = function(num) { |
984 | if (num == 0) { |
985 | return document.getElementById("EndPath") |
986 | } |
987 | return document.getElementById("Path" + num); |
988 | }; |
989 | |
990 | var 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 | |
1001 | window.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 | |