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