clang API Documentation
00001 //===--- HTMLDiagnostics.cpp - HTML Diagnostics for Paths ----*- C++ -*-===// 00002 // 00003 // The LLVM Compiler Infrastructure 00004 // 00005 // This file is distributed under the University of Illinois Open Source 00006 // License. See LICENSE.TXT for details. 00007 // 00008 //===----------------------------------------------------------------------===// 00009 // 00010 // This file defines the HTMLDiagnostics object. 00011 // 00012 //===----------------------------------------------------------------------===// 00013 00014 #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" 00015 #include "clang/AST/ASTContext.h" 00016 #include "clang/AST/Decl.h" 00017 #include "clang/Basic/FileManager.h" 00018 #include "clang/Basic/SourceManager.h" 00019 #include "clang/Lex/Lexer.h" 00020 #include "clang/Lex/Preprocessor.h" 00021 #include "clang/Rewrite/Core/HTMLRewrite.h" 00022 #include "clang/Rewrite/Core/Rewriter.h" 00023 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 00024 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" 00025 #include "llvm/Support/FileSystem.h" 00026 #include "llvm/Support/MemoryBuffer.h" 00027 #include "llvm/Support/Path.h" 00028 #include "llvm/Support/raw_ostream.h" 00029 #include <sstream> 00030 00031 using namespace clang; 00032 using namespace ento; 00033 00034 //===----------------------------------------------------------------------===// 00035 // Boilerplate. 00036 //===----------------------------------------------------------------------===// 00037 00038 namespace { 00039 00040 class HTMLDiagnostics : public PathDiagnosticConsumer { 00041 std::string Directory; 00042 bool createdDir, noDir; 00043 const Preprocessor &PP; 00044 AnalyzerOptions &AnalyzerOpts; 00045 public: 00046 HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp); 00047 00048 virtual ~HTMLDiagnostics() { FlushDiagnostics(nullptr); } 00049 00050 void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, 00051 FilesMade *filesMade) override; 00052 00053 StringRef getName() const override { 00054 return "HTMLDiagnostics"; 00055 } 00056 00057 unsigned ProcessMacroPiece(raw_ostream &os, 00058 const PathDiagnosticMacroPiece& P, 00059 unsigned num); 00060 00061 void HandlePiece(Rewriter& R, FileID BugFileID, 00062 const PathDiagnosticPiece& P, unsigned num, unsigned max); 00063 00064 void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, 00065 const char *HighlightStart = "<span class=\"mrange\">", 00066 const char *HighlightEnd = "</span>"); 00067 00068 void ReportDiag(const PathDiagnostic& D, 00069 FilesMade *filesMade); 00070 }; 00071 00072 } // end anonymous namespace 00073 00074 HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, 00075 const std::string& prefix, 00076 const Preprocessor &pp) 00077 : Directory(prefix), createdDir(false), noDir(false), PP(pp), AnalyzerOpts(AnalyzerOpts) { 00078 } 00079 00080 void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, 00081 PathDiagnosticConsumers &C, 00082 const std::string& prefix, 00083 const Preprocessor &PP) { 00084 C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP)); 00085 } 00086 00087 //===----------------------------------------------------------------------===// 00088 // Report processing. 00089 //===----------------------------------------------------------------------===// 00090 00091 void HTMLDiagnostics::FlushDiagnosticsImpl( 00092 std::vector<const PathDiagnostic *> &Diags, 00093 FilesMade *filesMade) { 00094 for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(), 00095 et = Diags.end(); it != et; ++it) { 00096 ReportDiag(**it, filesMade); 00097 } 00098 } 00099 00100 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, 00101 FilesMade *filesMade) { 00102 00103 // Create the HTML directory if it is missing. 00104 if (!createdDir) { 00105 createdDir = true; 00106 if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { 00107 llvm::errs() << "warning: could not create directory '" 00108 << Directory << "': " << ec.message() << '\n'; 00109 00110 noDir = true; 00111 00112 return; 00113 } 00114 } 00115 00116 if (noDir) 00117 return; 00118 00119 // First flatten out the entire path to make it easier to use. 00120 PathPieces path = D.path.flatten(/*ShouldFlattenMacros=*/false); 00121 00122 // The path as already been prechecked that all parts of the path are 00123 // from the same file and that it is non-empty. 00124 const SourceManager &SMgr = (*path.begin())->getLocation().getManager(); 00125 assert(!path.empty()); 00126 FileID FID = 00127 (*path.begin())->getLocation().asLocation().getExpansionLoc().getFileID(); 00128 assert(!FID.isInvalid()); 00129 00130 // Create a new rewriter to generate HTML. 00131 Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); 00132 00133 // Get the function/method name 00134 SmallString<128> declName("unknown"); 00135 int offsetDecl = 0; 00136 if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { 00137 if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) { 00138 declName = ND->getDeclName().getAsString(); 00139 } 00140 00141 if (const Stmt *Body = DeclWithIssue->getBody()) { 00142 // Retrieve the relative position of the declaration which will be used 00143 // for the file name 00144 FullSourceLoc L( 00145 SMgr.getExpansionLoc((*path.rbegin())->getLocation().asLocation()), 00146 SMgr); 00147 FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr); 00148 offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); 00149 } 00150 } 00151 00152 // Process the path. 00153 unsigned n = path.size(); 00154 unsigned max = n; 00155 00156 for (PathPieces::const_reverse_iterator I = path.rbegin(), 00157 E = path.rend(); 00158 I != E; ++I, --n) 00159 HandlePiece(R, FID, **I, n, max); 00160 00161 // Add line numbers, header, footer, etc. 00162 00163 // unsigned FID = R.getSourceMgr().getMainFileID(); 00164 html::EscapeText(R, FID); 00165 html::AddLineNumbers(R, FID); 00166 00167 // If we have a preprocessor, relex the file and syntax highlight. 00168 // We might not have a preprocessor if we come from a deserialized AST file, 00169 // for example. 00170 00171 html::SyntaxHighlight(R, FID, PP); 00172 html::HighlightMacros(R, FID, PP); 00173 00174 // Get the full directory name of the analyzed file. 00175 00176 const FileEntry* Entry = SMgr.getFileEntryForID(FID); 00177 00178 // This is a cludge; basically we want to append either the full 00179 // working directory if we have no directory information. This is 00180 // a work in progress. 00181 00182 llvm::SmallString<0> DirName; 00183 00184 if (llvm::sys::path::is_relative(Entry->getName())) { 00185 llvm::sys::fs::current_path(DirName); 00186 DirName += '/'; 00187 } 00188 00189 int LineNumber = (*path.rbegin())->getLocation().asLocation().getExpansionLineNumber(); 00190 int ColumnNumber = (*path.rbegin())->getLocation().asLocation().getExpansionColumnNumber(); 00191 00192 // Add the name of the file as an <h1> tag. 00193 00194 { 00195 std::string s; 00196 llvm::raw_string_ostream os(s); 00197 00198 os << "<!-- REPORTHEADER -->\n" 00199 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" 00200 "<tr><td class=\"rowname\">File:</td><td>" 00201 << html::EscapeText(DirName) 00202 << html::EscapeText(Entry->getName()) 00203 << "</td></tr>\n<tr><td class=\"rowname\">Location:</td><td>" 00204 "<a href=\"#EndPath\">line " 00205 << LineNumber 00206 << ", column " 00207 << ColumnNumber 00208 << "</a></td></tr>\n" 00209 "<tr><td class=\"rowname\">Description:</td><td>" 00210 << D.getVerboseDescription() << "</td></tr>\n"; 00211 00212 // Output any other meta data. 00213 00214 for (PathDiagnostic::meta_iterator I=D.meta_begin(), E=D.meta_end(); 00215 I!=E; ++I) { 00216 os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; 00217 } 00218 00219 os << "</table>\n<!-- REPORTSUMMARYEXTRA -->\n" 00220 "<h3>Annotated Source Code</h3>\n"; 00221 00222 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 00223 } 00224 00225 // Embed meta-data tags. 00226 { 00227 std::string s; 00228 llvm::raw_string_ostream os(s); 00229 00230 StringRef BugDesc = D.getVerboseDescription(); 00231 if (!BugDesc.empty()) 00232 os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; 00233 00234 StringRef BugType = D.getBugType(); 00235 if (!BugType.empty()) 00236 os << "\n<!-- BUGTYPE " << BugType << " -->\n"; 00237 00238 StringRef BugCategory = D.getCategory(); 00239 if (!BugCategory.empty()) 00240 os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; 00241 00242 os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; 00243 00244 os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n"; 00245 00246 os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; 00247 00248 os << "\n<!-- BUGLINE " 00249 << LineNumber 00250 << " -->\n"; 00251 00252 os << "\n<!-- BUGCOLUMN " 00253 << ColumnNumber 00254 << " -->\n"; 00255 00256 os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n"; 00257 00258 // Mark the end of the tags. 00259 os << "\n<!-- BUGMETAEND -->\n"; 00260 00261 // Insert the text. 00262 R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); 00263 } 00264 00265 // Add CSS, header, and footer. 00266 00267 html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); 00268 00269 // Get the rewrite buffer. 00270 const RewriteBuffer *Buf = R.getRewriteBufferFor(FID); 00271 00272 if (!Buf) { 00273 llvm::errs() << "warning: no diagnostics generated for main file.\n"; 00274 return; 00275 } 00276 00277 // Create a path for the target HTML file. 00278 int FD; 00279 SmallString<128> Model, ResultPath; 00280 00281 if (!AnalyzerOpts.shouldWriteStableReportFilename()) { 00282 llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); 00283 00284 if (std::error_code EC = 00285 llvm::sys::fs::createUniqueFile(Model.str(), FD, ResultPath)) { 00286 llvm::errs() << "warning: could not create file in '" << Directory 00287 << "': " << EC.message() << '\n'; 00288 return; 00289 } 00290 00291 } else { 00292 int i = 1; 00293 std::error_code EC; 00294 do { 00295 // Find a filename which is not already used 00296 std::stringstream filename; 00297 Model = ""; 00298 filename << "report-" 00299 << llvm::sys::path::filename(Entry->getName()).str() 00300 << "-" << declName.c_str() 00301 << "-" << offsetDecl 00302 << "-" << i << ".html"; 00303 llvm::sys::path::append(Model, Directory, 00304 filename.str()); 00305 EC = llvm::sys::fs::openFileForWrite(Model.str(), 00306 FD, 00307 llvm::sys::fs::F_RW | 00308 llvm::sys::fs::F_Excl); 00309 if (EC && EC != std::errc::file_exists) { 00310 llvm::errs() << "warning: could not create file '" << Model.str() 00311 << "': " << EC.message() << '\n'; 00312 return; 00313 } 00314 i++; 00315 } while (EC); 00316 } 00317 00318 llvm::raw_fd_ostream os(FD, true); 00319 00320 if (filesMade) 00321 filesMade->addDiagnostic(D, getName(), 00322 llvm::sys::path::filename(ResultPath)); 00323 00324 // Emit the HTML to disk. 00325 for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I) 00326 os << *I; 00327 } 00328 00329 void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, 00330 const PathDiagnosticPiece& P, 00331 unsigned num, unsigned max) { 00332 00333 // For now, just draw a box above the line in question, and emit the 00334 // warning. 00335 FullSourceLoc Pos = P.getLocation().asLocation(); 00336 00337 if (!Pos.isValid()) 00338 return; 00339 00340 SourceManager &SM = R.getSourceMgr(); 00341 assert(&Pos.getManager() == &SM && "SourceManagers are different!"); 00342 std::pair<FileID, unsigned> LPosInfo = SM.getDecomposedExpansionLoc(Pos); 00343 00344 if (LPosInfo.first != BugFileID) 00345 return; 00346 00347 const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); 00348 const char* FileStart = Buf->getBufferStart(); 00349 00350 // Compute the column number. Rewind from the current position to the start 00351 // of the line. 00352 unsigned ColNo = SM.getColumnNumber(LPosInfo.first, LPosInfo.second); 00353 const char *TokInstantiationPtr =Pos.getExpansionLoc().getCharacterData(); 00354 const char *LineStart = TokInstantiationPtr-ColNo; 00355 00356 // Compute LineEnd. 00357 const char *LineEnd = TokInstantiationPtr; 00358 const char* FileEnd = Buf->getBufferEnd(); 00359 while (*LineEnd != '\n' && LineEnd != FileEnd) 00360 ++LineEnd; 00361 00362 // Compute the margin offset by counting tabs and non-tabs. 00363 unsigned PosNo = 0; 00364 for (const char* c = LineStart; c != TokInstantiationPtr; ++c) 00365 PosNo += *c == '\t' ? 8 : 1; 00366 00367 // Create the html for the message. 00368 00369 const char *Kind = nullptr; 00370 switch (P.getKind()) { 00371 case PathDiagnosticPiece::Call: 00372 llvm_unreachable("Calls should already be handled"); 00373 case PathDiagnosticPiece::Event: Kind = "Event"; break; 00374 case PathDiagnosticPiece::ControlFlow: Kind = "Control"; break; 00375 // Setting Kind to "Control" is intentional. 00376 case PathDiagnosticPiece::Macro: Kind = "Control"; break; 00377 } 00378 00379 std::string sbuf; 00380 llvm::raw_string_ostream os(sbuf); 00381 00382 os << "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\""; 00383 00384 if (num == max) 00385 os << "EndPath"; 00386 else 00387 os << "Path" << num; 00388 00389 os << "\" class=\"msg"; 00390 if (Kind) 00391 os << " msg" << Kind; 00392 os << "\" style=\"margin-left:" << PosNo << "ex"; 00393 00394 // Output a maximum size. 00395 if (!isa<PathDiagnosticMacroPiece>(P)) { 00396 // Get the string and determining its maximum substring. 00397 const std::string& Msg = P.getString(); 00398 unsigned max_token = 0; 00399 unsigned cnt = 0; 00400 unsigned len = Msg.size(); 00401 00402 for (std::string::const_iterator I=Msg.begin(), E=Msg.end(); I!=E; ++I) 00403 switch (*I) { 00404 default: 00405 ++cnt; 00406 continue; 00407 case ' ': 00408 case '\t': 00409 case '\n': 00410 if (cnt > max_token) max_token = cnt; 00411 cnt = 0; 00412 } 00413 00414 if (cnt > max_token) 00415 max_token = cnt; 00416 00417 // Determine the approximate size of the message bubble in em. 00418 unsigned em; 00419 const unsigned max_line = 120; 00420 00421 if (max_token >= max_line) 00422 em = max_token / 2; 00423 else { 00424 unsigned characters = max_line; 00425 unsigned lines = len / max_line; 00426 00427 if (lines > 0) { 00428 for (; characters > max_token; --characters) 00429 if (len / characters > lines) { 00430 ++characters; 00431 break; 00432 } 00433 } 00434 00435 em = characters / 2; 00436 } 00437 00438 if (em < max_line/2) 00439 os << "; max-width:" << em << "em"; 00440 } 00441 else 00442 os << "; max-width:100em"; 00443 00444 os << "\">"; 00445 00446 if (max > 1) { 00447 os << "<table class=\"msgT\"><tr><td valign=\"top\">"; 00448 os << "<div class=\"PathIndex"; 00449 if (Kind) os << " PathIndex" << Kind; 00450 os << "\">" << num << "</div>"; 00451 00452 if (num > 1) { 00453 os << "</td><td><div class=\"PathNav\"><a href=\"#Path" 00454 << (num - 1) 00455 << "\" title=\"Previous event (" 00456 << (num - 1) 00457 << ")\">←</a></div></td>"; 00458 } 00459 00460 os << "</td><td>"; 00461 } 00462 00463 if (const PathDiagnosticMacroPiece *MP = 00464 dyn_cast<PathDiagnosticMacroPiece>(&P)) { 00465 00466 os << "Within the expansion of the macro '"; 00467 00468 // Get the name of the macro by relexing it. 00469 { 00470 FullSourceLoc L = MP->getLocation().asLocation().getExpansionLoc(); 00471 assert(L.isFileID()); 00472 StringRef BufferInfo = L.getBufferData(); 00473 std::pair<FileID, unsigned> LocInfo = L.getDecomposedLoc(); 00474 const char* MacroName = LocInfo.second + BufferInfo.data(); 00475 Lexer rawLexer(SM.getLocForStartOfFile(LocInfo.first), PP.getLangOpts(), 00476 BufferInfo.begin(), MacroName, BufferInfo.end()); 00477 00478 Token TheTok; 00479 rawLexer.LexFromRawLexer(TheTok); 00480 for (unsigned i = 0, n = TheTok.getLength(); i < n; ++i) 00481 os << MacroName[i]; 00482 } 00483 00484 os << "':\n"; 00485 00486 if (max > 1) { 00487 os << "</td>"; 00488 if (num < max) { 00489 os << "<td><div class=\"PathNav\"><a href=\"#"; 00490 if (num == max - 1) 00491 os << "EndPath"; 00492 else 00493 os << "Path" << (num + 1); 00494 os << "\" title=\"Next event (" 00495 << (num + 1) 00496 << ")\">→</a></div></td>"; 00497 } 00498 00499 os << "</tr></table>"; 00500 } 00501 00502 // Within a macro piece. Write out each event. 00503 ProcessMacroPiece(os, *MP, 0); 00504 } 00505 else { 00506 os << html::EscapeText(P.getString()); 00507 00508 if (max > 1) { 00509 os << "</td>"; 00510 if (num < max) { 00511 os << "<td><div class=\"PathNav\"><a href=\"#"; 00512 if (num == max - 1) 00513 os << "EndPath"; 00514 else 00515 os << "Path" << (num + 1); 00516 os << "\" title=\"Next event (" 00517 << (num + 1) 00518 << ")\">→</a></div></td>"; 00519 } 00520 00521 os << "</tr></table>"; 00522 } 00523 } 00524 00525 os << "</div></td></tr>"; 00526 00527 // Insert the new html. 00528 unsigned DisplayPos = LineEnd - FileStart; 00529 SourceLocation Loc = 00530 SM.getLocForStartOfFile(LPosInfo.first).getLocWithOffset(DisplayPos); 00531 00532 R.InsertTextBefore(Loc, os.str()); 00533 00534 // Now highlight the ranges. 00535 ArrayRef<SourceRange> Ranges = P.getRanges(); 00536 for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), 00537 E = Ranges.end(); I != E; ++I) { 00538 HighlightRange(R, LPosInfo.first, *I); 00539 } 00540 } 00541 00542 static void EmitAlphaCounter(raw_ostream &os, unsigned n) { 00543 unsigned x = n % ('z' - 'a'); 00544 n /= 'z' - 'a'; 00545 00546 if (n > 0) 00547 EmitAlphaCounter(os, n); 00548 00549 os << char('a' + x); 00550 } 00551 00552 unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, 00553 const PathDiagnosticMacroPiece& P, 00554 unsigned num) { 00555 00556 for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); 00557 I!=E; ++I) { 00558 00559 if (const PathDiagnosticMacroPiece *MP = 00560 dyn_cast<PathDiagnosticMacroPiece>(*I)) { 00561 num = ProcessMacroPiece(os, *MP, num); 00562 continue; 00563 } 00564 00565 if (PathDiagnosticEventPiece *EP = dyn_cast<PathDiagnosticEventPiece>(*I)) { 00566 os << "<div class=\"msg msgEvent\" style=\"width:94%; " 00567 "margin-left:5px\">" 00568 "<table class=\"msgT\"><tr>" 00569 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">"; 00570 EmitAlphaCounter(os, num++); 00571 os << "</div></td><td valign=\"top\">" 00572 << html::EscapeText(EP->getString()) 00573 << "</td></tr></table></div>\n"; 00574 } 00575 } 00576 00577 return num; 00578 } 00579 00580 void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, 00581 SourceRange Range, 00582 const char *HighlightStart, 00583 const char *HighlightEnd) { 00584 SourceManager &SM = R.getSourceMgr(); 00585 const LangOptions &LangOpts = R.getLangOpts(); 00586 00587 SourceLocation InstantiationStart = SM.getExpansionLoc(Range.getBegin()); 00588 unsigned StartLineNo = SM.getExpansionLineNumber(InstantiationStart); 00589 00590 SourceLocation InstantiationEnd = SM.getExpansionLoc(Range.getEnd()); 00591 unsigned EndLineNo = SM.getExpansionLineNumber(InstantiationEnd); 00592 00593 if (EndLineNo < StartLineNo) 00594 return; 00595 00596 if (SM.getFileID(InstantiationStart) != BugFileID || 00597 SM.getFileID(InstantiationEnd) != BugFileID) 00598 return; 00599 00600 // Compute the column number of the end. 00601 unsigned EndColNo = SM.getExpansionColumnNumber(InstantiationEnd); 00602 unsigned OldEndColNo = EndColNo; 00603 00604 if (EndColNo) { 00605 // Add in the length of the token, so that we cover multi-char tokens. 00606 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM, LangOpts)-1; 00607 } 00608 00609 // Highlight the range. Make the span tag the outermost tag for the 00610 // selected range. 00611 00612 SourceLocation E = 00613 InstantiationEnd.getLocWithOffset(EndColNo - OldEndColNo); 00614 00615 html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd); 00616 }