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