blob: b49586f22381be8127dafa4ad0e491cb7e71f78d [file] [log] [blame]
Ted Kremenek88f5cde2008-03-27 06:17:42 +00001//===--- 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 "HTMLDiagnostics.h"
15#include "clang/Basic/SourceManager.h"
Ted Kremenek2e939812008-03-27 07:35:49 +000016#include "clang/Basic/FileManager.h"
Ted Kremenek88f5cde2008-03-27 06:17:42 +000017#include "clang/AST/ASTContext.h"
18#include "clang/Analysis/PathDiagnostic.h"
19#include "clang/Rewrite/Rewriter.h"
20#include "clang/Rewrite/HTMLRewrite.h"
21#include "clang/Lex/Lexer.h"
22#include "llvm/Support/Compiler.h"
23#include "llvm/Support/MemoryBuffer.h"
24#include "llvm/Support/Streams.h"
25#include "llvm/System/Path.h"
26#include <fstream>
27#include <sstream>
28
29using namespace clang;
30
31//===----------------------------------------------------------------------===//
32// Boilerplate.
33//===----------------------------------------------------------------------===//
34
35namespace {
36
37class VISIBILITY_HIDDEN HTMLDiagnostics : public PathDiagnosticClient {
38 llvm::sys::Path Directory, FilePrefix;
39 bool createdDir, noDir;
Ted Kremenek47abe762008-04-16 16:39:56 +000040 Preprocessor* PP;
Ted Kremenek88f5cde2008-03-27 06:17:42 +000041public:
Ted Kremenek47abe762008-04-16 16:39:56 +000042 HTMLDiagnostics(const std::string& prefix, Preprocessor* pp = NULL);
Ted Kremenek88f5cde2008-03-27 06:17:42 +000043
44 virtual ~HTMLDiagnostics() {}
45
46 virtual void HandlePathDiagnostic(const PathDiagnostic& D);
47
Ted Kremenek33bd9422008-03-31 23:30:12 +000048 void HandlePiece(Rewriter& R, const PathDiagnosticPiece& P,
49 unsigned num, unsigned max);
50
Ted Kremenek5dd00412008-04-14 21:06:04 +000051 void HighlightRange(Rewriter& R, SourceRange Range);
Ted Kremenek88f5cde2008-03-27 06:17:42 +000052};
53
54} // end anonymous namespace
55
Ted Kremenek47abe762008-04-16 16:39:56 +000056HTMLDiagnostics::HTMLDiagnostics(const std::string& prefix, Preprocessor* pp)
57 : Directory(prefix), FilePrefix(prefix), createdDir(false), noDir(false),
58 PP(pp) {
Ted Kremenek88f5cde2008-03-27 06:17:42 +000059
60 // All html files begin with "report"
61 FilePrefix.appendComponent("report");
62}
63
64PathDiagnosticClient*
Ted Kremenek47abe762008-04-16 16:39:56 +000065clang::CreateHTMLDiagnosticClient(const std::string& prefix, Preprocessor* PP) {
Ted Kremenek88f5cde2008-03-27 06:17:42 +000066
Ted Kremenek47abe762008-04-16 16:39:56 +000067 return new HTMLDiagnostics(prefix, PP);
Ted Kremenek88f5cde2008-03-27 06:17:42 +000068}
69
70//===----------------------------------------------------------------------===//
71// Report processing.
72//===----------------------------------------------------------------------===//
73
74void HTMLDiagnostics::HandlePathDiagnostic(const PathDiagnostic& D) {
75
76 if (D.empty())
77 return;
78
79 // Create the HTML directory if it is missing.
80
81 if (!createdDir) {
82 createdDir = true;
Ted Kremenek344f7e32008-04-03 17:55:57 +000083 std::string ErrorMsg;
84 Directory.createDirectoryOnDisk(true, &ErrorMsg);
Ted Kremenek88f5cde2008-03-27 06:17:42 +000085
86 if (!Directory.isDirectory()) {
87 llvm::cerr << "warning: could not create directory '"
Ted Kremenek344f7e32008-04-03 17:55:57 +000088 << Directory.toString() << "'\n"
89 << "reason: " << ErrorMsg << '\n';
Ted Kremenek88f5cde2008-03-27 06:17:42 +000090
91 noDir = true;
92
93 return;
94 }
95 }
96
97 if (noDir)
98 return;
99
100 // Create a new rewriter to generate HTML.
101 SourceManager& SMgr = D.begin()->getLocation().getManager();
102 Rewriter R(SMgr);
103
104 // Process the path.
105
106 unsigned n = D.size();
Ted Kremenek33bd9422008-03-31 23:30:12 +0000107 unsigned max = n;
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000108
109 for (PathDiagnostic::const_reverse_iterator I=D.rbegin(), E=D.rend();
110 I!=E; ++I, --n) {
111
Ted Kremenek33bd9422008-03-31 23:30:12 +0000112 HandlePiece(R, *I, n, max);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000113 }
114
115 // Add line numbers, header, footer, etc.
Ted Kremenek2e939812008-03-27 07:35:49 +0000116
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000117 unsigned FileID = R.getSourceMgr().getMainFileID();
118 html::EscapeText(R, FileID);
119 html::AddLineNumbers(R, FileID);
120
Ted Kremenek47abe762008-04-16 16:39:56 +0000121 // If we have a preprocessor, relex the file and syntax highlight.
122 // We might not have a preprocessor if we come from a deserialized AST file,
123 // for example.
124
125 if (PP) {
126 html::SyntaxHighlight(R, FileID, *PP);
127 html::HighlightMacros(R, FileID, *PP);
128 }
129
Ted Kremenekb9476392008-04-02 20:44:16 +0000130 // Get the full directory name of the analyzed file.
131
132 const FileEntry* Entry = SMgr.getFileEntryForID(FileID);
133 std::string DirName(Entry->getDir()->getName());
Ted Kremenek2e939812008-03-27 07:35:49 +0000134
Ted Kremenekb9476392008-04-02 20:44:16 +0000135 if (DirName == ".")
136 DirName = llvm::sys::Path::GetCurrentDirectory().toString();
137
Ted Kremenek4b0f8132008-04-15 21:25:08 +0000138 // Add the name of the file as an <h1> tag.
139
Ted Kremenek2e939812008-03-27 07:35:49 +0000140 {
141 std::ostringstream os;
Ted Kremenek2e939812008-03-27 07:35:49 +0000142
Ted Kremenek4b0f8132008-04-15 21:25:08 +0000143 os << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n"
144 "<tr><td class=\"rowname\">File:</td><td>"
145 << html::EscapeText(DirName)
146 << html::EscapeText(Entry->getName())
147 << "</td></tr>\n<tr><td class=\"rowname\">Location:</td><td>"
148 "<a href=\"#EndPath\">line "
149 << (*D.rbegin()).getLocation().getLogicalLineNumber()
150 << ", column "
151 << (*D.rbegin()).getLocation().getLogicalColumnNumber()
152 << "</a></td></tr>\n"
153 "<tr><td class=\"rowname\">Description:</td><td>"
154 << D.getDescription()
155 << "</td></tr>\n</table>\n"
156 "<h3>Annotated Source Code</h3>\n";
157
Ted Kremenek2e939812008-03-27 07:35:49 +0000158 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str());
Ted Kremenek07615402008-04-02 07:04:46 +0000159 }
160
Ted Kremenekb9476392008-04-02 20:44:16 +0000161 // Embed meta-data tags.
Ted Kremenek07615402008-04-02 07:04:46 +0000162
163 const std::string& BugDesc = D.getDescription();
164
165 if (!BugDesc.empty()) {
166 std::ostringstream os;
Ted Kremenek86b43812008-04-02 18:03:20 +0000167 os << "\n<!-- BUGDESC " << BugDesc << " -->\n";
Ted Kremenek07615402008-04-02 07:04:46 +0000168 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str());
Ted Kremenekb9476392008-04-02 20:44:16 +0000169 }
170
171 {
172 std::ostringstream os;
173 os << "\n<!-- BUGFILE " << DirName << "/" << Entry->getName() << " -->\n";
174 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str());
175 }
176
177 {
178 std::ostringstream os;
Ted Kremenek344f7e32008-04-03 17:55:57 +0000179 os << "\n<!-- BUGLINE " << D.back()->getLocation().getLogicalLineNumber()
Ted Kremenekb9476392008-04-02 20:44:16 +0000180 << " -->\n";
181 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str());
182 }
183
184 {
185 std::ostringstream os;
186 os << "\n<!-- BUGPATHLENGTH " << D.size() << " -->\n";
187 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str());
188 }
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000189
190 // Add CSS, header, and footer.
191
192 html::AddHeaderFooterInternalBuiltinCSS(R, FileID);
193
194 // Get the rewrite buffer.
195 const RewriteBuffer *Buf = R.getRewriteBufferFor(FileID);
196
197 if (!Buf) {
198 llvm::cerr << "warning: no diagnostics generated for main file.\n";
199 return;
200 }
201
202 // Create the stream to write out the HTML.
203 std::ofstream os;
204
205 {
206 // Create a path for the target HTML file.
207 llvm::sys::Path F(FilePrefix);
208 F.makeUnique(false, NULL);
209
210 // Rename the file with an HTML extension.
211 llvm::sys::Path H(F);
212 H.appendSuffix("html");
213 F.renamePathOnDisk(H, NULL);
214
215 os.open(H.toString().c_str());
216
217 if (!os) {
218 llvm::cerr << "warning: could not create file '" << F.toString() << "'\n";
219 return;
220 }
221 }
222
223 // Emit the HTML to disk.
224
Ted Kremenekfa5be362008-04-08 22:37:58 +0000225 for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I) {
226 // Expand tabs.
227 if (*I == '\t')
228 os << " ";
229 else
230 os << *I;
231 }
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000232}
233
234void HTMLDiagnostics::HandlePiece(Rewriter& R,
235 const PathDiagnosticPiece& P,
Ted Kremenek33bd9422008-03-31 23:30:12 +0000236 unsigned num, unsigned max) {
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000237
238 // For now, just draw a box above the line in question, and emit the
239 // warning.
240
241 FullSourceLoc Pos = P.getLocation();
242
243 if (!Pos.isValid())
244 return;
245
246 SourceManager& SM = R.getSourceMgr();
247 FullSourceLoc LPos = Pos.getLogicalLoc();
Ted Kremenek5dd00412008-04-14 21:06:04 +0000248 unsigned FileID = SM.getCanonicalFileID(LPos.getLocation());
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000249
250 assert (&LPos.getManager() == &SM && "SourceManagers are different!");
251
Ted Kremenek5dd00412008-04-14 21:06:04 +0000252 if (!SM.isFromMainFile(LPos.getLocation()))
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000253 return;
254
255 // Compute the column number. Rewind from the current position to the start
256 // of the line.
257
258 unsigned ColNo = LPos.getColumnNumber();
259 const char *TokLogicalPtr = LPos.getCharacterData();
260 const char *LineStart = TokLogicalPtr-ColNo;
261
Ted Kremenek2aa13b52008-03-31 21:40:14 +0000262 // Compute the margin offset by counting tabs and non-tabs.
263
264 unsigned PosNo = 0;
265
266 for (const char* c = LineStart; c != TokLogicalPtr; ++c)
Ted Kremenek8fb00162008-03-31 23:14:05 +0000267 PosNo += *c == '\t' ? 4 : 1;
Ted Kremenek2aa13b52008-03-31 21:40:14 +0000268
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000269 // Create the html for the message.
270
271 std::ostringstream os;
272
273 os << "\n<tr><td class=\"num\"></td><td class=\"line\">"
Ted Kremenek33bd9422008-03-31 23:30:12 +0000274 << "<div id=\"";
275
276 if (num == max)
277 os << "EndPath";
278 else
279 os << "Path" << num;
280
281 os << "\" class=\"msg\" style=\"margin-left:"
Ted Kremenek2aa13b52008-03-31 21:40:14 +0000282 << PosNo << "ex\">";
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000283
Ted Kremenek718ceb12008-04-02 21:04:20 +0000284 if (max > 1)
285 os << "<span class=\"PathIndex\">[" << num << "]</span> ";
286
Ted Kremenek053ef592008-03-27 17:15:29 +0000287 os << html::EscapeText(P.getString()) << "</div></td></tr>";
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000288
289 // Insert the new html.
290
291 const llvm::MemoryBuffer *Buf = SM.getBuffer(FileID);
292 const char* FileStart = Buf->getBufferStart();
293
294 R.InsertStrBefore(SourceLocation::getFileLoc(FileID, LineStart - FileStart),
295 os.str());
296
297 // Now highlight the ranges.
298
299 for (const SourceRange *I = P.ranges_begin(), *E = P.ranges_end();
300 I != E; ++I)
Ted Kremenek5dd00412008-04-14 21:06:04 +0000301 HighlightRange(R, *I);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000302}
303
Ted Kremenek5dd00412008-04-14 21:06:04 +0000304void HTMLDiagnostics::HighlightRange(Rewriter& R, SourceRange Range) {
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000305
Ted Kremenek5dd00412008-04-14 21:06:04 +0000306 SourceManager& SM = R.getSourceMgr();
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000307
Ted Kremenek5dd00412008-04-14 21:06:04 +0000308 SourceLocation LogicalStart = SM.getLogicalLoc(Range.getBegin());
309 unsigned StartLineNo = SM.getLineNumber(LogicalStart);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000310
Ted Kremenek5dd00412008-04-14 21:06:04 +0000311 SourceLocation LogicalEnd = SM.getLogicalLoc(Range.getEnd());
312 unsigned EndLineNo = SM.getLineNumber(LogicalEnd);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000313
314 if (EndLineNo < StartLineNo)
315 return;
316
Ted Kremenek5dd00412008-04-14 21:06:04 +0000317 if (!SM.isFromMainFile(LogicalStart) ||
318 !SM.isFromMainFile(LogicalEnd))
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000319 return;
320
321 // Compute the column number of the end.
Ted Kremenek5dd00412008-04-14 21:06:04 +0000322 unsigned EndColNo = SM.getColumnNumber(LogicalEnd);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000323 unsigned OldEndColNo = EndColNo;
324
325 if (EndColNo) {
326 // Add in the length of the token, so that we cover multi-char tokens.
Ted Kremenek5dd00412008-04-14 21:06:04 +0000327 EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM);
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000328 }
329
330 // Highlight the range. Make the span tag the outermost tag for the
331 // selected range.
332
333 SourceLocation E =
334 LogicalEnd.getFileLocWithOffset(OldEndColNo > EndColNo
335 ? -(OldEndColNo - EndColNo)
336 : EndColNo - OldEndColNo);
337
338 R.InsertCStrBefore(LogicalStart, "<span class=\"mrange\">");
339 R.InsertCStrAfter(E, "</span>");
Ted Kremenekdab4ead2008-04-08 21:29:14 +0000340
341 if (EndLineNo == StartLineNo)
342 return;
343
344 // Add in </span><span> tags for intermediate lines.
345
Ted Kremenek5dd00412008-04-14 21:06:04 +0000346 unsigned FileID = SM.getCanonicalFileID(LogicalStart);
347 const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(FileID);
Ted Kremenekdab4ead2008-04-08 21:29:14 +0000348
Ted Kremenek5dd00412008-04-14 21:06:04 +0000349 unsigned Pos = SM.getFullFilePos(LogicalStart);
350 unsigned EndPos = SM.getFullFilePos(E);
Ted Kremenekdab4ead2008-04-08 21:29:14 +0000351 const char* buf = Buf->getBufferStart();
352
353 for (; Pos != EndPos; ++Pos) {
354
Ted Kremenek5dd00412008-04-14 21:06:04 +0000355 SourceLocation L = SourceLocation::getFileLoc(FileID, Pos);
356 unsigned Col = SM.getColumnNumber(L);
Ted Kremenekdab4ead2008-04-08 21:29:14 +0000357
358 if (Col == 1) {
359
360 // Start if a new line. Scan to see if we hit anything that is not
361 // whitespace or a newline.
362
363 unsigned PosTmp = Pos;
364 bool NewLine = false;
365
366 for ( ; PosTmp != EndPos ; ++PosTmp) {
367 switch (buf[PosTmp]) {
368 case ' ':
369 case '\t': continue;
370 case '\n':
371 NewLine = true;
372 break;
373 default:
374 break;
375 }
376
377 break;
378 }
379
380 if (PosTmp == EndPos)
381 break;
382
383 Pos = PosTmp;
384
385 // Don't highlight a blank line.
386 if (NewLine)
387 continue;
388
389 // This line contains text that we should highlight.
390 // Ignore leading whitespace.
Ted Kremenek5dd00412008-04-14 21:06:04 +0000391 L = SourceLocation::getFileLoc(FileID, Pos);
Ted Kremenekdab4ead2008-04-08 21:29:14 +0000392 R.InsertCStrAfter(L, "<span class=\"mrange\">");
393 }
394 else if (buf[Pos] == '\n')
395 R.InsertCStrBefore(L, "</span>");
396 }
Ted Kremenek88f5cde2008-03-27 06:17:42 +0000397}