]> CyberLeo.Net >> Repos - FreeBSD/stable/9.git/blob - contrib/llvm/tools/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
MFC r234353:
[FreeBSD/stable/9.git] / contrib / llvm / tools / clang / lib / StaticAnalyzer / Core / HTMLDiagnostics.cpp
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 }