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