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