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