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