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