diff options
Diffstat (limited to 'lib/Driver/HTMLDiagnostics.cpp')
-rw-r--r-- | lib/Driver/HTMLDiagnostics.cpp | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/lib/Driver/HTMLDiagnostics.cpp b/lib/Driver/HTMLDiagnostics.cpp new file mode 100644 index 0000000000..e691882098 --- /dev/null +++ b/lib/Driver/HTMLDiagnostics.cpp @@ -0,0 +1,444 @@ +//===--- HTMLDiagnostics.cpp - HTML Diagnostics for Paths ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the HTMLDiagnostics object. +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/HTMLDiagnostics.h" +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/FileManager.h" +#include "clang/Rewrite/Rewriter.h" +#include "clang/Rewrite/HTMLRewrite.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Streams.h" +#include "llvm/System/Path.h" +#include <fstream> +#include <sstream> + +using namespace clang; + +//===----------------------------------------------------------------------===// +// Boilerplate. +//===----------------------------------------------------------------------===// + +namespace { + +class VISIBILITY_HIDDEN HTMLDiagnostics : public PathDiagnosticClient { + llvm::sys::Path Directory, FilePrefix; + bool createdDir, noDir; + Preprocessor* PP; + PreprocessorFactory* PPF; + std::vector<const PathDiagnostic*> BatchedDiags; +public: + HTMLDiagnostics(const std::string& prefix, Preprocessor* pp, + PreprocessorFactory* ppf); + + virtual ~HTMLDiagnostics(); + + virtual void HandlePathDiagnostic(const PathDiagnostic* D); + + void HandlePiece(Rewriter& R, unsigned BugFileID, + const PathDiagnosticPiece& P, unsigned num, unsigned max); + + void HighlightRange(Rewriter& R, unsigned BugFileID, SourceRange Range); + + void ReportDiag(const PathDiagnostic& D); +}; + +} // end anonymous namespace + +HTMLDiagnostics::HTMLDiagnostics(const std::string& prefix, Preprocessor* pp, + PreprocessorFactory* ppf) + : Directory(prefix), FilePrefix(prefix), createdDir(false), noDir(false), + PP(pp), PPF(ppf) { + + // All html files begin with "report" + FilePrefix.appendComponent("report"); +} + +PathDiagnosticClient* +clang::CreateHTMLDiagnosticClient(const std::string& prefix, Preprocessor* PP, + PreprocessorFactory* PPF) { + + return new HTMLDiagnostics(prefix, PP, PPF); +} + +//===----------------------------------------------------------------------===// +// Report processing. +//===----------------------------------------------------------------------===// + +void HTMLDiagnostics::HandlePathDiagnostic(const PathDiagnostic* D) { + if (!D) + return; + + if (D->empty()) { + delete D; + return; + } + + BatchedDiags.push_back(D); +} + +HTMLDiagnostics::~HTMLDiagnostics() { + + while (!BatchedDiags.empty()) { + const PathDiagnostic* D = BatchedDiags.back(); + BatchedDiags.pop_back(); + ReportDiag(*D); + delete D; + } +} + +void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D) { + + // Create the HTML directory if it is missing. + + if (!createdDir) { + createdDir = true; + std::string ErrorMsg; + Directory.createDirectoryOnDisk(true, &ErrorMsg); + + if (!Directory.isDirectory()) { + llvm::cerr << "warning: could not create directory '" + << Directory.toString() << "'\n" + << "reason: " << ErrorMsg << '\n'; + + noDir = true; + + return; + } + } + + if (noDir) + return; + + SourceManager& SMgr = D.begin()->getLocation().getManager(); + + unsigned FileID = 0; + bool FileIDInitialized = false; + + // Verify that the entire path is from the same FileID. + for (PathDiagnostic::const_iterator I=D.begin(), E=D.end(); I != E; ++I) { + + FullSourceLoc L = I->getLocation(); + + if (!L.isFileID()) + return; // FIXME: Emit a warning? + + if (!FileIDInitialized) { + FileID = L.getCanonicalFileID(); + FileIDInitialized = true; + } + else if (L.getCanonicalFileID() != FileID) + return; // FIXME: Emit a warning? + + // Check the source ranges. + for (PathDiagnosticPiece::range_iterator RI=I->ranges_begin(), + RE=I->ranges_end(); RI!=RE; ++RI) { + + SourceLocation L = RI->getBegin(); + + if (!L.isFileID()) + return; // FIXME: Emit a warning? + + if (SMgr.getCanonicalFileID(L) != FileID) + return; // FIXME: Emit a warning? + + L = RI->getEnd(); + + if (!L.isFileID()) + return; // FIXME: Emit a warning? + + if (SMgr.getCanonicalFileID(L) != FileID) + return; // FIXME: Emit a warning? + } + } + + if (!FileIDInitialized) + return; // FIXME: Emit a warning? + + // Create a new rewriter to generate HTML. + Rewriter R(SMgr); + + // Process the path. + + unsigned n = D.size(); + unsigned max = n; + + for (PathDiagnostic::const_reverse_iterator I=D.rbegin(), E=D.rend(); + I!=E; ++I, --n) { + + HandlePiece(R, FileID, *I, n, max); + } + + // Add line numbers, header, footer, etc. + + // unsigned FileID = R.getSourceMgr().getMainFileID(); + html::EscapeText(R, FileID); + html::AddLineNumbers(R, FileID); + + // If we have a preprocessor, relex the file and syntax highlight. + // We might not have a preprocessor if we come from a deserialized AST file, + // for example. + + if (PP) html::SyntaxHighlight(R, FileID, *PP); + + // FIXME: We eventually want to use PPF to create a fresh Preprocessor, + // once we have worked out the bugs. + // + // if (PPF) html::HighlightMacros(R, FileID, *PPF); + // + if (PP) html::HighlightMacros(R, FileID, *PP); + + // Get the full directory name of the analyzed file. + + const FileEntry* Entry = SMgr.getFileEntryForID(FileID); + + // This is a cludge; basically we want to append either the full + // working directory if we have no directory information. This is + // a work in progress. + + std::string DirName = ""; + + if (!llvm::sys::Path(Entry->getName()).isAbsolute()) { + llvm::sys::Path P = llvm::sys::Path::GetCurrentDirectory(); + DirName = P.toString() + "/"; + } + + // Add the name of the file as an <h1> tag. + + { + std::ostringstream os; + + os << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" + "<tr><td class=\"rowname\">File:</td><td>" + << html::EscapeText(DirName) + << html::EscapeText(Entry->getName()) + << "</td></tr>\n<tr><td class=\"rowname\">Location:</td><td>" + "<a href=\"#EndPath\">line " + << (*D.rbegin()).getLocation().getLogicalLineNumber() + << ", column " + << (*D.rbegin()).getLocation().getLogicalColumnNumber() + << "</a></td></tr>\n" + "<tr><td class=\"rowname\">Description:</td><td>" + << D.getDescription() << "</td></tr>\n"; + + // Output any other meta data. + + for (PathDiagnostic::meta_iterator I=D.meta_begin(), E=D.meta_end(); + I!=E; ++I) { + os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; + } + + os << "</table>\n<h3>Annotated Source Code</h3>\n"; + + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str()); + } + + // Embed meta-data tags. + + const std::string& BugDesc = D.getDescription(); + + if (!BugDesc.empty()) { + std::ostringstream os; + os << "\n<!-- BUGDESC " << BugDesc << " -->\n"; + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str()); + } + + { + std::ostringstream os; + os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str()); + } + + { + std::ostringstream os; + os << "\n<!-- BUGLINE " << D.back()->getLocation().getLogicalLineNumber() + << " -->\n"; + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str()); + } + + { + std::ostringstream os; + os << "\n<!-- BUGPATHLENGTH " << D.size() << " -->\n"; + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, 0), os.str()); + } + + // Add CSS, header, and footer. + + html::AddHeaderFooterInternalBuiltinCSS(R, FileID, Entry->getName()); + + // Get the rewrite buffer. + const RewriteBuffer *Buf = R.getRewriteBufferFor(FileID); + + if (!Buf) { + llvm::cerr << "warning: no diagnostics generated for main file.\n"; + return; + } + + // Create the stream to write out the HTML. + std::ofstream os; + + { + // Create a path for the target HTML file. + llvm::sys::Path F(FilePrefix); + F.makeUnique(false, NULL); + + // Rename the file with an HTML extension. + llvm::sys::Path H(F); + H.appendSuffix("html"); + F.renamePathOnDisk(H, NULL); + + os.open(H.toString().c_str()); + + if (!os) { + llvm::cerr << "warning: could not create file '" << F.toString() << "'\n"; + return; + } + } + + // Emit the HTML to disk. + + for (RewriteBuffer::iterator I = Buf->begin(), E = Buf->end(); I!=E; ++I) + os << *I; +} + +void HTMLDiagnostics::HandlePiece(Rewriter& R, unsigned BugFileID, + const PathDiagnosticPiece& P, + unsigned num, unsigned max) { + + // For now, just draw a box above the line in question, and emit the + // warning. + + FullSourceLoc Pos = P.getLocation(); + + if (!Pos.isValid()) + return; + + SourceManager& SM = R.getSourceMgr(); + FullSourceLoc LPos = Pos.getLogicalLoc(); + unsigned FileID = SM.getCanonicalFileID(LPos.getLocation()); + + assert (&LPos.getManager() == &SM && "SourceManagers are different!"); + + if (LPos.getCanonicalFileID() != BugFileID) + return; + + const llvm::MemoryBuffer *Buf = SM.getBuffer(FileID); + const char* FileStart = Buf->getBufferStart(); + + // Compute the column number. Rewind from the current position to the start + // of the line. + + unsigned ColNo = LPos.getColumnNumber(); + const char *TokLogicalPtr = LPos.getCharacterData(); + const char *LineStart = TokLogicalPtr-ColNo; + + // Only compute LineEnd if we display below a line. + const char *LineEnd = TokLogicalPtr; + + if (P.getDisplayHint() == PathDiagnosticPiece::Below) { + const char* FileEnd = Buf->getBufferEnd(); + + while (*LineEnd != '\n' && LineEnd != FileEnd) + ++LineEnd; + } + + // Compute the margin offset by counting tabs and non-tabs. + + unsigned PosNo = 0; + + for (const char* c = LineStart; c != TokLogicalPtr; ++c) + PosNo += *c == '\t' ? 8 : 1; + + // Create the html for the message. + + std::ostringstream os; + + os << "\n<tr><td class=\"num\"></td><td class=\"line\">" + << "<div id=\""; + + if (num == max) + os << "EndPath"; + else + os << "Path" << num; + + os << "\" class=\"msg\" style=\"margin-left:" + << PosNo << "ex\">"; + + if (max > 1) + os << "<span class=\"PathIndex\">[" << num << "]</span> "; + + os << html::EscapeText(P.getString()) << "</div></td></tr>"; + + // Insert the new html. + + unsigned DisplayPos = 0; + + switch (P.getDisplayHint()) { + case PathDiagnosticPiece::Above: + DisplayPos = LineStart - FileStart; + break; + case PathDiagnosticPiece::Below: + DisplayPos = LineEnd - FileStart; + break; + default: + assert (false && "Unhandled hint."); + } + + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, DisplayPos), os.str()); + + // Now highlight the ranges. + + for (const SourceRange *I = P.ranges_begin(), *E = P.ranges_end(); + I != E; ++I) + HighlightRange(R, FileID, *I); +} + +void HTMLDiagnostics::HighlightRange(Rewriter& R, unsigned BugFileID, + SourceRange Range) { + + SourceManager& SM = R.getSourceMgr(); + + SourceLocation LogicalStart = SM.getLogicalLoc(Range.getBegin()); + unsigned StartLineNo = SM.getLineNumber(LogicalStart); + + SourceLocation LogicalEnd = SM.getLogicalLoc(Range.getEnd()); + unsigned EndLineNo = SM.getLineNumber(LogicalEnd); + + if (EndLineNo < StartLineNo) + return; + + if (SM.getCanonicalFileID(LogicalStart) != BugFileID || + SM.getCanonicalFileID(LogicalEnd) != BugFileID) + return; + + // Compute the column number of the end. + unsigned EndColNo = SM.getColumnNumber(LogicalEnd); + unsigned OldEndColNo = EndColNo; + + if (EndColNo) { + // Add in the length of the token, so that we cover multi-char tokens. + EndColNo += Lexer::MeasureTokenLength(Range.getEnd(), SM) - 1; + } + + // Highlight the range. Make the span tag the outermost tag for the + // selected range. + + SourceLocation E = LogicalEnd.getFileLocWithOffset(EndColNo - OldEndColNo); + + html::HighlightRange(R, LogicalStart, E, + "<span class=\"mrange\">", "</span>"); +} |