From 6efb0266238c6dd944e19002b4a8921abcb80c0a Mon Sep 17 00:00:00 2001 From: Ted Kremenek Date: Thu, 27 Mar 2008 06:17:42 +0000 Subject: [PATCH] Added "HTMLDiagnostic", a generic DiagnosticClient (that also implements PathDiagnostic) so that all diagnostics can be piped to HTML files instead of as text diagnostics using --html-diags. llvm-svn: 48865 --- clang/Driver/HTMLDiagnostics.cpp | 245 +++++++++++++++++++++++++++++++ clang/Driver/HTMLDiagnostics.h | 25 ++++ clang/Driver/clang.cpp | 49 +++++-- 3 files changed, 303 insertions(+), 16 deletions(-) create mode 100644 clang/Driver/HTMLDiagnostics.cpp create mode 100644 clang/Driver/HTMLDiagnostics.h diff --git a/clang/Driver/HTMLDiagnostics.cpp b/clang/Driver/HTMLDiagnostics.cpp new file mode 100644 index 000000000000..5e4ba971ebef --- /dev/null +++ b/clang/Driver/HTMLDiagnostics.cpp @@ -0,0 +1,245 @@ +//===--- 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 "HTMLDiagnostics.h" +#include "clang/Basic/SourceManager.h" +#include "clang/AST/ASTContext.h" +#include "clang/Analysis/PathDiagnostic.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 +#include + +using namespace clang; + +//===----------------------------------------------------------------------===// +// Boilerplate. +//===----------------------------------------------------------------------===// + +namespace { + +class VISIBILITY_HIDDEN HTMLDiagnostics : public PathDiagnosticClient { + llvm::sys::Path Directory, FilePrefix; + bool createdDir, noDir; +public: + HTMLDiagnostics(const std::string& prefix); + + virtual ~HTMLDiagnostics() {} + + virtual void HandlePathDiagnostic(const PathDiagnostic& D); + + void HandlePiece(Rewriter& R, const PathDiagnosticPiece& P, unsigned num); + void HighlightRange(Rewriter& R, SourceRange Range, unsigned MainFileID); +}; + +} // end anonymous namespace + +HTMLDiagnostics::HTMLDiagnostics(const std::string& prefix) + : Directory(prefix), FilePrefix(prefix), createdDir(false), noDir(false) { + + // All html files begin with "report" + FilePrefix.appendComponent("report"); +} + +PathDiagnosticClient* +clang::CreateHTMLDiagnosticClient(const std::string& prefix) { + + return new HTMLDiagnostics(prefix); +} + +//===----------------------------------------------------------------------===// +// Report processing. +//===----------------------------------------------------------------------===// + +void HTMLDiagnostics::HandlePathDiagnostic(const PathDiagnostic& D) { + + if (D.empty()) + return; + + // Create the HTML directory if it is missing. + + if (!createdDir) { + createdDir = true; + Directory.createDirectoryOnDisk(true, NULL); + + if (!Directory.isDirectory()) { + llvm::cerr << "warning: could not create directory '" + << FilePrefix.toString() << "'\n"; + + noDir = true; + + return; + } + } + + if (noDir) + return; + + // Create a new rewriter to generate HTML. + SourceManager& SMgr = D.begin()->getLocation().getManager(); + Rewriter R(SMgr); + + // Process the path. + + unsigned n = D.size(); + + for (PathDiagnostic::const_reverse_iterator I=D.rbegin(), E=D.rend(); + I!=E; ++I, --n) { + + HandlePiece(R, *I, n); + } + + // Add line numbers, header, footer, etc. + unsigned FileID = R.getSourceMgr().getMainFileID(); + html::EscapeText(R, FileID); + html::AddLineNumbers(R, FileID); + + // FIXME: Add the number of the file here. + + // Add CSS, header, and footer. + + html::AddHeaderFooterInternalBuiltinCSS(R, FileID); + + // 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, + const PathDiagnosticPiece& P, + unsigned num) { + + // 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 = LPos.getLocation().getFileID(); + + assert (&LPos.getManager() == &SM && "SourceManagers are different!"); + + unsigned MainFileID = SM.getMainFileID(); + + if (FileID != MainFileID) + return; + + // 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; + + // Create the html for the message. + + std::ostringstream os; + + os << "\n" + << "
"; + + os << P.getString() << "
"; + + // Insert the new html. + + const llvm::MemoryBuffer *Buf = SM.getBuffer(FileID); + const char* FileStart = Buf->getBufferStart(); + + R.InsertStrBefore(SourceLocation::getFileLoc(FileID, LineStart - FileStart), + os.str()); + + // Now highlight the ranges. + + for (const SourceRange *I = P.ranges_begin(), *E = P.ranges_end(); + I != E; ++I) + HighlightRange(R, *I, MainFileID); +} + +void HTMLDiagnostics::HighlightRange(Rewriter& R, SourceRange Range, + unsigned MainFileID) { + + SourceManager& SourceMgr = R.getSourceMgr(); + + SourceLocation LogicalStart = SourceMgr.getLogicalLoc(Range.getBegin()); + unsigned StartLineNo = SourceMgr.getLineNumber(LogicalStart); + + SourceLocation LogicalEnd = SourceMgr.getLogicalLoc(Range.getEnd()); + unsigned EndLineNo = SourceMgr.getLineNumber(LogicalEnd); + + if (EndLineNo < StartLineNo) + return; + + if (LogicalStart.getFileID() != MainFileID || + LogicalEnd.getFileID() != MainFileID) + return; + + // Compute the column number of the end. + unsigned EndColNo = SourceMgr.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(), SourceMgr); + } + + // Highlight the range. Make the span tag the outermost tag for the + // selected range. + + SourceLocation E = + LogicalEnd.getFileLocWithOffset(OldEndColNo > EndColNo + ? -(OldEndColNo - EndColNo) + : EndColNo - OldEndColNo); + + R.InsertCStrBefore(LogicalStart, ""); + R.InsertCStrAfter(E, ""); +} diff --git a/clang/Driver/HTMLDiagnostics.h b/clang/Driver/HTMLDiagnostics.h new file mode 100644 index 000000000000..172c0021c5f8 --- /dev/null +++ b/clang/Driver/HTMLDiagnostics.h @@ -0,0 +1,25 @@ +//===--- HTMLPathDiagnostic.h - 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 interface to create a HTMLPathDiagnostic object. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_PATH_HTML_DIAGNOSTIC_H +#define LLVM_CLANG_PATH_HTML_DIAGNOSTIC_H + +#include + +namespace clang { + class PathDiagnosticClient; + + PathDiagnosticClient* CreateHTMLDiagnosticClient(const std::string& prefix); +} + +#endif diff --git a/clang/Driver/clang.cpp b/clang/Driver/clang.cpp index 2e54dd94c1df..2bf99f8eafb3 100644 --- a/clang/Driver/clang.cpp +++ b/clang/Driver/clang.cpp @@ -26,6 +26,8 @@ #include "ASTConsumers.h" #include "TextDiagnosticBuffer.h" #include "TextDiagnosticPrinter.h" +#include "HTMLDiagnostics.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/TranslationUnit.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Sema/ParseAST.h" @@ -138,6 +140,7 @@ ProgAction(llvm::cl::desc("Choose output type:"), llvm::cl::ZeroOrMore, "Playground for the code rewriter"), clEnumValN(HTMLTest, "html-test", "Playground for the HTML displayer"), + clEnumValEnd)); @@ -150,6 +153,11 @@ static llvm::cl::opt VerifyDiagnostics("verify", llvm::cl::desc("Verify emitted diagnostics and warnings.")); +static llvm::cl::opt +HTMLDiag("html-diags", + llvm::cl::desc("Generate HTML to report diagnostics"), + llvm::cl::value_desc("HTML directory")); + //===----------------------------------------------------------------------===// // Language Options //===----------------------------------------------------------------------===// @@ -1058,8 +1066,7 @@ static ASTConsumer* CreateASTConsumer(const std::string& InFile, /// ProcessInputFile - Process a single input file with the specified state. /// -static void ProcessInputFile(Preprocessor &PP, const std::string &InFile, - TextDiagnostics &OurDiagnosticClient) { +static void ProcessInputFile(Preprocessor &PP, const std::string &InFile) { ASTConsumer* Consumer = NULL; bool ClearSourceMgr = false; @@ -1255,19 +1262,29 @@ int main(int argc, char **argv) { // Create the diagnostic client for reporting errors or for // implementing -verify. - std::auto_ptr DiagClient; - if (!VerifyDiagnostics) { - // Print diagnostics to stderr by default. - DiagClient.reset(new TextDiagnosticPrinter()); - } else { - // When checking diagnostics, just buffer them up. - DiagClient.reset(new TextDiagnosticBuffer()); - - if (InputFilenames.size() != 1) { - fprintf(stderr, - "-verify only works on single input files for now.\n"); - return 1; + std::auto_ptr DiagClient; + TextDiagnostics* TextDiagClient = NULL; + + if (!HTMLDiag.empty()) { + DiagClient.reset(CreateHTMLDiagnosticClient(HTMLDiag)); + } + else { // Use Text diagnostics. + if (!VerifyDiagnostics) { + // Print diagnostics to stderr by default. + TextDiagClient = new TextDiagnosticPrinter(); + } else { + // When checking diagnostics, just buffer them up. + TextDiagClient = new TextDiagnosticBuffer(); + + if (InputFilenames.size() != 1) { + fprintf(stderr, + "-verify only works on single input files for now.\n"); + return 1; + } } + + assert (TextDiagClient); + DiagClient.reset(TextDiagClient); } // Configure our handling of diagnostics. @@ -1312,7 +1329,7 @@ int main(int argc, char **argv) { // Process the -I options and set them in the HeaderInfo. HeaderSearch HeaderInfo(FileMgr); - DiagClient->setHeaderSearch(HeaderInfo); + if (TextDiagClient) TextDiagClient->setHeaderSearch(HeaderInfo); InitializeIncludePaths(argv[0], HeaderInfo, FileMgr, LangInfo); // Set up the preprocessor with these options. @@ -1322,7 +1339,7 @@ int main(int argc, char **argv) { if (!InitializePreprocessor(PP, InFile, PredefineBuffer)) continue; - ProcessInputFile(PP, InFile, *DiagClient); + ProcessInputFile(PP, InFile); HeaderInfo.ClearFileInfo(); if (Stats)