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