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