1 //===--- HTMLDiagnostics.cpp - HTML Diagnostics for Paths ----*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file defines the HTMLDiagnostics object.
12 //===----------------------------------------------------------------------===//
14 #include "clang/StaticAnalyzer/PathDiagnosticClients.h"
15 #include "clang/StaticAnalyzer/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"
29 using namespace clang
;
32 //===----------------------------------------------------------------------===//
34 //===----------------------------------------------------------------------===//
38 class HTMLDiagnostics
: public PathDiagnosticClient
{
39 llvm::sys::Path Directory
, FilePrefix
;
40 bool createdDir
, noDir
;
41 const Preprocessor
&PP
;
42 std::vector
<const PathDiagnostic
*> BatchedDiags
;
44 HTMLDiagnostics(const std::string
& prefix
, const Preprocessor
&pp
);
46 virtual ~HTMLDiagnostics() { FlushDiagnostics(NULL
); }
48 virtual void FlushDiagnostics(llvm::SmallVectorImpl
<std::string
> *FilesMade
);
50 virtual void HandlePathDiagnostic(const PathDiagnostic
* D
);
52 virtual llvm::StringRef
getName() const {
53 return "HTMLDiagnostics";
56 unsigned ProcessMacroPiece(llvm::raw_ostream
& os
,
57 const PathDiagnosticMacroPiece
& P
,
60 void HandlePiece(Rewriter
& R
, FileID BugFileID
,
61 const PathDiagnosticPiece
& P
, unsigned num
, unsigned max
);
63 void HighlightRange(Rewriter
& R
, FileID BugFileID
, SourceRange Range
,
64 const char *HighlightStart
= "<span class=\"mrange\">",
65 const char *HighlightEnd
= "</span>");
67 void ReportDiag(const PathDiagnostic
& D
,
68 llvm::SmallVectorImpl
<std::string
> *FilesMade
);
71 } // end anonymous namespace
73 HTMLDiagnostics::HTMLDiagnostics(const std::string
& prefix
,
74 const Preprocessor
&pp
)
75 : Directory(prefix
), FilePrefix(prefix
), createdDir(false), noDir(false),
77 // All html files begin with "report"
78 FilePrefix
.appendComponent("report");
82 ento::createHTMLDiagnosticClient(const std::string
& prefix
,
83 const Preprocessor
&PP
) {
84 return new HTMLDiagnostics(prefix
, PP
);
87 //===----------------------------------------------------------------------===//
89 //===----------------------------------------------------------------------===//
91 void HTMLDiagnostics::HandlePathDiagnostic(const PathDiagnostic
* D
) {
100 const_cast<PathDiagnostic
*>(D
)->flattenLocations();
101 BatchedDiags
.push_back(D
);
105 HTMLDiagnostics::FlushDiagnostics(llvm::SmallVectorImpl
<std::string
> *FilesMade
)
107 while (!BatchedDiags
.empty()) {
108 const PathDiagnostic
* D
= BatchedDiags
.back();
109 BatchedDiags
.pop_back();
110 ReportDiag(*D
, FilesMade
);
114 BatchedDiags
.clear();
117 void HTMLDiagnostics::ReportDiag(const PathDiagnostic
& D
,
118 llvm::SmallVectorImpl
<std::string
> *FilesMade
){
119 // Create the HTML directory if it is missing.
122 std::string ErrorMsg
;
123 Directory
.createDirectoryOnDisk(true, &ErrorMsg
);
126 if (llvm::sys::fs::is_directory(Directory
.str(), IsDirectory
) ||
128 llvm::errs() << "warning: could not create directory '"
129 << Directory
.str() << "'\n"
130 << "reason: " << ErrorMsg
<< '\n';
141 const SourceManager
&SMgr
= D
.begin()->getLocation().getManager();
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().getInstantiationLoc();
148 if (FID
.isInvalid()) {
149 FID
= SMgr
.getFileID(L
);
150 } else if (SMgr
.getFileID(L
) != FID
)
151 return; // FIXME: Emit a warning?
153 // Check the source ranges.
154 for (PathDiagnosticPiece::range_iterator RI
=I
->ranges_begin(),
155 RE
=I
->ranges_end(); RI
!=RE
; ++RI
) {
157 SourceLocation L
= SMgr
.getInstantiationLoc(RI
->getBegin());
159 if (!L
.isFileID() || SMgr
.getFileID(L
) != FID
)
160 return; // FIXME: Emit a warning?
162 L
= SMgr
.getInstantiationLoc(RI
->getEnd());
164 if (!L
.isFileID() || SMgr
.getFileID(L
) != FID
)
165 return; // FIXME: Emit a warning?
170 return; // FIXME: Emit a warning?
172 // Create a new rewriter to generate HTML.
173 Rewriter
R(const_cast<SourceManager
&>(SMgr
), PP
.getLangOptions());
176 unsigned n
= D
.size();
179 for (PathDiagnostic::const_reverse_iterator I
=D
.rbegin(), E
=D
.rend();
181 HandlePiece(R
, FID
, *I
, n
, max
);
183 // Add line numbers, header, footer, etc.
185 // unsigned FID = R.getSourceMgr().getMainFileID();
186 html::EscapeText(R
, FID
);
187 html::AddLineNumbers(R
, FID
);
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,
193 html::SyntaxHighlight(R
, FID
, PP
);
194 html::HighlightMacros(R
, FID
, PP
);
196 // Get the full directory name of the analyzed file.
198 const FileEntry
* Entry
= SMgr
.getFileEntryForID(FID
);
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.
204 std::string DirName
= "";
206 if (llvm::sys::path::is_relative(Entry
->getName())) {
207 llvm::sys::Path P
= llvm::sys::Path::GetCurrentDirectory();
208 DirName
= P
.str() + "/";
211 // Add the name of the file as an <h1> tag.
215 llvm::raw_string_ostream
os(s
);
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().getInstantiationLineNumber()
226 << (*D
.rbegin()).getLocation().asLocation().getInstantiationColumnNumber()
227 << "</a></td></tr>\n"
228 "<tr><td class=\"rowname\">Description:</td><td>"
229 << D
.getDescription() << "</td></tr>\n";
231 // Output any other meta data.
233 for (PathDiagnostic::meta_iterator I
=D
.meta_begin(), E
=D
.meta_end();
235 os
<< "<tr><td></td><td>" << html::EscapeText(*I
) << "</td></tr>\n";
238 os
<< "</table>\n<!-- REPORTSUMMARYEXTRA -->\n"
239 "<h3>Annotated Source Code</h3>\n";
241 R
.InsertTextBefore(SMgr
.getLocForStartOfFile(FID
), os
.str());
244 // Embed meta-data tags.
247 llvm::raw_string_ostream
os(s
);
249 const std::string
& BugDesc
= D
.getDescription();
250 if (!BugDesc
.empty())
251 os
<< "\n<!-- BUGDESC " << BugDesc
<< " -->\n";
253 const std::string
& BugType
= D
.getBugType();
254 if (!BugType
.empty())
255 os
<< "\n<!-- BUGTYPE " << BugType
<< " -->\n";
257 const std::string
& BugCategory
= D
.getCategory();
258 if (!BugCategory
.empty())
259 os
<< "\n<!-- BUGCATEGORY " << BugCategory
<< " -->\n";
261 os
<< "\n<!-- BUGFILE " << DirName
<< Entry
->getName() << " -->\n";
263 os
<< "\n<!-- BUGLINE "
264 << D
.back()->getLocation().asLocation().getInstantiationLineNumber()
267 os
<< "\n<!-- BUGPATHLENGTH " << D
.size() << " -->\n";
269 // Mark the end of the tags.
270 os
<< "\n<!-- BUGMETAEND -->\n";
273 R
.InsertTextBefore(SMgr
.getLocForStartOfFile(FID
), os
.str());
276 // Add CSS, header, and footer.
278 html::AddHeaderFooterInternalBuiltinCSS(R
, FID
, Entry
->getName());
280 // Get the rewrite buffer.
281 const RewriteBuffer
*Buf
= R
.getRewriteBufferFor(FID
);
284 llvm::errs() << "warning: no diagnostics generated for main file.\n";
288 // Create a path for the target HTML file.
289 llvm::sys::Path
F(FilePrefix
);
290 F
.makeUnique(false, NULL
);
292 // Rename the file with an HTML extension.
293 llvm::sys::Path
H(F
);
294 H
.appendSuffix("html");
295 F
.renamePathOnDisk(H
, NULL
);
297 std::string ErrorMsg
;
298 llvm::raw_fd_ostream
os(H
.c_str(), ErrorMsg
);
300 if (!ErrorMsg
.empty()) {
301 llvm::errs() << "warning: could not create file '" << F
.str()
307 FilesMade
->push_back(llvm::sys::path::filename(H
.str()));
309 // Emit the HTML to disk.
310 for (RewriteBuffer::iterator I
= Buf
->begin(), E
= Buf
->end(); I
!=E
; ++I
)
314 void HTMLDiagnostics::HandlePiece(Rewriter
& R
, FileID BugFileID
,
315 const PathDiagnosticPiece
& P
,
316 unsigned num
, unsigned max
) {
318 // For now, just draw a box above the line in question, and emit the
320 FullSourceLoc Pos
= P
.getLocation().asLocation();
325 SourceManager
&SM
= R
.getSourceMgr();
326 assert(&Pos
.getManager() == &SM
&& "SourceManagers are different!");
327 std::pair
<FileID
, unsigned> LPosInfo
= SM
.getDecomposedInstantiationLoc(Pos
);
329 if (LPosInfo
.first
!= BugFileID
)
332 const llvm::MemoryBuffer
*Buf
= SM
.getBuffer(LPosInfo
.first
);
333 const char* FileStart
= Buf
->getBufferStart();
335 // Compute the column number. Rewind from the current position to the start
337 unsigned ColNo
= SM
.getColumnNumber(LPosInfo
.first
, LPosInfo
.second
);
338 const char *TokInstantiationPtr
=Pos
.getInstantiationLoc().getCharacterData();
339 const char *LineStart
= TokInstantiationPtr
-ColNo
;
342 const char *LineEnd
= TokInstantiationPtr
;
343 const char* FileEnd
= Buf
->getBufferEnd();
344 while (*LineEnd
!= '\n' && LineEnd
!= FileEnd
)
347 // Compute the margin offset by counting tabs and non-tabs.
349 for (const char* c
= LineStart
; c
!= TokInstantiationPtr
; ++c
)
350 PosNo
+= *c
== '\t' ? 8 : 1;
352 // Create the html for the message.
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;
363 llvm::raw_string_ostream
os(sbuf
);
365 os
<< "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
372 os
<< "\" class=\"msg";
374 os
<< " msg" << Kind
;
375 os
<< "\" style=\"margin-left:" << PosNo
<< "ex";
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;
383 unsigned len
= Msg
.size();
385 for (std::string::const_iterator I
=Msg
.begin(), E
=Msg
.end(); I
!=E
; ++I
)
393 if (cnt
> max_token
) max_token
= cnt
;
400 // Determine the approximate size of the message bubble in em.
402 const unsigned max_line
= 120;
404 if (max_token
>= max_line
)
407 unsigned characters
= max_line
;
408 unsigned lines
= len
/ max_line
;
411 for (; characters
> max_token
; --characters
)
412 if (len
/ characters
> lines
) {
422 os
<< "; max-width:" << em
<< "em";
425 os
<< "; max-width:100em";
430 os
<< "<table class=\"msgT\"><tr><td valign=\"top\">";
431 os
<< "<div class=\"PathIndex";
432 if (Kind
) os
<< " PathIndex" << Kind
;
433 os
<< "\">" << num
<< "</div>";
437 if (const PathDiagnosticMacroPiece
*MP
=
438 dyn_cast
<PathDiagnosticMacroPiece
>(&P
)) {
440 os
<< "Within the expansion of the macro '";
442 // Get the name of the macro by relexing it.
444 FullSourceLoc L
= MP
->getLocation().asLocation().getInstantiationLoc();
445 assert(L
.isFileID());
446 llvm::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());
452 rawLexer
.LexFromRawLexer(TheTok
);
453 for (unsigned i
= 0, n
= TheTok
.getLength(); i
< n
; ++i
)
460 os
<< "</td></tr></table>";
462 // Within a macro piece. Write out each event.
463 ProcessMacroPiece(os
, *MP
, 0);
466 os
<< html::EscapeText(P
.getString());
469 os
<< "</td></tr></table>";
472 os
<< "</div></td></tr>";
474 // Insert the new html.
475 unsigned DisplayPos
= LineEnd
- FileStart
;
477 SM
.getLocForStartOfFile(LPosInfo
.first
).getFileLocWithOffset(DisplayPos
);
479 R
.InsertTextBefore(Loc
, os
.str());
481 // Now highlight the ranges.
482 for (const SourceRange
*I
= P
.ranges_begin(), *E
= P
.ranges_end();
484 HighlightRange(R
, LPosInfo
.first
, *I
);
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>");
497 if (Hint
->InsertionLoc
.isValid()) {
498 std::string EscapedCode
= html::EscapeText(Hint
->CodeToInsert
, true);
499 EscapedCode
= "<span class=\"CodeInsertionHint\">" + EscapedCode
501 R
.InsertTextBefore(Hint
->InsertionLoc
, EscapedCode
);
507 static void EmitAlphaCounter(llvm::raw_ostream
& os
, unsigned n
) {
508 unsigned x
= n
% ('z' - 'a');
512 EmitAlphaCounter(os
, n
);
517 unsigned HTMLDiagnostics::ProcessMacroPiece(llvm::raw_ostream
& os
,
518 const PathDiagnosticMacroPiece
& P
,
521 for (PathDiagnosticMacroPiece::const_iterator I
=P
.begin(), E
=P
.end();
524 if (const PathDiagnosticMacroPiece
*MP
=
525 dyn_cast
<PathDiagnosticMacroPiece
>(*I
)) {
526 num
= ProcessMacroPiece(os
, *MP
, num
);
530 if (PathDiagnosticEventPiece
*EP
= dyn_cast
<PathDiagnosticEventPiece
>(*I
)) {
531 os
<< "<div class=\"msg msgEvent\" style=\"width:94%; "
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";
545 void HTMLDiagnostics::HighlightRange(Rewriter
& R
, FileID BugFileID
,
547 const char *HighlightStart
,
548 const char *HighlightEnd
) {
549 SourceManager
&SM
= R
.getSourceMgr();
550 const LangOptions
&LangOpts
= R
.getLangOpts();
552 SourceLocation InstantiationStart
= SM
.getInstantiationLoc(Range
.getBegin());
553 unsigned StartLineNo
= SM
.getInstantiationLineNumber(InstantiationStart
);
555 SourceLocation InstantiationEnd
= SM
.getInstantiationLoc(Range
.getEnd());
556 unsigned EndLineNo
= SM
.getInstantiationLineNumber(InstantiationEnd
);
558 if (EndLineNo
< StartLineNo
)
561 if (SM
.getFileID(InstantiationStart
) != BugFileID
||
562 SM
.getFileID(InstantiationEnd
) != BugFileID
)
565 // Compute the column number of the end.
566 unsigned EndColNo
= SM
.getInstantiationColumnNumber(InstantiationEnd
);
567 unsigned OldEndColNo
= 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;
574 // Highlight the range. Make the span tag the outermost tag for the
578 InstantiationEnd
.getFileLocWithOffset(EndColNo
- OldEndColNo
);
580 html::HighlightRange(R
, InstantiationStart
, E
, HighlightStart
, HighlightEnd
);