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/GR/PathDiagnosticClients.h"
15 #include "clang/GR/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/MemoryBuffer.h"
25 #include "llvm/Support/raw_ostream.h"
26 #include "llvm/Support/Path.h"
28 using namespace clang
;
30 //===----------------------------------------------------------------------===//
32 //===----------------------------------------------------------------------===//
36 class HTMLDiagnostics
: public PathDiagnosticClient
{
37 llvm::sys::Path Directory
, FilePrefix
;
38 bool createdDir
, noDir
;
39 const Preprocessor
&PP
;
40 std::vector
<const PathDiagnostic
*> BatchedDiags
;
42 HTMLDiagnostics(const std::string
& prefix
, const Preprocessor
&pp
);
44 virtual ~HTMLDiagnostics() { FlushDiagnostics(NULL
); }
46 virtual void FlushDiagnostics(llvm::SmallVectorImpl
<std::string
> *FilesMade
);
48 virtual void HandlePathDiagnostic(const PathDiagnostic
* D
);
50 virtual llvm::StringRef
getName() const {
51 return "HTMLDiagnostics";
54 unsigned ProcessMacroPiece(llvm::raw_ostream
& os
,
55 const PathDiagnosticMacroPiece
& P
,
58 void HandlePiece(Rewriter
& R
, FileID BugFileID
,
59 const PathDiagnosticPiece
& P
, unsigned num
, unsigned max
);
61 void HighlightRange(Rewriter
& R
, FileID BugFileID
, SourceRange Range
,
62 const char *HighlightStart
= "<span class=\"mrange\">",
63 const char *HighlightEnd
= "</span>");
65 void ReportDiag(const PathDiagnostic
& D
,
66 llvm::SmallVectorImpl
<std::string
> *FilesMade
);
69 } // end anonymous namespace
71 HTMLDiagnostics::HTMLDiagnostics(const std::string
& prefix
,
72 const Preprocessor
&pp
)
73 : Directory(prefix
), FilePrefix(prefix
), createdDir(false), noDir(false),
75 // All html files begin with "report"
76 FilePrefix
.appendComponent("report");
80 clang::createHTMLDiagnosticClient(const std::string
& prefix
,
81 const Preprocessor
&PP
) {
82 return new HTMLDiagnostics(prefix
, PP
);
85 //===----------------------------------------------------------------------===//
87 //===----------------------------------------------------------------------===//
89 void HTMLDiagnostics::HandlePathDiagnostic(const PathDiagnostic
* D
) {
98 const_cast<PathDiagnostic
*>(D
)->flattenLocations();
99 BatchedDiags
.push_back(D
);
103 HTMLDiagnostics::FlushDiagnostics(llvm::SmallVectorImpl
<std::string
> *FilesMade
)
105 while (!BatchedDiags
.empty()) {
106 const PathDiagnostic
* D
= BatchedDiags
.back();
107 BatchedDiags
.pop_back();
108 ReportDiag(*D
, FilesMade
);
112 BatchedDiags
.clear();
115 void HTMLDiagnostics::ReportDiag(const PathDiagnostic
& D
,
116 llvm::SmallVectorImpl
<std::string
> *FilesMade
){
117 // Create the HTML directory if it is missing.
120 std::string ErrorMsg
;
121 Directory
.createDirectoryOnDisk(true, &ErrorMsg
);
123 if (!Directory
.isDirectory()) {
124 llvm::errs() << "warning: could not create directory '"
125 << Directory
.str() << "'\n"
126 << "reason: " << ErrorMsg
<< '\n';
137 const SourceManager
&SMgr
= D
.begin()->getLocation().getManager();
140 // Verify that the entire path is from the same FileID.
141 for (PathDiagnostic::const_iterator I
= D
.begin(), E
= D
.end(); I
!= E
; ++I
) {
142 FullSourceLoc L
= I
->getLocation().asLocation().getInstantiationLoc();
144 if (FID
.isInvalid()) {
145 FID
= SMgr
.getFileID(L
);
146 } else if (SMgr
.getFileID(L
) != FID
)
147 return; // FIXME: Emit a warning?
149 // Check the source ranges.
150 for (PathDiagnosticPiece::range_iterator RI
=I
->ranges_begin(),
151 RE
=I
->ranges_end(); RI
!=RE
; ++RI
) {
153 SourceLocation L
= SMgr
.getInstantiationLoc(RI
->getBegin());
155 if (!L
.isFileID() || SMgr
.getFileID(L
) != FID
)
156 return; // FIXME: Emit a warning?
158 L
= SMgr
.getInstantiationLoc(RI
->getEnd());
160 if (!L
.isFileID() || SMgr
.getFileID(L
) != FID
)
161 return; // FIXME: Emit a warning?
166 return; // FIXME: Emit a warning?
168 // Create a new rewriter to generate HTML.
169 Rewriter
R(const_cast<SourceManager
&>(SMgr
), PP
.getLangOptions());
172 unsigned n
= D
.size();
175 for (PathDiagnostic::const_reverse_iterator I
=D
.rbegin(), E
=D
.rend();
177 HandlePiece(R
, FID
, *I
, n
, max
);
179 // Add line numbers, header, footer, etc.
181 // unsigned FID = R.getSourceMgr().getMainFileID();
182 html::EscapeText(R
, FID
);
183 html::AddLineNumbers(R
, FID
);
185 // If we have a preprocessor, relex the file and syntax highlight.
186 // We might not have a preprocessor if we come from a deserialized AST file,
189 html::SyntaxHighlight(R
, FID
, PP
);
190 html::HighlightMacros(R
, FID
, PP
);
192 // Get the full directory name of the analyzed file.
194 const FileEntry
* Entry
= SMgr
.getFileEntryForID(FID
);
196 // This is a cludge; basically we want to append either the full
197 // working directory if we have no directory information. This is
198 // a work in progress.
200 std::string DirName
= "";
202 if (llvm::sys::path::is_relative(Entry
->getName())) {
203 llvm::sys::Path P
= llvm::sys::Path::GetCurrentDirectory();
204 DirName
= P
.str() + "/";
207 // Add the name of the file as an <h1> tag.
211 llvm::raw_string_ostream
os(s
);
213 os
<< "<!-- REPORTHEADER -->\n"
214 << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
215 "<tr><td class=\"rowname\">File:</td><td>"
216 << html::EscapeText(DirName
)
217 << html::EscapeText(Entry
->getName())
218 << "</td></tr>\n<tr><td class=\"rowname\">Location:</td><td>"
219 "<a href=\"#EndPath\">line "
220 << (*D
.rbegin()).getLocation().asLocation().getInstantiationLineNumber()
222 << (*D
.rbegin()).getLocation().asLocation().getInstantiationColumnNumber()
223 << "</a></td></tr>\n"
224 "<tr><td class=\"rowname\">Description:</td><td>"
225 << D
.getDescription() << "</td></tr>\n";
227 // Output any other meta data.
229 for (PathDiagnostic::meta_iterator I
=D
.meta_begin(), E
=D
.meta_end();
231 os
<< "<tr><td></td><td>" << html::EscapeText(*I
) << "</td></tr>\n";
234 os
<< "</table>\n<!-- REPORTSUMMARYEXTRA -->\n"
235 "<h3>Annotated Source Code</h3>\n";
237 R
.InsertTextBefore(SMgr
.getLocForStartOfFile(FID
), os
.str());
240 // Embed meta-data tags.
243 llvm::raw_string_ostream
os(s
);
245 const std::string
& BugDesc
= D
.getDescription();
246 if (!BugDesc
.empty())
247 os
<< "\n<!-- BUGDESC " << BugDesc
<< " -->\n";
249 const std::string
& BugType
= D
.getBugType();
250 if (!BugType
.empty())
251 os
<< "\n<!-- BUGTYPE " << BugType
<< " -->\n";
253 const std::string
& BugCategory
= D
.getCategory();
254 if (!BugCategory
.empty())
255 os
<< "\n<!-- BUGCATEGORY " << BugCategory
<< " -->\n";
257 os
<< "\n<!-- BUGFILE " << DirName
<< Entry
->getName() << " -->\n";
259 os
<< "\n<!-- BUGLINE "
260 << D
.back()->getLocation().asLocation().getInstantiationLineNumber()
263 os
<< "\n<!-- BUGPATHLENGTH " << D
.size() << " -->\n";
265 // Mark the end of the tags.
266 os
<< "\n<!-- BUGMETAEND -->\n";
269 R
.InsertTextBefore(SMgr
.getLocForStartOfFile(FID
), os
.str());
272 // Add CSS, header, and footer.
274 html::AddHeaderFooterInternalBuiltinCSS(R
, FID
, Entry
->getName());
276 // Get the rewrite buffer.
277 const RewriteBuffer
*Buf
= R
.getRewriteBufferFor(FID
);
280 llvm::errs() << "warning: no diagnostics generated for main file.\n";
284 // Create a path for the target HTML file.
285 llvm::sys::Path
F(FilePrefix
);
286 F
.makeUnique(false, NULL
);
288 // Rename the file with an HTML extension.
289 llvm::sys::Path
H(F
);
290 H
.appendSuffix("html");
291 F
.renamePathOnDisk(H
, NULL
);
293 std::string ErrorMsg
;
294 llvm::raw_fd_ostream
os(H
.c_str(), ErrorMsg
);
296 if (!ErrorMsg
.empty()) {
297 llvm::errs() << "warning: could not create file '" << F
.str()
303 FilesMade
->push_back(llvm::sys::path::filename(H
.str()));
305 // Emit the HTML to disk.
306 for (RewriteBuffer::iterator I
= Buf
->begin(), E
= Buf
->end(); I
!=E
; ++I
)
310 void HTMLDiagnostics::HandlePiece(Rewriter
& R
, FileID BugFileID
,
311 const PathDiagnosticPiece
& P
,
312 unsigned num
, unsigned max
) {
314 // For now, just draw a box above the line in question, and emit the
316 FullSourceLoc Pos
= P
.getLocation().asLocation();
321 SourceManager
&SM
= R
.getSourceMgr();
322 assert(&Pos
.getManager() == &SM
&& "SourceManagers are different!");
323 std::pair
<FileID
, unsigned> LPosInfo
= SM
.getDecomposedInstantiationLoc(Pos
);
325 if (LPosInfo
.first
!= BugFileID
)
328 const llvm::MemoryBuffer
*Buf
= SM
.getBuffer(LPosInfo
.first
);
329 const char* FileStart
= Buf
->getBufferStart();
331 // Compute the column number. Rewind from the current position to the start
333 unsigned ColNo
= SM
.getColumnNumber(LPosInfo
.first
, LPosInfo
.second
);
334 const char *TokInstantiationPtr
=Pos
.getInstantiationLoc().getCharacterData();
335 const char *LineStart
= TokInstantiationPtr
-ColNo
;
338 const char *LineEnd
= TokInstantiationPtr
;
339 const char* FileEnd
= Buf
->getBufferEnd();
340 while (*LineEnd
!= '\n' && LineEnd
!= FileEnd
)
343 // Compute the margin offset by counting tabs and non-tabs.
345 for (const char* c
= LineStart
; c
!= TokInstantiationPtr
; ++c
)
346 PosNo
+= *c
== '\t' ? 8 : 1;
348 // Create the html for the message.
350 const char *Kind
= 0;
351 switch (P
.getKind()) {
352 case PathDiagnosticPiece::Event
: Kind
= "Event"; break;
353 case PathDiagnosticPiece::ControlFlow
: Kind
= "Control"; break;
354 // Setting Kind to "Control" is intentional.
355 case PathDiagnosticPiece::Macro
: Kind
= "Control"; break;
359 llvm::raw_string_ostream
os(sbuf
);
361 os
<< "\n<tr><td class=\"num\"></td><td class=\"line\"><div id=\"";
368 os
<< "\" class=\"msg";
370 os
<< " msg" << Kind
;
371 os
<< "\" style=\"margin-left:" << PosNo
<< "ex";
373 // Output a maximum size.
374 if (!isa
<PathDiagnosticMacroPiece
>(P
)) {
375 // Get the string and determining its maximum substring.
376 const std::string
& Msg
= P
.getString();
377 unsigned max_token
= 0;
379 unsigned len
= Msg
.size();
381 for (std::string::const_iterator I
=Msg
.begin(), E
=Msg
.end(); I
!=E
; ++I
)
389 if (cnt
> max_token
) max_token
= cnt
;
396 // Determine the approximate size of the message bubble in em.
398 const unsigned max_line
= 120;
400 if (max_token
>= max_line
)
403 unsigned characters
= max_line
;
404 unsigned lines
= len
/ max_line
;
407 for (; characters
> max_token
; --characters
)
408 if (len
/ characters
> lines
) {
418 os
<< "; max-width:" << em
<< "em";
421 os
<< "; max-width:100em";
426 os
<< "<table class=\"msgT\"><tr><td valign=\"top\">";
427 os
<< "<div class=\"PathIndex";
428 if (Kind
) os
<< " PathIndex" << Kind
;
429 os
<< "\">" << num
<< "</div>";
433 if (const PathDiagnosticMacroPiece
*MP
=
434 dyn_cast
<PathDiagnosticMacroPiece
>(&P
)) {
436 os
<< "Within the expansion of the macro '";
438 // Get the name of the macro by relexing it.
440 FullSourceLoc L
= MP
->getLocation().asLocation().getInstantiationLoc();
441 assert(L
.isFileID());
442 llvm::StringRef BufferInfo
= L
.getBufferData();
443 const char* MacroName
= L
.getDecomposedLoc().second
+ BufferInfo
.data();
444 Lexer
rawLexer(L
, PP
.getLangOptions(), BufferInfo
.begin(),
445 MacroName
, BufferInfo
.end());
448 rawLexer
.LexFromRawLexer(TheTok
);
449 for (unsigned i
= 0, n
= TheTok
.getLength(); i
< n
; ++i
)
456 os
<< "</td></tr></table>";
458 // Within a macro piece. Write out each event.
459 ProcessMacroPiece(os
, *MP
, 0);
462 os
<< html::EscapeText(P
.getString());
465 os
<< "</td></tr></table>";
468 os
<< "</div></td></tr>";
470 // Insert the new html.
471 unsigned DisplayPos
= LineEnd
- FileStart
;
473 SM
.getLocForStartOfFile(LPosInfo
.first
).getFileLocWithOffset(DisplayPos
);
475 R
.InsertTextBefore(Loc
, os
.str());
477 // Now highlight the ranges.
478 for (const SourceRange
*I
= P
.ranges_begin(), *E
= P
.ranges_end();
480 HighlightRange(R
, LPosInfo
.first
, *I
);
483 // If there is a code insertion hint, insert that code.
484 // FIXME: This code is disabled because it seems to mangle the HTML
485 // output. I'm leaving it here because it's generally the right idea,
486 // but needs some help from someone more familiar with the rewriter.
487 for (const FixItHint
*Hint
= P
.fixit_begin(), *HintEnd
= P
.fixit_end();
488 Hint
!= HintEnd
; ++Hint
) {
489 if (Hint
->RemoveRange
.isValid()) {
490 HighlightRange(R
, LPosInfo
.first
, Hint
->RemoveRange
,
491 "<span class=\"CodeRemovalHint\">", "</span>");
493 if (Hint
->InsertionLoc
.isValid()) {
494 std::string EscapedCode
= html::EscapeText(Hint
->CodeToInsert
, true);
495 EscapedCode
= "<span class=\"CodeInsertionHint\">" + EscapedCode
497 R
.InsertTextBefore(Hint
->InsertionLoc
, EscapedCode
);
503 static void EmitAlphaCounter(llvm::raw_ostream
& os
, unsigned n
) {
504 unsigned x
= n
% ('z' - 'a');
508 EmitAlphaCounter(os
, n
);
513 unsigned HTMLDiagnostics::ProcessMacroPiece(llvm::raw_ostream
& os
,
514 const PathDiagnosticMacroPiece
& P
,
517 for (PathDiagnosticMacroPiece::const_iterator I
=P
.begin(), E
=P
.end();
520 if (const PathDiagnosticMacroPiece
*MP
=
521 dyn_cast
<PathDiagnosticMacroPiece
>(*I
)) {
522 num
= ProcessMacroPiece(os
, *MP
, num
);
526 if (PathDiagnosticEventPiece
*EP
= dyn_cast
<PathDiagnosticEventPiece
>(*I
)) {
527 os
<< "<div class=\"msg msgEvent\" style=\"width:94%; "
529 "<table class=\"msgT\"><tr>"
530 "<td valign=\"top\"><div class=\"PathIndex PathIndexEvent\">";
531 EmitAlphaCounter(os
, num
++);
532 os
<< "</div></td><td valign=\"top\">"
533 << html::EscapeText(EP
->getString())
534 << "</td></tr></table></div>\n";
541 void HTMLDiagnostics::HighlightRange(Rewriter
& R
, FileID BugFileID
,
543 const char *HighlightStart
,
544 const char *HighlightEnd
) {
545 SourceManager
&SM
= R
.getSourceMgr();
546 const LangOptions
&LangOpts
= R
.getLangOpts();
548 SourceLocation InstantiationStart
= SM
.getInstantiationLoc(Range
.getBegin());
549 unsigned StartLineNo
= SM
.getInstantiationLineNumber(InstantiationStart
);
551 SourceLocation InstantiationEnd
= SM
.getInstantiationLoc(Range
.getEnd());
552 unsigned EndLineNo
= SM
.getInstantiationLineNumber(InstantiationEnd
);
554 if (EndLineNo
< StartLineNo
)
557 if (SM
.getFileID(InstantiationStart
) != BugFileID
||
558 SM
.getFileID(InstantiationEnd
) != BugFileID
)
561 // Compute the column number of the end.
562 unsigned EndColNo
= SM
.getInstantiationColumnNumber(InstantiationEnd
);
563 unsigned OldEndColNo
= EndColNo
;
566 // Add in the length of the token, so that we cover multi-char tokens.
567 EndColNo
+= Lexer::MeasureTokenLength(Range
.getEnd(), SM
, LangOpts
)-1;
570 // Highlight the range. Make the span tag the outermost tag for the
574 InstantiationEnd
.getFileLocWithOffset(EndColNo
- OldEndColNo
);
576 html::HighlightRange(R
, InstantiationStart
, E
, HighlightStart
, HighlightEnd
);