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