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 33using namespace clang; 34using namespace ento; 35 36//===----------------------------------------------------------------------===// 37// Boilerplate. 38//===----------------------------------------------------------------------===// 39 40namespace { 41 42class HTMLDiagnostics : public PathDiagnosticConsumer { 43 std::string Directory; 44 bool createdDir, noDir; 45 const Preprocessor &PP; 46 AnalyzerOptions &AnalyzerOpts; 47public: 48 HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp); 49 50 ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } 51 52 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 53 FilesMade *filesMade) override; 54 55 StringRef getName() const override { 56 return "HTMLDiagnostics"; 57 } 58 59 unsigned ProcessMacroPiece(raw_ostream &os, 60 const PathDiagnosticMacroPiece& P, 61 unsigned num); 62 63 void HandlePiece(Rewriter& R, FileID BugFileID, 64 const PathDiagnosticPiece& P, unsigned num, unsigned max); 65 66 void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, 67 const char *HighlightStart = "<span class=\"mrange\">", 68 const char *HighlightEnd = "</span>"); 69 70 void ReportDiag(const PathDiagnostic& D, 71 FilesMade *filesMade); 72}; 73 74} // end anonymous namespace 75 76HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, 77 const std::string& prefix, 78 const Preprocessor &pp) 79 : Directory(prefix), createdDir(false), noDir(false), PP(pp), AnalyzerOpts(AnalyzerOpts) { 80} 81 82void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 83 PathDiagnosticConsumers &C, 84 const std::string& prefix, 85 const Preprocessor &PP) { 86 C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP)); 87} 88 89//===----------------------------------------------------------------------===// 90// Report processing. 91//===----------------------------------------------------------------------===// 92 93void HTMLDiagnostics::FlushDiagnosticsImpl( 94 std::vector<const PathDiagnostic *> &Diags, 95 FilesMade *filesMade) { 96 for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(), 97 et = Diags.end(); it != et; ++it) { 98 ReportDiag(**it, filesMade); 99 } 100} 101 102void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, 103 FilesMade *filesMade) { 104 105 // Create the HTML directory if it is missing. 106 if (!createdDir) { 107 createdDir = true; 108 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { 109 llvm::errs() << "warning: could not create directory '" 110 << Directory << "': " << ec.message() << '\n'; 111 112 noDir = true; 113 114 return; 115 } 116 } 117 118 if (noDir) 119 return; 120 121 // First flatten out the entire path to make it easier to use. 122 PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false); 123 124 // The path as already been prechecked that all parts of the path are 125 // from the same file and that it is non-empty. 126 const SourceManager &SMgr = path.front()->getLocation().getManager(); 127 assert(!path.empty()); 128 FileID FID = 129 path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); 130 assert(FID.isValid()); 131 132 // Create a new rewriter to generate HTML. 133 Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); 134 135 // Get the function/method name 136 SmallString<128> declName("unknown"); 137 int offsetDecl = 0; 138 if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { 139 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { 140 declName = ND->getDeclName().getAsString(); 141 } 142 143 if (const Stmt *Body = DeclWithIssue->getBody()) { 144 // Retrieve the relative position of the declaration which will be used 145 // for the file name 146 FullSourceLoc L( 147 SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), 148 SMgr); 149 FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr); 150 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); 151 } 152 } 153 154 // Process the path. 155 unsigned n = path.size(); 156 unsigned max = n; 157 158 for (PathPieces::const_reverse_iterator I = path.rbegin(), 159 E = path.rend(); 160 I != E; ++I, --n) 161 HandlePiece(R, FID, **I, n, max); 162 163 // Add line numbers, header, footer, etc. 164 165 // unsigned FID = R.getSourceMgr().getMainFileID(); 166 html::EscapeText(R, FID); 167 html::AddLineNumbers(R, FID); 168 169 // If we have a preprocessor, relex the file and syntax highlight. 170 // We might not have a preprocessor if we come from a deserialized AST file, 171 // for example. 172 173 html::SyntaxHighlight(R, FID, PP); 174 html::HighlightMacros(R, FID, PP); 175 176 // Get the full directory name of the analyzed file. 177 178 const FileEntry* Entry = SMgr.getFileEntryForID(FID); 179 180 // This is a cludge; basically we want to append either the full 181 // working directory if we have no directory information. This is 182 // a work in progress. 183 184 llvm::SmallString<0> DirName; 185 186 if (llvm::sys::path::is_relative(Entry->getName())) { 187 llvm::sys::fs::current_path(DirName); 188 DirName += '/'; 189 } 190 191 int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); 192 int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); 193 194 // Add the name of the file as an <h1> tag. 195 196 { 197 std::string s; 198 llvm::raw_string_ostream os(s); 199 200 os << "<!-- REPORTHEADER -->\n" 201 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" 202 "<tr><td class=\"rowname\">File:</td><td>" 203 << html::EscapeText(DirName) 204 << html::EscapeText(Entry->getName()) 205 << "</td></tr>\n<tr><td class=\"rowname\">Location:</td><td>" 206 "<a href=\"#EndPath\">line " 207 << LineNumber 208 << ", column " 209 << ColumnNumber 210 << "</a></td></tr>\n" 211 "<tr><td class=\"rowname\">Description:</td><td>" 212 << D.getVerboseDescription() << "</td></tr>\n"; 213 214 // Output any other meta data. 215 216 for (PathDiagnostic::meta_iterator I=D.meta_begin(), E=D.meta_end(); 217 I!=E; ++I) { 218 os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; 219 } 220 221 os << "</table>\n<!-- REPORTSUMMARYEXTRA -->\n" 222 "<h3>Annotated Source Code</h3>\n"; 223 224 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 225 } 226 227 // Embed meta-data tags. 228 { 229 std::string s; 230 llvm::raw_string_ostream os(s); 231 232 StringRef BugDesc = D.getVerboseDescription(); 233 if (!BugDesc.empty()) 234 os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; 235 236 StringRef BugType = D.getBugType(); 237 if (!BugType.empty()) 238 os << "\n<!-- BUGTYPE " << BugType << " -->\n"; 239 240 PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); 241 FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() 242 ? UPDLoc.asLocation() 243 : D.getLocation().asLocation()), 244 SMgr); 245 const Decl *DeclWithIssue = D.getDeclWithIssue(); 246 247 StringRef BugCategory = D.getCategory(); 248 if (!BugCategory.empty()) 249 os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; 250 251 os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; 252 253 os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n"; 254 255 os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; 256 257 os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " 258 << GetIssueHash(SMgr, L, D.getCheckName(), D.getBugType(), DeclWithIssue, 259 PP.getLangOpts()) << " -->\n"; 260 261 os << "\n<!-- BUGLINE " 262 << LineNumber 263 << " -->\n"; 264 265 os << "\n<!-- BUGCOLUMN " 266 << ColumnNumber 267 << " -->\n"; 268 269 os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n"; 270 271 // Mark the end of the tags. 272 os << "\n<!-- BUGMETAEND -->\n"; 273 274 // Insert the text. 275 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 276 } 277 278 // Add CSS, header, and footer. 279 280 html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); 281 282 // Get the rewrite buffer. 283 const RewriteBuffer *Buf = R.getRewriteBufferFor(FID); 284 285 if (!Buf) { 286 llvm::errs() << "warning: no diagnostics generated for main file.\n"; 287 return; 288 } 289 290 // Create a path for the target HTML file. 291 int FD; 292 SmallString<128> Model, ResultPath; 293 294 if (!AnalyzerOpts.shouldWriteStableReportFilename()) { 295 llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); 296 if (std::error_code EC = 297 llvm::sys::fs::make_absolute(Model)) { 298 llvm::errs() << "warning: could not make '" << Model 299 << "' absolute: " << EC.message() << '\n'; 300 return; 301 } 302 if (std::error_code EC = 303 llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) { 304 llvm::errs() << "warning: could not create file in '" << Directory 305 << "': " << EC.message() << '\n'; 306 return; 307 } 308 309 } else { 310 int i = 1; 311 std::error_code EC; 312 do { 313 // Find a filename which is not already used 314 std::stringstream filename; 315 Model = ""; 316 filename << "report-" 317 << llvm::sys::path::filename(Entry->getName()).str() 318 << "-" << declName.c_str() 319 << "-" << offsetDecl 320 << "-" << i << ".html"; 321 llvm::sys::path::append(Model, Directory, 322 filename.str()); 323 EC = llvm::sys::fs::openFileForWrite(Model, 324 FD, 325 llvm::sys::fs::F_RW | 326 llvm::sys::fs::F_Excl); 327 if (EC && EC != llvm::errc::file_exists) { 328 llvm::errs() << "warning: could not create file '" << Model 329 << "': " << EC.message() << '\n'; 330 return; 331 } 332 i++; 333 } while (EC); 334 } 335 336 llvm::raw_fd_ostream os(FD, true); 337 338 if (filesMade) 339 filesMade->addDiagnostic(D, getName(), 340 llvm::sys::path::filename(ResultPath)); 341 342 // Emit the HTML to disk. 343 for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I) 344 os << *I; 345} 346 347void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, 348 const PathDiagnosticPiece& P, 349 unsigned num, unsigned max) { 350 351 // For now, just draw a box above the line in question, and emit the 352 // warning. 353 FullSourceLoc Pos = P.getLocation().asLocation(); 354 355 if (!Pos.isValid()) 356 return; 357 358 SourceManager &SM = R.getSourceMgr(); 359 assert(&Pos.getManager() == &SM && "SourceManagers are different!"); 360 std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); 361 362 if (LPosInfo.first != BugFileID) 363 return; 364 365 const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); 366 const char* FileStart = Buf->getBufferStart(); 367 368 // Compute the column number. Rewind from the current position to the start 369 // of the line. 370 unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); 371 const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); 372 const char *LineStart = TokInstantiationPtr-ColNo; 373 374 // Compute LineEnd. 375 const char *LineEnd = TokInstantiationPtr; 376 const char* FileEnd = Buf->getBufferEnd(); 377 while (*LineEnd != '\n' && LineEnd != FileEnd) 378 ++LineEnd; 379 380 // Compute the margin offset by counting tabs and non-tabs. 381 unsigned PosNo = 0; 382 for (const char* c = LineStart; c != TokInstantiationPtr; ++c) 383 PosNo += *c == '\t' ? 8 : 1; 384 385 // Create the html for the message. 386 387 const char *Kind = nullptr; 388 switch (P.getKind()) { 389 case PathDiagnosticPiece::Call: 390 llvm_unreachable("Calls should already be handled"); 391 case PathDiagnosticPiece::Event: Kind = "Event"; break; 392 case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break; 393 // Setting Kind to "Control" is intentional. 394 case PathDiagnosticPiece::Macro: Kind = "Control"; break; 395 } 396 397 std::string sbuf; 398 llvm::raw_string_ostream os(sbuf); 399 400 os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\""; 401 402 if (num == max) 403 os << "EndPath"; 404 else 405 os << "Path" << num; 406 407 os << "\" class=\"msg"; 408 if (Kind) 409 os << " msg" << Kind; 410 os << "\" style=\"margin-left:" << PosNo << "ex"; 411 412 // Output a maximum size. 413 if (!isa<PathDiagnosticMacroPiece>(P)) { 414 // Get the string and determining its maximum substring. 415 const auto &Msg = P.getString(); 416 unsigned max_token = 0; 417 unsigned cnt = 0; 418 unsigned len = Msg.size(); 419 420 for (char C : Msg) 421 switch (C) { 422 default: 423 ++cnt; 424 continue; 425 case ' ': 426 case '\t': 427 case '\n': 428 if (cnt > max_token) max_token = cnt; 429 cnt = 0; 430 } 431 432 if (cnt > max_token) 433 max_token = cnt; 434 435 // Determine the approximate size of the message bubble in em. 436 unsigned em; 437 const unsigned max_line = 120; 438 439 if (max_token >= max_line) 440 em = max_token / 2; 441 else { 442 unsigned characters = max_line; 443 unsigned lines = len / max_line; 444 445 if (lines > 0) { 446 for (; characters > max_token; --characters) 447 if (len / characters > lines) { 448 ++characters; 449 break; 450 } 451 } 452 453 em = characters / 2; 454 } 455 456 if (em < max_line/2) 457 os << "; max-width:" << em << "em"; 458 } 459 else 460 os << "; max-width:100em"; 461 462 os << "\">"; 463 464 if (max > 1) { 465 os << "<table class=\"msgT\"><tr><td valign=\"top\">"; 466 os << "<div class=\"PathIndex"; 467 if (Kind) os << " PathIndex" << Kind; 468 os << "\">" << num << "</div>"; 469 470 if (num > 1) { 471 os << "</td><td><div class=\"PathNav\"><a href=\"#Path" 472 << (num - 1) 473 << "\" title=\"Previous event (" 474 << (num - 1) 475 << ")\">←</a></div></td>"; 476 } 477 478 os << "</td><td>"; 479 } 480 481 if (const PathDiagnosticMacroPiece *MP = 482 dyn_cast<PathDiagnosticMacroPiece>(&P)) { 483 484 os << "Within the expansion of the macro '"; 485 486 // Get the name of the macro by relexing it. 487 { 488 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); 489 assert(L.isFileID()); 490 StringRef BufferInfo = L.getBufferData(); 491 std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); 492 const char* MacroName = LocInfo.second + BufferInfo.data(); 493 Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(), 494 BufferInfo.begin(), MacroName, BufferInfo.end()); 495 496 Token TheTok; 497 rawLexer.LexFromRawLexer(TheTok); 498 for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i) 499 os << MacroName[i]; 500 } 501 502 os << "':\n"; 503 504 if (max > 1) { 505 os << "</td>"; 506 if (num < max) { 507 os << "<td><div class=\"PathNav\"><a href=\"#"; 508 if (num == max - 1) 509 os << "EndPath"; 510 else 511 os << "Path" << (num + 1); 512 os << "\" title=\"Next event (" 513 << (num + 1) 514 << ")\">→</a></div></td>"; 515 } 516 517 os << "</tr></table>"; 518 } 519 520 // Within a macro piece. Write out each event. 521 ProcessMacroPiece(os, *MP, 0); 522 } 523 else { 524 os << html::EscapeText(P.getString()); 525 526 if (max > 1) { 527 os << "</td>"; 528 if (num < max) { 529 os << "<td><div class=\"PathNav\"><a href=\"#"; 530 if (num == max - 1) 531 os << "EndPath"; 532 else 533 os << "Path" << (num + 1); 534 os << "\" title=\"Next event (" 535 << (num + 1) 536 << ")\">→</a></div></td>"; 537 } 538 539 os << "</tr></table>"; 540 } 541 } 542 543 os << "</div></td></tr>"; 544 545 // Insert the new html. 546 unsigned DisplayPos = LineEnd - FileStart; 547 SourceLocation Loc = 548 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); 549 550 R.InsertTextBefore(Loc, os.str()); 551 552 // Now highlight the ranges. 553 ArrayRef<SourceRange> Ranges = P.getRanges(); 554 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), 555 E = Ranges.end(); I != E; ++I) { 556 HighlightRange(R, LPosInfo.first, *I); 557 } 558} 559 560static void EmitAlphaCounter(raw_ostream &os, unsigned n) { 561 unsigned x = n % ('z' - 'a'); 562 n /= 'z' - 'a'; 563 564 if (n > 0) 565 EmitAlphaCounter(os, n); 566 567 os << char('a' + x); 568} 569 570unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, 571 const PathDiagnosticMacroPiece& P, 572 unsigned num) { 573 574 for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); 575 I!=E; ++I) { 576 577 if (const PathDiagnosticMacroPiece *MP = 578 dyn_cast<PathDiagnosticMacroPiece>(*I)) { 579 num = ProcessMacroPiece(os, *MP, num); 580 continue; 581 } 582 583 if (PathDiagnosticEventPiece *EP = dyn_cast<PathDiagnosticEventPiece>(*I)) { 584 os << "<div class=\"msg msgEvent\" style=\"width:94%; " 585 "margin-left:5px\">" 586 "<table class=\"msgT\"><tr>" 587 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">"; 588 EmitAlphaCounter(os, num++); 589 os << "</div></td><td valign=\"top\">" 590 << html::EscapeText(EP->getString()) 591 << "</td></tr></table></div>\n"; 592 } 593 } 594 595 return num; 596} 597 598void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, 599 SourceRange Range, 600 const char *HighlightStart, 601 const char *HighlightEnd) { 602 SourceManager &SM = R.getSourceMgr(); 603 const LangOptions &LangOpts = R.getLangOpts(); 604 605 SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin()); 606 unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart); 607 608 SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd()); 609 unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd); 610 611 if (EndLineNo < StartLineNo) 612 return; 613 614 if (SM.getFileID(InstantiationStart) != BugFileID || 615 SM.getFileID(InstantiationEnd) != BugFileID) 616 return; 617 618 // Compute the column number of the end. 619 unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); 620 unsigned OldEndColNo = EndColNo; 621 622 if (EndColNo) { 623 // Add in the length of the token, so that we cover multi-char tokens. 624 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; 625 } 626 627 // Highlight the range. Make the span tag the outermost tag for the 628 // selected range. 629 630 SourceLocation E = 631 InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); 632 633 html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); 634} 635