From: Ted Kremenek Date: Sat, 29 Oct 2011 00:12:39 +0000 (+0000) Subject: Start work on SerializedDiagnosticPrinter, a new DiagnosticConsumer that serializes... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7800212ef29be314d55814e8dcc568ff8beed106;p=clang Start work on SerializedDiagnosticPrinter, a new DiagnosticConsumer that serializes out the diagnostics for a given translation unit to a bit code file. This is a WIP. The motivation for this new DiagnosticConsumer is to provide a way for tools invoking the compiler to get its diagnostics via a libclang interface, rather than textually parsing the compiler output. This gives us flexibility to change the compiler's textual output, but have a structured data format for clients to use to get the diagnostics via a stable API. I have no tests for this, but llvm-bcanalyzer so far shows that the emitted file is well-formed. More work to follow. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@143259 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticFrontendKinds.td b/include/clang/Basic/DiagnosticFrontendKinds.td index fffa42feb2..82f91961e7 100644 --- a/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/include/clang/Basic/DiagnosticFrontendKinds.td @@ -82,6 +82,10 @@ def warn_fe_cc_print_header_failure : Warning< def warn_fe_cc_log_diagnostics_failure : Warning< "unable to open CC_LOG_DIAGNOSTICS file: %0 (using stderr)">; +def warn_fe_serialized_diag_failure : Warning< + "unable to open file %0 for serializing diagnostics (%1)">, + InGroup>; + def err_verify_missing_start : Error< "cannot find start ('{{') of expected %0">; def err_verify_missing_end : Error< diff --git a/include/clang/Driver/CC1Options.td b/include/clang/Driver/CC1Options.td index 7e61b6c6eb..c5d232abf6 100644 --- a/include/clang/Driver/CC1Options.td +++ b/include/clang/Driver/CC1Options.td @@ -228,6 +228,9 @@ def dump_build_information : Separate<"-dump-build-information">, HelpText<"output a dump of some build information to a file">; def diagnostic_log_file : Separate<"-diagnostic-log-file">, HelpText<"Filename (or -) to log diagnostics to">; +def diagnostic_serialized_file : Separate<"-diagnostic-serialized-file">, + MetaVarName<"">, + HelpText<"File for serializing diagnostics in a binary format">; def fno_show_column : Flag<"-fno-show-column">, HelpText<"Do not include column number on diagnostics">; def fshow_column : Flag<"-fshow-column">, diff --git a/include/clang/Frontend/DiagnosticOptions.h b/include/clang/Frontend/DiagnosticOptions.h index 319abeb4fb..8433da4fa4 100644 --- a/include/clang/Frontend/DiagnosticOptions.h +++ b/include/clang/Frontend/DiagnosticOptions.h @@ -67,6 +67,9 @@ public: /// The file to log diagnostic output to. std::string DiagnosticLogFile; + + /// The file to serialize diagnostics to (non-appending). + std::string DiagnosticSerializationFile; /// The list of -W... options used to alter the diagnostic mappings, with the /// prefixes removed. diff --git a/include/clang/Frontend/SerializedDiagnosticPrinter.h b/include/clang/Frontend/SerializedDiagnosticPrinter.h new file mode 100644 index 0000000000..b092b86ee5 --- /dev/null +++ b/include/clang/Frontend/SerializedDiagnosticPrinter.h @@ -0,0 +1,36 @@ +//===--- SerializedDiagnosticPrinter.h - Serializer for diagnostics -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_FRONTEND_SERIALIZE_DIAGNOSTIC_PRINTER_H_ +#define LLVM_CLANG_FRONTEND_SERIALIZE_DIAGNOSTIC_PRINTER_H_ + +namespace llvm { +class raw_ostream; +} + +namespace clang { +class DiagnosticConsumer; +class DiagnosticsEngine; + +namespace serialized_diags { +/// \brief Returns a DiagnosticConsumer that serializes diagnostics to +/// a bitcode file. +/// +/// The created DiagnosticConsumer is designed for quick and lightweight +/// transfer of of diagnostics to the enclosing build system (e.g., an IDE). +/// This allows wrapper tools for Clang to get diagnostics from Clang +/// (via libclang) without needing to parse Clang's command line output. +/// +DiagnosticConsumer *create(llvm::raw_ostream *OS, + DiagnosticsEngine &Diags); + +} // end serialized_diags namespace +} // end clang namespace + +#endif diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index 90fa91d04d..cbeb69d12b 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -27,6 +27,7 @@ add_clang_library(clangFrontend LogDiagnosticPrinter.cpp MultiplexConsumer.cpp PrintPreprocessedOutput.cpp + SerializedDiagnosticPrinter.cpp TextDiagnostic.cpp TextDiagnosticBuffer.cpp TextDiagnosticPrinter.cpp diff --git a/lib/Frontend/CompilerInstance.cpp b/lib/Frontend/CompilerInstance.cpp index 55264870b8..9b96141b5d 100644 --- a/lib/Frontend/CompilerInstance.cpp +++ b/lib/Frontend/CompilerInstance.cpp @@ -24,6 +24,7 @@ #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/LogDiagnosticPrinter.h" +#include "clang/Frontend/SerializedDiagnosticPrinter.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/VerifyDiagnosticConsumer.h" #include "clang/Frontend/Utils.h" @@ -153,6 +154,28 @@ static void SetUpDiagnosticLog(const DiagnosticOptions &DiagOpts, Diags.setClient(new ChainedDiagnosticConsumer(Diags.takeClient(), Logger)); } +static void SetupSerializedDiagnostics(const DiagnosticOptions &DiagOpts, + DiagnosticsEngine &Diags, + StringRef OutputFile) { + std::string ErrorInfo; + llvm::OwningPtr OS; + OS.reset(new llvm::raw_fd_ostream(OutputFile.str().c_str(), ErrorInfo, + llvm::raw_fd_ostream::F_Binary)); + + if (!ErrorInfo.empty()) { + Diags.Report(diag::warn_fe_serialized_diag_failure) + << OutputFile << ErrorInfo; + return; + } + + DiagnosticConsumer *SerializedConsumer = + clang::serialized_diags::create(OS.take(), Diags); + + + Diags.setClient(new ChainedDiagnosticConsumer(Diags.takeClient(), + SerializedConsumer)); +} + void CompilerInstance::createDiagnostics(int Argc, const char* const *Argv, DiagnosticConsumer *Client, bool ShouldOwnClient, @@ -194,6 +217,10 @@ CompilerInstance::createDiagnostics(const DiagnosticOptions &Opts, if (!Opts.DumpBuildInformation.empty()) SetUpBuildDumpLog(Opts, Argc, Argv, *Diags); + if (!Opts.DiagnosticSerializationFile.empty()) + SetupSerializedDiagnostics(Opts, *Diags, + Opts.DiagnosticSerializationFile); + // Configure our handling of diagnostics. ProcessWarningOptions(*Diags, Opts); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index f1d98b9458..a4851f1740 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1109,6 +1109,8 @@ static void ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args, DiagnosticsEngine &Diags) { using namespace cc1options; Opts.DiagnosticLogFile = Args.getLastArgValue(OPT_diagnostic_log_file); + Opts.DiagnosticSerializationFile = + Args.getLastArgValue(OPT_diagnostic_serialized_file); Opts.IgnoreWarnings = Args.hasArg(OPT_w); Opts.NoRewriteMacros = Args.hasArg(OPT_Wno_rewrite_macros); Opts.Pedantic = Args.hasArg(OPT_pedantic); diff --git a/lib/Frontend/SerializedDiagnosticPrinter.cpp b/lib/Frontend/SerializedDiagnosticPrinter.cpp new file mode 100644 index 0000000000..4a7c8cf60c --- /dev/null +++ b/lib/Frontend/SerializedDiagnosticPrinter.cpp @@ -0,0 +1,357 @@ +//===--- SerializedDiagnosticPrinter.cpp - Serializer for diagnostics -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include "llvm/Bitcode/BitstreamWriter.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/DenseSet.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/SerializedDiagnosticPrinter.h" + +using namespace clang; + +namespace { + +/// \brief A utility class for entering and exiting bitstream blocks. +class BlockEnterExit { + llvm::BitstreamWriter &Stream; +public: + BlockEnterExit(llvm::BitstreamWriter &stream, unsigned blockID, + unsigned codelen = 3) + : Stream(stream) { + Stream.EnterSubblock(blockID, codelen); + } + ~BlockEnterExit() { + Stream.ExitBlock(); + } +}; + +class AbbreviationMap { + llvm::DenseMap Abbrevs; +public: + AbbreviationMap() {} + + void set(unsigned recordID, unsigned abbrevID) { + assert(Abbrevs.find(recordID) == Abbrevs.end() + && "Abbreviation already set."); + Abbrevs[recordID] = abbrevID; + } + + unsigned get(unsigned recordID) { + assert(Abbrevs.find(recordID) != Abbrevs.end() && + "Abbreviation not set."); + return Abbrevs[recordID]; + } +}; + +typedef llvm::SmallVector RecordData; +typedef llvm::SmallVectorImpl RecordDataImpl; + +class SDiagsWriter : public DiagnosticConsumer { +public: + SDiagsWriter(DiagnosticsEngine &diags, llvm::raw_ostream *os) + : Stream(Buffer), OS(os), Diags(diags) + { + EmitPreamble(); + }; + + ~SDiagsWriter() {} + + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info); + + void EndSourceFile(); + + DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const { + // It makes no sense to clone this. + return 0; + } + +private: + /// \brief Emit the preamble for the serialized diagnostics. + void EmitPreamble(); + + /// \brief Emit the BLOCKINFO block. + void EmitBlockInfoBlock(); + + /// \brief Emit the raw characters of the provided string. + void EmitRawStringContents(StringRef str); + + /// \brief Emit the block containing categories and file names. + void EmitCategoriesAndFileNames(); + + /// \brief The version of the diagnostics file. + enum { Version = 1 }; + + /// \brief The byte buffer for the serialized content. + std::vector Buffer; + + /// \brief The BitStreamWriter for the serialized diagnostics. + llvm::BitstreamWriter Stream; + + /// \brief The name of the diagnostics file. + llvm::OwningPtr OS; + + /// \brief The DiagnosticsEngine tied to all diagnostic locations. + DiagnosticsEngine &Diags; + + /// \brief The set of constructed record abbreviations. + AbbreviationMap Abbrevs; + + /// \brief A utility buffer for constructing record content. + RecordData Record; + + /// \brief A text buffer for rendering diagnostic text. + llvm::SmallString<256> diagBuf; + + /// \brief The collection of diagnostic categories used. + llvm::DenseSet Categories; + + /// \brief The collection of files used. + llvm::DenseSet Files; + + enum BlockIDs { + /// \brief The DIAG block, which acts as a container around a diagnostic. + BLOCK_DIAG = llvm::bitc::FIRST_APPLICATION_BLOCKID, + /// \brief The STRINGS block, which contains strings + /// from multiple diagnostics. + BLOCK_STRINGS + }; + + enum RecordIDs { + RECORD_DIAG = 1, + RECORD_DIAG_FLAG, + RECORD_CATEGORY, + RECORD_FILENAME + }; + +}; +} // end anonymous namespace + +namespace clang { +namespace serialized_diags { +DiagnosticConsumer *create(llvm::raw_ostream *OS, DiagnosticsEngine &Diags) { + return new SDiagsWriter(Diags, OS); +} +} // end namespace serialized_diags +} // end namespace clang + +//===----------------------------------------------------------------------===// +// Serialization methods. +//===----------------------------------------------------------------------===// + +/// \brief Emits a block ID in the BLOCKINFO block. +static void EmitBlockID(unsigned ID, const char *Name, + llvm::BitstreamWriter &Stream, + RecordDataImpl &Record) { + Record.clear(); + Record.push_back(ID); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Record); + + // Emit the block name if present. + if (Name == 0 || Name[0] == 0) + return; + + Record.clear(); + + while (*Name) + Record.push_back(*Name++); + + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, Record); +} + +/// \brief Emits a record ID in the BLOCKINFO block. +static void EmitRecordID(unsigned ID, const char *Name, + llvm::BitstreamWriter &Stream, + RecordDataImpl &Record){ + Record.clear(); + Record.push_back(ID); + + while (*Name) + Record.push_back(*Name++); + + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Record); +} + +/// \brief Emits the preamble of the diagnostics file. +void SDiagsWriter::EmitPreamble() { + // EmitRawStringContents("CLANG_DIAGS"); + // Stream.Emit(Version, 32); + + // Emit the file header. + Stream.Emit((unsigned)'D', 8); + Stream.Emit((unsigned)'I', 8); + Stream.Emit((unsigned)'A', 8); + Stream.Emit((unsigned)'G', 8); + + EmitBlockInfoBlock(); +} + +void SDiagsWriter::EmitBlockInfoBlock() { + Stream.EnterBlockInfoBlock(3); + + // ==---------------------------------------------------------------------==// + // The subsequent records and Abbrevs are for the "Diagnostic" block. + // ==---------------------------------------------------------------------==// + + EmitBlockID(BLOCK_DIAG, "Diagnostic", Stream, Record); + EmitRecordID(RECORD_DIAG, "Diagnostic Info", Stream, Record); + EmitRecordID(RECORD_DIAG_FLAG, "Diagnostic Flag", Stream, Record); + + // Emit Abbrevs. + using namespace llvm; + + // Emit abbreviation for RECORD_DIAG. + BitCodeAbbrev *Abbrev = new BitCodeAbbrev(); + Abbrev->Add(BitCodeAbbrevOp(RECORD_DIAG)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // Diag level. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16-3)); // Category. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Text size. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Diagnostc text. + Abbrevs.set(RECORD_DIAG, Stream.EmitBlockInfoAbbrev(BLOCK_DIAG, Abbrev)); + + + // Emit the abbreviation for RECORD_DIAG_FLAG. + Abbrev = new BitCodeAbbrev(); + Abbrev->Add(BitCodeAbbrevOp(RECORD_DIAG_FLAG)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Text size. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Flag name text. + Abbrevs.set(RECORD_DIAG_FLAG, Stream.EmitBlockInfoAbbrev(BLOCK_DIAG, Abbrev)); + + // ==---------------------------------------------------------------------==// + // The subsequent records and Abbrevs are for the "Strings" block. + // ==---------------------------------------------------------------------==// + + EmitBlockID(BLOCK_STRINGS, "Strings", Stream, Record); + EmitRecordID(RECORD_CATEGORY, "Category Name", Stream, Record); + EmitRecordID(RECORD_FILENAME, "File Name", Stream, Record); + + Abbrev = new BitCodeAbbrev(); + Abbrev->Add(BitCodeAbbrevOp(RECORD_CATEGORY)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 8)); // Text size. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // Category text. + Abbrevs.set(RECORD_CATEGORY, Stream.EmitBlockInfoAbbrev(BLOCK_STRINGS, + Abbrev)); + + Abbrev = new BitCodeAbbrev(); + Abbrev->Add(BitCodeAbbrevOp(RECORD_CATEGORY)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Text size. + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); // File name text. + Abbrevs.set(RECORD_FILENAME, Stream.EmitBlockInfoAbbrev(BLOCK_STRINGS, + Abbrev)); + + Stream.ExitBlock(); +} + +void SDiagsWriter::EmitRawStringContents(llvm::StringRef str) { + for (StringRef::const_iterator I = str.begin(), E = str.end(); I!=E; ++I) + Stream.Emit(*I, 8); +} + +void SDiagsWriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) { + + BlockEnterExit DiagBlock(Stream, BLOCK_DIAG); + + // Emit the RECORD_DIAG record. + Record.clear(); + Record.push_back(RECORD_DIAG); + Record.push_back(DiagLevel); + unsigned category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID()); + Record.push_back(category); + Categories.insert(category); + diagBuf.clear(); + Info.FormatDiagnostic(diagBuf); // Compute the diagnostic text. + Record.push_back(diagBuf.str().size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RECORD_DIAG), Record, diagBuf.str()); + + // Emit the RECORD_DIAG_FLAG record. + StringRef FlagName = DiagnosticIDs::getWarningOptionForDiag(Info.getID()); + if (!FlagName.empty()) { + Record.clear(); + Record.push_back(RECORD_DIAG_FLAG); + Record.push_back(FlagName.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RECORD_DIAG_FLAG), + Record, FlagName.str()); + } + + // FIXME: emit location + // FIXME: emit ranges + // FIXME: emit notes + // FIXME: emit fixits +} + +template +static void populateAndSort(std::vector &scribble, + llvm::DenseSet &set) { + scribble.clear(); + + for (typename llvm::DenseSet::iterator it = set.begin(), ei = set.end(); + it != ei; ++it) + scribble.push_back(*it); + + // Sort 'scribble' so we always have a deterministic ordering in the + // serialized file. + std::sort(scribble.begin(), scribble.end()); +} + +void SDiagsWriter::EmitCategoriesAndFileNames() { + + if (Categories.empty() && Files.empty()) + return; + + BlockEnterExit BlockEnter(Stream, BLOCK_STRINGS); + + // Emit the category names. + { + std::vector scribble; + populateAndSort(scribble, Categories); + for (std::vector::iterator it = scribble.begin(), + ei = scribble.end(); it != ei ; ++it) { + Record.clear(); + Record.push_back(RECORD_CATEGORY); + StringRef catName = DiagnosticIDs::getCategoryNameFromID(*it); + Record.push_back(catName.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RECORD_CATEGORY), Record, catName); + } + } + + // Emit the file names. + { + std::vector scribble; + populateAndSort(scribble, Files); + for (std::vector::iterator it = scribble.begin(), + ei = scribble.end(); it != ei; ++it) { + SourceManager &SM = Diags.getSourceManager(); + const FileEntry *FE = SM.getFileEntryForID(*it); + StringRef Name = FE->getName(); + + Record.clear(); + Record.push_back(Name.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(RECORD_FILENAME), Record, Name); + } + } + +} + +void SDiagsWriter::EndSourceFile() { + EmitCategoriesAndFileNames(); + + // Write the generated bitstream to "Out". + OS->write((char *)&Buffer.front(), Buffer.size()); + OS->flush(); + + OS.reset(0); +} +