abrahamcd created this revision.
Herald added a subscriber: mgorny.
Herald added a project: All.
abrahamcd requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.
Work in progress to enable Clang to emit SARIF diagnostics.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D131632
Files:
clang/include/clang/Frontend/SARIFDiagnostic.h
clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
clang/lib/Frontend/CMakeLists.txt
clang/lib/Frontend/CompilerInstance.cpp
clang/lib/Frontend/FrontendAction.cpp
clang/lib/Frontend/SARIFDiagnostic.cpp
clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
clang/unittests/Frontend/CMakeLists.txt
clang/unittests/Frontend/SARIFDiagnosticTest.cpp
clang/unittests/Frontend/sarif-diagnostics.cpp
Index: clang/unittests/Frontend/sarif-diagnostics.cpp
===================================================================
--- /dev/null
+++ clang/unittests/Frontend/sarif-diagnostics.cpp
@@ -0,0 +1,138 @@
+// RUN: %clang -fdiagnostics-format=sarif %s -o %t.exe -DGTEST
+// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif %s 2>
+// %t.diags || true RUN: %t.exe < %t.diags
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FileUtilities.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Program.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <filesystem>
+#include <vector>
+
+namespace {
+
+constexpr llvm::StringRef BrokenProgram =
+ R"(// Example errors below start on line 2
+void main() {
+ int i = hello;
+
+ float test = 1a.0;
+
+ if (true)
+ bool Yes = true;
+ return;
+
+ bool j = hi;
+}
+})";
+
+TEST(SARIFDiagnosticTest, TestFields) {
+ llvm::SmallString<256> SearchDir;
+ llvm::sys::fs::current_path(SearchDir);
+
+ SearchDir.append("/../../../bin");
+ // ASSERT_EQ(SearchDir.str(), "hi");
+ llvm::ErrorOr<std::string> ClangPathOrErr =
+ llvm::sys::findProgramByName("clang", {SearchDir});
+ ASSERT_TRUE(ClangPathOrErr);
+ const std::string &ClangPath = *ClangPathOrErr;
+ // ASSERT_EQ(ClangPath, "hi");
+
+ llvm::ErrorOr<std::string> EchoPathOrErr =
+ llvm::sys::findProgramByName("echo");
+ ASSERT_TRUE(EchoPathOrErr);
+ const std::string &EchoPath = *EchoPathOrErr;
+
+ int EchoInputFD;
+ llvm::SmallString<32> EchoInputFile, EchoOutputFile;
+ llvm::sys::fs::createTemporaryFile("echo-input", "", EchoInputFD,
+ EchoInputFile);
+ llvm::sys::fs::createTemporaryFile("echo-output", "", EchoOutputFile);
+ llvm::FileRemover InputRemover(EchoInputFile.c_str());
+ llvm::FileRemover OutputRemover(EchoOutputFile.c_str());
+
+ llvm::Optional<llvm::StringRef> Redirects[] = {
+ EchoInputFile.str(), EchoOutputFile.str(), llvm::StringRef("")};
+
+ int RunResult = llvm::sys::ExecuteAndWait(EchoPath, {"echo", BrokenProgram},
+ llvm::None, Redirects);
+ ASSERT_EQ(RunResult, 0);
+
+ // auto EchoOutputBuf = llvm::MemoryBuffer::getFile(EchoOutputFile.c_str());
+ // ASSERT_TRUE(EchoOutputBuf);
+ // llvm::StringRef EchoOutput = EchoOutputBuf.get()->getBuffer();
+ // ASSERT_EQ(EchoOutput.str(), "hi");
+
+ llvm::SmallString<32> ClangErrFile;
+ llvm::sys::fs::createTemporaryFile("clang-err", "", ClangErrFile);
+ llvm::FileRemover ClangErrRemover(ClangErrFile.c_str());
+
+ llvm::Optional<llvm::StringRef> ClangRedirects[] = {
+ EchoOutputFile.str(), llvm::StringRef(""), ClangErrFile.str()};
+ llvm::StringRef Args[] = {"clang",
+ "-xc++",
+ "-",
+ "-fsyntax-only",
+ "-Wall",
+ "-Wextra",
+ "-fdiagnostics-format=sarif"};
+
+ int ClangResult =
+ llvm::sys::ExecuteAndWait(ClangPath, Args, llvm::None, ClangRedirects);
+ ASSERT_EQ(ClangResult, 1);
+
+ // auto ClangOutputBuf = llvm::MemoryBuffer::getFile(ClangOutputFile.c_str());
+ // ASSERT_TRUE(ClangOutputBuf);
+ // llvm::StringRef ClangOutput = ClangOutputBuf.get()->getBuffer();
+ // ASSERT_EQ(ClangOutput.str(), "hi");
+
+ auto ClangErrBuf = llvm::MemoryBuffer::getFile(ClangErrFile.c_str());
+ ASSERT_TRUE(ClangErrBuf);
+ llvm::StringRef ClangErr = ClangErrBuf.get()->getBuffer();
+ ASSERT_EQ(ClangErr.str(), "hi");
+
+ llvm::Expected<llvm::json::Value> Value = llvm::json::parse(ClangErr.str());
+ ASSERT_FALSE(!Value);
+
+ llvm::json::Object *SarifDoc = Value->getAsObject();
+
+ const llvm::json::Array *Runs = SarifDoc->getArray("runs");
+ const llvm::json::Object *TheRun = Runs->back().getAsObject();
+ const llvm::json::Array *Results = TheRun->getArray("results");
+
+ // Check Artifacts
+ const llvm::json::Array *Artifacts = TheRun->getArray("artifacts");
+ const llvm::json::Object *TheArtifact = Artifacts->back().getAsObject();
+ const llvm::json::Object *Location = TheArtifact->getObject("location");
+
+ ASSERT_TRUE(Location->getInteger("index").hasValue());
+ ASSERT_TRUE(Location->getString("uri").hasValue());
+
+ EXPECT_EQ(Location->getInteger("index").getValue(), 0);
+ EXPECT_EQ(Location->getString("uri").getValue(), "file://<stdin>");
+
+ // Check Driver
+ const llvm::json::Object *Driver =
+ TheRun->getObject("tool")->getObject("driver");
+
+ ASSERT_TRUE(Driver->getString("name").hasValue());
+ ASSERT_TRUE(Driver->getString("fullName").hasValue());
+
+ EXPECT_EQ(Driver->getString("name").getValue(), "clang");
+ EXPECT_EQ(Driver->getString("fullName").getValue(), "clang-15");
+
+ // Check Rules
+ const llvm::json::Array *Rules = Driver->getArray("rules");
+ std::vector<std::string> IDs;
+
+
+
+
+}
+
+} // namespace
Index: clang/unittests/Frontend/SARIFDiagnosticTest.cpp
===================================================================
--- /dev/null
+++ clang/unittests/Frontend/SARIFDiagnosticTest.cpp
@@ -0,0 +1,100 @@
+// //===- unittests/Frontend/SARIFDiagnosticTest.cpp - ------------------------===//
+// //
+// // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// // See https://llvm.org/LICENSE.txt for license information.
+// // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// //
+// //===----------------------------------------------------------------------===//
+
+// #include "clang/Frontend/SARIFDiagnostic.h"
+// #include "clang/Basic/FileManager.h"
+// #include "clang/Basic/LangOptions.h"
+// #include "clang/Basic/SourceManager.h"
+// #include "llvm/Support/SmallVectorMemoryBuffer.h"
+// #include "gtest/gtest.h"
+
+// using namespace llvm;
+// using namespace clang;
+
+// namespace {
+
+// /// Prints a diagnostic with the given DiagnosticOptions and the given
+// /// SourceLocation and returns the printed diagnostic text.
+// static std::string PrintDiag(const DiagnosticOptions &Opts, FullSourceLoc Loc) {
+// std::string Out;
+// llvm::raw_string_ostream OS(Out);
+// clang::LangOptions LangOpts;
+// // Owned by SARIFDiagnostic.
+// DiagnosticOptions *DiagOpts = new DiagnosticOptions(Opts);
+// SARIFDiagnostic Diag(OS, LangOpts, DiagOpts);
+// // Emit a dummy diagnostic that is just 'message'.
+// Diag.emitDiagnostic(Loc, DiagnosticsEngine::Level::Warning, "message",
+// /*Ranges=*/{}, /*FixItHints=*/{});
+// OS.flush();
+// return Out;
+// }
+
+// TEST(SARIFDiagnostic, ShowLine) {
+// // Create dummy FileManager and SourceManager.
+// FileSystemOptions FSOpts;
+// FileManager FileMgr(FSOpts);
+// IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs);
+// DiagnosticsEngine DiagEngine(DiagID, new DiagnosticOptions,
+// new IgnoringDiagConsumer());
+// SourceManager SrcMgr(DiagEngine, FileMgr);
+
+// // Create a dummy file with some contents to produce a test SourceLocation.
+// const llvm::StringRef file_path = "main.cpp";
+// const llvm::StringRef main_file_contents = "some\nsource\ncode\n";
+// const clang::FileEntryRef fe = FileMgr.getVirtualFileRef(
+// file_path,
+// /*Size=*/static_cast<off_t>(main_file_contents.size()),
+// /*ModificationTime=*/0);
+
+// llvm::SmallVector<char, 64> buffer;
+// buffer.append(main_file_contents.begin(), main_file_contents.end());
+// auto file_contents = std::make_unique<llvm::SmallVectorMemoryBuffer>(
+// std::move(buffer), file_path, /*RequiresNullTerminator=*/false);
+// SrcMgr.overrideFileContents(fe, std::move(file_contents));
+
+// // Create the actual file id and use it as the main file.
+// clang::FileID fid =
+// SrcMgr.createFileID(fe, SourceLocation(), clang::SrcMgr::C_User);
+// SrcMgr.setMainFileID(fid);
+
+// // Create the source location for the test diagnostic.
+// FullSourceLoc Loc(SrcMgr.translateLineCol(fid, /*Line=*/1, /*Col=*/2),
+// SrcMgr);
+
+// DiagnosticOptions DiagOpts;
+// DiagOpts.ShowLine = true;
+// DiagOpts.ShowColumn = true;
+// // Hide printing the source line/caret to make the diagnostic shorter and it's
+// // not relevant for this test.
+// DiagOpts.ShowCarets = false;
+// EXPECT_EQ("main.cpp:1:2: warning: message\n", PrintDiag(DiagOpts, Loc));
+
+// // Check that ShowLine doesn't influence the Vi/MSVC diagnostic formats as its
+// // a Clang-specific diagnostic option.
+// DiagOpts.setFormat(TextDiagnosticFormat::Vi);
+// DiagOpts.ShowLine = false;
+// EXPECT_EQ("main.cpp +1:2: warning: message\n", PrintDiag(DiagOpts, Loc));
+
+// DiagOpts.setFormat(TextDiagnosticFormat::MSVC);
+// DiagOpts.ShowLine = false;
+// EXPECT_EQ("main.cpp(1,2): warning: message\n", PrintDiag(DiagOpts, Loc));
+
+// // Reset back to the Clang format.
+// DiagOpts.setFormat(TextDiagnosticFormat::Clang);
+
+// // Hide line number but show column.
+// DiagOpts.ShowLine = false;
+// EXPECT_EQ("main.cpp:2: warning: message\n", PrintDiag(DiagOpts, Loc));
+
+// // Show line number but hide column.
+// DiagOpts.ShowLine = true;
+// DiagOpts.ShowColumn = false;
+// EXPECT_EQ("main.cpp:1: warning: message\n", PrintDiag(DiagOpts, Loc));
+// }
+
+// } // anonymous namespace
Index: clang/unittests/Frontend/CMakeLists.txt
===================================================================
--- clang/unittests/Frontend/CMakeLists.txt
+++ clang/unittests/Frontend/CMakeLists.txt
@@ -12,6 +12,8 @@
ParsedSourceLocationTest.cpp
PCHPreambleTest.cpp
OutputStreamTest.cpp
+ sarif-diagnostics.cpp
+ SARIFDiagnosticTest.cpp
TextDiagnosticTest.cpp
UtilsTest.cpp
)
Index: clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
===================================================================
--- /dev/null
+++ clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
@@ -0,0 +1,179 @@
+//===--- SARIFDiagnoSARIFPrinter.cpp - Diagnostic Printer -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This diagnostic client prints out their diagnostic messages.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/SARIFDiagnosticPrinter.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/Sarif.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/SARIFDiagnostic.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/JSON.h"
+#include <algorithm>
+using namespace clang;
+
+SARIFDiagnosticPrinter::SARIFDiagnosticPrinter(raw_ostream &os,
+ DiagnosticOptions *diags,
+ bool _OwnsOutputStream)
+ : OS(os), DiagOpts(diags),
+ OwnsOutputStream(_OwnsOutputStream) {}
+
+SARIFDiagnosticPrinter::~SARIFDiagnosticPrinter() {
+ if (OwnsOutputStream)
+ delete &OS;
+}
+
+void SARIFDiagnosticPrinter::BeginSourceFile(const LangOptions &LO,
+ const Preprocessor *PP) {
+ // Build the SARIFDiagnostic utility.
+ assert(hasSarifWriter() && "Writer not set!");
+ SARIFDiag.reset(new SARIFDiagnostic(OS, LO, &*DiagOpts, &*Writer));
+ // Initialize the SARIF object.
+ Writer->createRun("clang", Prefix);
+}
+static std::string serializeSarifDocument(llvm::json::Object &&Doc) {
+ std::string Output;
+ llvm::json::Value value(std::move(Doc));
+ llvm::raw_string_ostream OS{Output};
+ OS << llvm::formatv("{0}", value);
+ OS.flush();
+ return Output;
+}
+
+void SARIFDiagnosticPrinter::EndSourceFile() {
+ Writer->endRun();
+ // const llvm::json::Object &Doc = Writer->createDocument();
+ // llvm::json::Value value(std::move(Doc));
+ llvm::json::Value value(std::move(Writer->createDocument()));
+ OS << value;
+ OS.flush();const SarifRule &Rule =
+ SarifRule::create()
+ .setRuleId("clang.unittest")
+ .setDescription("Example rule created during unit tests")
+ .setName("clang unit test");
+ SARIFDiag.reset();
+}
+
+/// Print any diagnostic option information to a raw_ostream.
+///
+/// This implements all of the logic for adding diagnostic options to a message
+/// (via OS). Each relevant option is comma separated and all are enclosed in
+/// the standard bracketing: " [...]".
+static void printDiagnosticOptions(raw_ostream &OS, /// Seems that all this information might be important to add to sarif, but we dont need to just be printing it
+ DiagnosticsEngine::Level Level,
+ const Diagnostic &Info,
+ const DiagnosticOptions &DiagOpts) {
+ bool Started = false;
+ if (DiagOpts.ShowOptionNames) {
+ // Handle special cases for non-warnings early.
+ if (Info.getID() == diag::fatal_too_many_errors) {
+ OS << " [-ferror-limit=]";
+ return;
+ }
+
+ // The code below is somewhat fragile because we are essentially trying to
+ // report to the user what happened by inferring what the diagnostic engine
+ // did. Eventually it might make more sense to have the diagnostic engine
+ // include some "why" information in the diagnostic.
+
+ // If this is a warning which has been mapped to an error by the user (as
+ // inferred by checking whether the default mapping is to an error) then
+ // flag it as such. Note that diagnostics could also have been mapped by a
+ // pragma, but we don't currently have a way to distinguish this.
+ if (Level == DiagnosticsEngine::Error &&
+ DiagnosticIDs::isBuiltinWarningOrExtension(Info.getID()) &&
+ !DiagnosticIDs::isDefaultMappingAsError(Info.getID())) {
+ OS << " [-Werror";
+ Started = true;
+ }
+
+ StringRef Opt = DiagnosticIDs::getWarningOptionForDiag(Info.getID());
+ if (!Opt.empty()) {
+ OS << (Started ? "," : " [")
+ << (Level == DiagnosticsEngine::Remark ? "-R" : "-W") << Opt;
+ StringRef OptValue = Info.getDiags()->getFlagValue();
+ if (!OptValue.empty())
+ OS << "=" << OptValue;
+ Started = true;
+ }
+ }
+
+ // If the user wants to see category information, include it too.
+ if (DiagOpts.ShowCategories) {
+ unsigned DiagCategory =
+ DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
+ if (DiagCategory) {
+ OS << (Started ? "," : " [");
+ Started = true;
+ if (DiagOpts.ShowCategories == 1)
+ OS << DiagCategory;
+ else {
+ assert(DiagOpts.ShowCategories == 2 && "Invalid ShowCategories value");
+ OS << DiagnosticIDs::getCategoryNameFromID(DiagCategory);
+ }
+ }
+ }
+ if (Started)
+ OS << ']';
+}
+
+void SARIFDiagnosticPrinter::HandleDiagnostic(DiagnosticsEngine::Level Level,
+ const Diagnostic &Info) {
+ // Default implementation (Warnings/errors count). // Keeps track of the number of errors
+ DiagnosticConsumer::HandleDiagnostic(Level, Info);
+
+ // Render the diagnostic message into a temporary buffer eagerly. We'll use
+ // this later as we print out the diagnostic to the terminal.
+ SmallString<100> OutStr;
+ Info.FormatDiagnostic(OutStr);
+
+ llvm::raw_svector_ostream DiagMessageStream(OutStr);
+ // printDiagnosticOptions(DiagMessageStream, Level, Info, *DiagOpts);
+
+ // Keeps track of the starting position of the location
+ // information (e.g., "foo.c:10:4:") that precedes the error
+ // message. We use this information to determine how long the
+ // file+line+column number prefix is.
+ uint64_t StartOfLocationInfo = OS.tell();
+
+ if (!Prefix.empty())
+ OS << Prefix << ": ";
+
+ // Use a dedicated, simpler path for diagnostics without a valid location.
+ // This is important as if the location is missing, we may be emitting
+ // diagnostics in a context that lacks language options, a source manager, or
+ // other infrastructure necessary when emitting more rich diagnostics.
+ if (!Info.getLocation().isValid()) {
+ SARIFDiagnostic::printDiagnosticLevel(OS, Level, DiagOpts->ShowColors);
+ SARIFDiagnostic::printDiagnosticMessage(
+ OS, /*IsSupplemental=*/Level == DiagnosticsEngine::Note,
+ DiagMessageStream.str(), OS.tell() - StartOfLocationInfo,
+ DiagOpts->MessageLength, DiagOpts->ShowColors);
+ OS.flush();
+ return;
+ }
+
+ // Assert that the rest of our infrastructure is setup properly.
+ assert(DiagOpts && "Unexpected diagnostic without options set");
+ assert(Info.hasSourceManager() &&
+ "Unexpected diagnostic with no source manager");
+ assert(SARIFDiag && "Unexpected diagnostic outside source file processing");
+ OS << Info.getID();
+
+ SARIFDiag->emitDiagnostic(
+ FullSourceLoc(Info.getLocation(), Info.getSourceManager()), Level,
+ DiagMessageStream.str(), Info.getRanges(), Info.getFixItHints());
+
+ OS.flush();
+}
Index: clang/lib/Frontend/SARIFDiagnostic.cpp
===================================================================
--- /dev/null
+++ clang/lib/Frontend/SARIFDiagnostic.cpp
@@ -0,0 +1,1374 @@
+//===--- SARIFDiagnostic.cpp - Text Diagnostic Pretty-Printing
+//-------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/SARIFDiagnostic.h"
+#include "clang/Basic/CharInfo.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/Sarif.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Locale.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+
+using namespace clang;
+
+static const enum raw_ostream::Colors noteColor = raw_ostream::BLACK;
+static const enum raw_ostream::Colors remarkColor = raw_ostream::BLUE;
+static const enum raw_ostream::Colors fixitColor = raw_ostream::GREEN;
+static const enum raw_ostream::Colors caretColor = raw_ostream::GREEN;
+static const enum raw_ostream::Colors warningColor = raw_ostream::MAGENTA;
+static const enum raw_ostream::Colors templateColor = raw_ostream::CYAN;
+static const enum raw_ostream::Colors errorColor = raw_ostream::RED;
+static const enum raw_ostream::Colors fatalColor = raw_ostream::RED;
+// Used for changing only the bold attribute.
+static const enum raw_ostream::Colors savedColor = raw_ostream::SAVEDCOLOR;
+
+/// Add highlights to differences in template strings.
+static void applyTemplateHighlighting(raw_ostream &OS, StringRef Str,
+ bool &Normal, bool Bold) {
+ while (true) {
+ size_t Pos = Str.find(ToggleHighlight);
+ OS << Str.slice(0, Pos);
+ if (Pos == StringRef::npos)
+ break;
+
+ Str = Str.substr(Pos + 1);
+ if (Normal)
+ OS.changeColor(templateColor, true);
+ else {
+ OS.resetColor();
+ if (Bold)
+ OS.changeColor(savedColor, true);
+ }
+ Normal = !Normal;
+ }
+}
+
+/// Number of spaces to indent when word-wrapping.
+const unsigned WordWrapIndentation = 6;
+
+static int bytesSincePreviousTabOrLineBegin(StringRef SourceLine, size_t i) {
+ int bytes = 0;
+ while (0 < i) {
+ if (SourceLine[--i] == '\t')
+ break;
+ ++bytes;
+ }
+ return bytes;
+}
+
+/// returns a printable representation of first item from input range
+///
+/// This function returns a printable representation of the next item in a line
+/// of source. If the next byte begins a valid and printable character, that
+/// character is returned along with 'true'.
+///
+/// Otherwise, if the next byte begins a valid, but unprintable character, a
+/// printable, escaped representation of the character is returned, along with
+/// 'false'. Otherwise a printable, escaped representation of the next byte
+/// is returned along with 'false'.
+///
+/// \note The index is updated to be used with a subsequent call to
+/// printableTextForNextCharacter.
+///
+/// \param SourceLine The line of source
+/// \param i Pointer to byte index,
+/// \param TabStop used to expand tabs
+/// \return pair(printable text, 'true' iff original text was printable)
+///
+static std::pair<SmallString<16>, bool>
+printableTextForNextCharacter(StringRef SourceLine, size_t *i,
+ unsigned TabStop) {
+ assert(i && "i must not be null");
+ assert(*i < SourceLine.size() && "must point to a valid index");
+
+ if (SourceLine[*i] == '\t') {
+ assert(0 < TabStop && TabStop <= DiagnosticOptions::MaxTabStop &&
+ "Invalid -ftabstop value");
+ unsigned col = bytesSincePreviousTabOrLineBegin(SourceLine, *i);
+ unsigned NumSpaces = TabStop - col % TabStop;
+ assert(0 < NumSpaces && NumSpaces <= TabStop &&
+ "Invalid computation of space amt");
+ ++(*i);
+
+ SmallString<16> expandedTab;
+ expandedTab.assign(NumSpaces, ' ');
+ return std::make_pair(expandedTab, true);
+ }
+
+ unsigned char const *begin, *end;
+ begin = reinterpret_cast<unsigned char const *>(&*(SourceLine.begin() + *i));
+ end = begin + (SourceLine.size() - *i);
+
+ if (llvm::isLegalUTF8Sequence(begin, end)) {
+ llvm::UTF32 c;
+ llvm::UTF32 *cptr = &c;
+ unsigned char const *original_begin = begin;
+ unsigned char const *cp_end =
+ begin + llvm::getNumBytesForUTF8(SourceLine[*i]);
+
+ llvm::ConversionResult res = llvm::ConvertUTF8toUTF32(
+ &begin, cp_end, &cptr, cptr + 1, llvm::strictConversion);
+ (void)res;
+ assert(llvm::conversionOK == res);
+ assert(0 < begin - original_begin &&
+ "we must be further along in the string now");
+ *i += begin - original_begin;
+
+ if (!llvm::sys::locale::isPrint(c)) {
+ // If next character is valid UTF-8, but not printable
+ SmallString<16> expandedCP("<U+>");
+ while (c) {
+ expandedCP.insert(expandedCP.begin() + 3, llvm::hexdigit(c % 16));
+ c /= 16;
+ }
+ while (expandedCP.size() < 8)
+ expandedCP.insert(expandedCP.begin() + 3, llvm::hexdigit(0));
+ return std::make_pair(expandedCP, false);
+ }
+
+ // If next character is valid UTF-8, and printable
+ return std::make_pair(SmallString<16>(original_begin, cp_end), true);
+ }
+
+ // If next byte is not valid UTF-8 (and therefore not printable)
+ SmallString<16> expandedByte("<XX>");
+ unsigned char byte = SourceLine[*i];
+ expandedByte[1] = llvm::hexdigit(byte / 16);
+ expandedByte[2] = llvm::hexdigit(byte % 16);
+ ++(*i);
+ return std::make_pair(expandedByte, false);
+}
+
+static void expandTabs(std::string &SourceLine, unsigned TabStop) {
+ size_t i = SourceLine.size();
+ while (i > 0) {
+ i--;
+ if (SourceLine[i] != '\t')
+ continue;
+ size_t tmp_i = i;
+ std::pair<SmallString<16>, bool> res =
+ printableTextForNextCharacter(SourceLine, &tmp_i, TabStop);
+ SourceLine.replace(i, 1, res.first.c_str());
+ }
+}
+
+/// This function takes a raw source line and produces a mapping from the bytes
+/// of the printable representation of the line to the columns those printable
+/// characters will appear at (numbering the first column as 0).
+///
+/// If a byte 'i' corresponds to multiple columns (e.g. the byte contains a tab
+/// character) then the array will map that byte to the first column the
+/// tab appears at and the next value in the map will have been incremented
+/// more than once.
+///
+/// If a byte is the first in a sequence of bytes that together map to a single
+/// entity in the output, then the array will map that byte to the appropriate
+/// column while the subsequent bytes will be -1.
+///
+/// The last element in the array does not correspond to any byte in the input
+/// and instead is the number of columns needed to display the source
+///
+/// example: (given a tabstop of 8)
+///
+/// "a \t \u3042" -> {0,1,2,8,9,-1,-1,11}
+///
+/// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to
+/// display)
+static void byteToColumn(StringRef SourceLine, unsigned TabStop,
+ SmallVectorImpl<int> &out) {
+ out.clear();
+
+ if (SourceLine.empty()) {
+ out.resize(1u, 0);
+ return;
+ }
+
+ out.resize(SourceLine.size() + 1, -1);
+
+ int columns = 0;
+ size_t i = 0;
+ while (i < SourceLine.size()) {
+ out[i] = columns;
+ std::pair<SmallString<16>, bool> res =
+ printableTextForNextCharacter(SourceLine, &i, TabStop);
+ columns += llvm::sys::locale::columnWidth(res.first);
+ }
+ out.back() = columns;
+}
+
+/// This function takes a raw source line and produces a mapping from columns
+/// to the byte of the source line that produced the character displaying at
+/// that column. This is the inverse of the mapping produced by byteToColumn()
+///
+/// The last element in the array is the number of bytes in the source string
+///
+/// example: (given a tabstop of 8)
+///
+/// "a \t \u3042" -> {0,1,2,-1,-1,-1,-1,-1,3,4,-1,7}
+///
+/// (\\u3042 is represented in UTF-8 by three bytes and takes two columns to
+/// display)
+static void columnToByte(StringRef SourceLine, unsigned TabStop,
+ SmallVectorImpl<int> &out) {
+ out.clear();
+
+ if (SourceLine.empty()) {
+ out.resize(1u, 0);
+ return;
+ }
+
+ int columns = 0;
+ size_t i = 0;
+ while (i < SourceLine.size()) {
+ out.resize(columns + 1, -1);
+ out.back() = i;
+ std::pair<SmallString<16>, bool> res =
+ printableTextForNextCharacter(SourceLine, &i, TabStop);
+ columns += llvm::sys::locale::columnWidth(res.first);
+ }
+ out.resize(columns + 1, -1);
+ out.back() = i;
+}
+
+namespace {
+struct SourceColumnMap {
+ SourceColumnMap(StringRef SourceLine, unsigned TabStop)
+ : m_SourceLine(SourceLine) {
+
+ ::byteToColumn(SourceLine, TabStop, m_byteToColumn);
+ ::columnToByte(SourceLine, TabStop, m_columnToByte);
+
+ assert(m_byteToColumn.size() == SourceLine.size() + 1);
+ assert(0 < m_byteToColumn.size() && 0 < m_columnToByte.size());
+ assert(m_byteToColumn.size() ==
+ static_cast<unsigned>(m_columnToByte.back() + 1));
+ assert(static_cast<unsigned>(m_byteToColumn.back() + 1) ==
+ m_columnToByte.size());
+ }
+ int columns() const { return m_byteToColumn.back(); }
+ int bytes() const { return m_columnToByte.back(); }
+
+ /// Map a byte to the column which it is at the start of, or return -1
+ /// if it is not at the start of a column (for a UTF-8 trailing byte).
+ int byteToColumn(int n) const {
+ assert(0 <= n && n < static_cast<int>(m_byteToColumn.size()));
+ return m_byteToColumn[n];
+ }
+
+ /// Map a byte to the first column which contains it.
+ int byteToContainingColumn(int N) const {
+ assert(0 <= N && N < static_cast<int>(m_byteToColumn.size()));
+ while (m_byteToColumn[N] == -1)
+ --N;
+ return m_byteToColumn[N];
+ }
+
+ /// Map a column to the byte which starts the column, or return -1 if
+ /// the column the second or subsequent column of an expanded tab or similar
+ /// multi-column entity.
+ int columnToByte(int n) const {
+ assert(0 <= n && n < static_cast<int>(m_columnToByte.size()));
+ return m_columnToByte[n];
+ }
+
+ /// Map from a byte index to the next byte which starts a column.
+ int startOfNextColumn(int N) const {
+ assert(0 <= N && N < static_cast<int>(m_byteToColumn.size() - 1));
+ while (byteToColumn(++N) == -1) {
+ }
+ return N;
+ }
+
+ /// Map from a byte index to the previous byte which starts a column.
+ int startOfPreviousColumn(int N) const {
+ assert(0 < N && N < static_cast<int>(m_byteToColumn.size()));
+ while (byteToColumn(--N) == -1) {
+ }
+ return N;
+ }
+
+ StringRef getSourceLine() const { return m_SourceLine; }
+
+private:
+ const std::string m_SourceLine;
+ SmallVector<int, 200> m_byteToColumn;
+ SmallVector<int, 200> m_columnToByte;
+};
+} // end anonymous namespace
+
+/// When the source code line we want to print is too long for
+/// the terminal, select the "interesting" region.
+static void selectInterestingSourceRegion(std::string &SourceLine,
+ std::string &CaretLine,
+ std::string &FixItInsertionLine,
+ unsigned Columns,
+ const SourceColumnMap &map) {
+ unsigned CaretColumns = CaretLine.size();
+ unsigned FixItColumns = llvm::sys::locale::columnWidth(FixItInsertionLine);
+ unsigned MaxColumns = std::max(static_cast<unsigned>(map.columns()),
+ std::max(CaretColumns, FixItColumns));
+ // if the number of columns is less than the desired number we're done
+ if (MaxColumns <= Columns)
+ return;
+
+ // No special characters are allowed in CaretLine.
+ assert(CaretLine.end() ==
+ llvm::find_if(CaretLine, [](char c) { return c < ' ' || '~' < c; }));
+
+ // Find the slice that we need to display the full caret line
+ // correctly.
+ unsigned CaretStart = 0, CaretEnd = CaretLine.size();
+ for (; CaretStart != CaretEnd; ++CaretStart)
+ if (!isWhitespace(CaretLine[CaretStart]))
+ break;
+
+ for (; CaretEnd != CaretStart; --CaretEnd)
+ if (!isWhitespace(CaretLine[CaretEnd - 1]))
+ break;
+
+ // caret has already been inserted into CaretLine so the above whitespace
+ // check is guaranteed to include the caret
+
+ // If we have a fix-it line, make sure the slice includes all of the
+ // fix-it information.
+ if (!FixItInsertionLine.empty()) {
+ unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size();
+ for (; FixItStart != FixItEnd; ++FixItStart)
+ if (!isWhitespace(FixItInsertionLine[FixItStart]))
+ break;
+
+ for (; FixItEnd != FixItStart; --FixItEnd)
+ if (!isWhitespace(FixItInsertionLine[FixItEnd - 1]))
+ break;
+
+ // We can safely use the byte offset FixItStart as the column offset
+ // because the characters up until FixItStart are all ASCII whitespace
+ // characters.
+ unsigned FixItStartCol = FixItStart;
+ unsigned FixItEndCol =
+ llvm::sys::locale::columnWidth(FixItInsertionLine.substr(0, FixItEnd));
+
+ CaretStart = std::min(FixItStartCol, CaretStart);
+ CaretEnd = std::max(FixItEndCol, CaretEnd);
+ }
+
+ // CaretEnd may have been set at the middle of a character
+ // If it's not at a character's first column then advance it past the current
+ // character.
+ while (static_cast<int>(CaretEnd) < map.columns() &&
+ -1 == map.columnToByte(CaretEnd))
+ ++CaretEnd;
+
+ assert((static_cast<int>(CaretStart) > map.columns() ||
+ -1 != map.columnToByte(CaretStart)) &&
+ "CaretStart must not point to a column in the middle of a source"
+ " line character");
+ assert((static_cast<int>(CaretEnd) > map.columns() ||
+ -1 != map.columnToByte(CaretEnd)) &&
+ "CaretEnd must not point to a column in the middle of a source line"
+ " character");
+
+ // CaretLine[CaretStart, CaretEnd) contains all of the interesting
+ // parts of the caret line. While this slice is smaller than the
+ // number of columns we have, try to grow the slice to encompass
+ // more context.
+
+ unsigned SourceStart =
+ map.columnToByte(std::min<unsigned>(CaretStart, map.columns()));
+ unsigned SourceEnd =
+ map.columnToByte(std::min<unsigned>(CaretEnd, map.columns()));
+
+ unsigned CaretColumnsOutsideSource =
+ CaretEnd - CaretStart -
+ (map.byteToColumn(SourceEnd) - map.byteToColumn(SourceStart));
+
+ char const *front_ellipse = " ...";
+ char const *front_space = " ";
+ char const *back_ellipse = "...";
+ unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse);
+
+ unsigned TargetColumns = Columns;
+ // Give us extra room for the ellipses
+ // and any of the caret line that extends past the source
+ if (TargetColumns > ellipses_space + CaretColumnsOutsideSource)
+ TargetColumns -= ellipses_space + CaretColumnsOutsideSource;
+
+ while (SourceStart > 0 || SourceEnd < SourceLine.size()) {
+ bool ExpandedRegion = false;
+
+ if (SourceStart > 0) {
+ unsigned NewStart = map.startOfPreviousColumn(SourceStart);
+
+ // Skip over any whitespace we see here; we're looking for
+ // another bit of interesting text.
+ // FIXME: Detect non-ASCII whitespace characters too.
+ while (NewStart && isWhitespace(SourceLine[NewStart]))
+ NewStart = map.startOfPreviousColumn(NewStart);
+
+ // Skip over this bit of "interesting" text.
+ while (NewStart) {
+ unsigned Prev = map.startOfPreviousColumn(NewStart);
+ if (isWhitespace(SourceLine[Prev]))
+ break;
+ NewStart = Prev;
+ }
+
+ assert(map.byteToColumn(NewStart) != -1);
+ unsigned NewColumns =
+ map.byteToColumn(SourceEnd) - map.byteToColumn(NewStart);
+ if (NewColumns <= TargetColumns) {
+ SourceStart = NewStart;
+ ExpandedRegion = true;
+ }
+ }
+
+ if (SourceEnd < SourceLine.size()) {
+ unsigned NewEnd = map.startOfNextColumn(SourceEnd);
+
+ // Skip over any whitespace we see here; we're looking for
+ // another bit of interesting text.
+ // FIXME: Detect non-ASCII whitespace characters too.
+ while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd]))
+ NewEnd = map.startOfNextColumn(NewEnd);
+
+ // Skip over this bit of "interesting" text.
+ while (NewEnd < SourceLine.size() && isWhitespace(SourceLine[NewEnd]))
+ NewEnd = map.startOfNextColumn(NewEnd);
+
+ assert(map.byteToColumn(NewEnd) != -1);
+ unsigned NewColumns =
+ map.byteToColumn(NewEnd) - map.byteToColumn(SourceStart);
+ if (NewColumns <= TargetColumns) {
+ SourceEnd = NewEnd;
+ ExpandedRegion = true;
+ }
+ }
+
+ if (!ExpandedRegion)
+ break;
+ }
+
+ CaretStart = map.byteToColumn(SourceStart);
+ CaretEnd = map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource;
+
+ // [CaretStart, CaretEnd) is the slice we want. Update the various
+ // output lines to show only this slice, with two-space padding
+ // before the lines so that it looks nicer.
+
+ assert(CaretStart != (unsigned)-1 && CaretEnd != (unsigned)-1 &&
+ SourceStart != (unsigned)-1 && SourceEnd != (unsigned)-1);
+ assert(SourceStart <= SourceEnd);
+ assert(CaretStart <= CaretEnd);
+
+ unsigned BackColumnsRemoved =
+ map.byteToColumn(SourceLine.size()) - map.byteToColumn(SourceEnd);
+ unsigned FrontColumnsRemoved = CaretStart;
+ unsigned ColumnsKept = CaretEnd - CaretStart;
+
+ // We checked up front that the line needed truncation
+ assert(FrontColumnsRemoved + ColumnsKept + BackColumnsRemoved > Columns);
+
+ // The line needs some truncation, and we'd prefer to keep the front
+ // if possible, so remove the back
+ if (BackColumnsRemoved > strlen(back_ellipse))
+ SourceLine.replace(SourceEnd, std::string::npos, back_ellipse);
+
+ // If that's enough then we're done
+ if (FrontColumnsRemoved + ColumnsKept <= Columns)
+ return;
+
+ // Otherwise remove the front as well
+ if (FrontColumnsRemoved > strlen(front_ellipse)) {
+ SourceLine.replace(0, SourceStart, front_ellipse);
+ CaretLine.replace(0, CaretStart, front_space);
+ if (!FixItInsertionLine.empty())
+ FixItInsertionLine.replace(0, CaretStart, front_space);
+ }
+}
+
+/// Skip over whitespace in the string, starting at the given
+/// index.
+///
+/// \returns The index of the first non-whitespace character that is
+/// greater than or equal to Idx or, if no such character exists,
+/// returns the end of the string.
+static unsigned skipWhitespace(unsigned Idx, StringRef Str, unsigned Length) {
+ while (Idx < Length && isWhitespace(Str[Idx]))
+ ++Idx;
+ return Idx;
+}
+
+/// If the given character is the start of some kind of
+/// balanced punctuation (e.g., quotes or parentheses), return the
+/// character that will terminate the punctuation.
+///
+/// \returns The ending punctuation character, if any, or the NULL
+/// character if the input character does not start any punctuation.
+static inline char findMatchingPunctuation(char c) {
+ switch (c) {
+ case '\'':
+ return '\'';
+ case '`':
+ return '\'';
+ case '"':
+ return '"';
+ case '(':
+ return ')';
+ case '[':
+ return ']';
+ case '{':
+ return '}';
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/// Find the end of the word starting at the given offset
+/// within a string.
+///
+/// \returns the index pointing one character past the end of the
+/// word.
+static unsigned findEndOfWord(unsigned Start, StringRef Str, unsigned Length,
+ unsigned Column, unsigned Columns) {
+ assert(Start < Str.size() && "Invalid start position!");
+ unsigned End = Start + 1;
+
+ // If we are already at the end of the string, take that as the word.
+ if (End == Str.size())
+ return End;
+
+ // Determine if the start of the string is actually opening
+ // punctuation, e.g., a quote or parentheses.
+ char EndPunct = findMatchingPunctuation(Str[Start]);
+ if (!EndPunct) {
+ // This is a normal word. Just find the first space character.
+ while (End < Length && !isWhitespace(Str[End]))
+ ++End;
+ return End;
+ }
+
+ // We have the start of a balanced punctuation sequence (quotes,
+ // parentheses, etc.). Determine the full sequence is.
+ SmallString<16> PunctuationEndStack;
+ PunctuationEndStack.push_back(EndPunct);
+ while (End < Length && !PunctuationEndStack.empty()) {
+ if (Str[End] == PunctuationEndStack.back())
+ PunctuationEndStack.pop_back();
+ else if (char SubEndPunct = findMatchingPunctuation(Str[End]))
+ PunctuationEndStack.push_back(SubEndPunct);
+
+ ++End;
+ }
+
+ // Find the first space character after the punctuation ended.
+ while (End < Length && !isWhitespace(Str[End]))
+ ++End;
+
+ unsigned PunctWordLength = End - Start;
+ if ( // If the word fits on this line
+ Column + PunctWordLength <= Columns ||
+ // ... or the word is "short enough" to take up the next line
+ // without too much ugly white space
+ PunctWordLength < Columns / 3)
+ return End; // Take the whole thing as a single "word".
+
+ // The whole quoted/parenthesized string is too long to print as a
+ // single "word". Instead, find the "word" that starts just after
+ // the punctuation and use that end-point instead. This will recurse
+ // until it finds something small enough to consider a word.
+ return findEndOfWord(Start + 1, Str, Length, Column + 1, Columns);
+}
+
+/// Print the given string to a stream, word-wrapping it to
+/// some number of columns in the process.
+///
+/// \param OS the stream to which the word-wrapping string will be
+/// emitted.
+/// \param Str the string to word-wrap and output.
+/// \param Columns the number of columns to word-wrap to.
+/// \param Column the column number at which the first character of \p
+/// Str will be printed. This will be non-zero when part of the first
+/// line has already been printed.
+/// \param Bold if the current text should be bold
+/// \param Indentation the number of spaces to indent any lines beyond
+/// the first line.
+/// \returns true if word-wrapping was required, or false if the
+/// string fit on the first line.
+static bool printWordWrapped(raw_ostream &OS, StringRef Str, unsigned Columns,
+ unsigned Column = 0, bool Bold = false,
+ unsigned Indentation = WordWrapIndentation) {
+ const unsigned Length = std::min(Str.find('\n'), Str.size());
+ bool TextNormal = true;
+
+ // The string used to indent each line.
+ SmallString<16> IndentStr;
+ IndentStr.assign(Indentation, ' ');
+ bool Wrapped = false;
+ for (unsigned WordStart = 0, WordEnd; WordStart < Length;
+ WordStart = WordEnd) {
+ // Find the beginning of the next word.
+ WordStart = skipWhitespace(WordStart, Str, Length);
+ if (WordStart == Length)
+ break;
+
+ // Find the end of this word.
+ WordEnd = findEndOfWord(WordStart, Str, Length, Column, Columns);
+
+ // Does this word fit on the current line?
+ unsigned WordLength = WordEnd - WordStart;
+ if (Column + WordLength < Columns) {
+ // This word fits on the current line; print it there.
+ if (WordStart) {
+ OS << ' ';
+ Column += 1;
+ }
+ applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength),
+ TextNormal, Bold);
+ Column += WordLength;
+ continue;
+ }
+
+ // This word does not fit on the current line, so wrap to the next
+ // line.
+ OS << '\n';
+ OS.write(&IndentStr[0], Indentation);
+ applyTemplateHighlighting(OS, Str.substr(WordStart, WordLength), TextNormal,
+ Bold);
+ Column = Indentation + WordLength;
+ Wrapped = true;
+ }
+
+ // Append any remaning text from the message with its existing formatting.
+ applyTemplateHighlighting(OS, Str.substr(Length), TextNormal, Bold);
+
+ assert(TextNormal && "Text highlighted at end of diagnostic message.");
+
+ return Wrapped;
+}
+
+SARIFDiagnostic::SARIFDiagnostic(raw_ostream &OS, const LangOptions &LangOpts,
+ DiagnosticOptions *DiagOpts,
+ SarifDocumentWriter *Writer)
+ : DiagnosticRenderer(LangOpts, DiagOpts), OS(OS), Writer(Writer) {}
+
+void SARIFDiagnostic::emitDiagnosticMessage(
+ FullSourceLoc Loc, PresumedLoc PLoc, DiagnosticsEngine::Level Level,
+ StringRef Message, ArrayRef<clang::CharSourceRange> Ranges,
+ DiagOrStoredDiag D) {
+ uint64_t StartOfLocationInfo = OS.tell();
+
+ // Emit the location of this particular diagnostic.
+ if (Loc.isValid())
+ emitDiagnosticLoc(Loc, PLoc, Level, Ranges);
+
+ if (DiagOpts->ShowColors)
+ OS.resetColor();
+
+ if (DiagOpts->ShowLevel)
+ printDiagnosticLevel(OS, Level, DiagOpts->ShowColors);
+ printDiagnosticMessage(OS,
+ /*IsSupplemental*/ Level == DiagnosticsEngine::Note,
+ Message, OS.tell() - StartOfLocationInfo,
+ DiagOpts->MessageLength, DiagOpts->ShowColors);
+}
+
+/*static*/ void SARIFDiagnostic::printDiagnosticLevel(
+ raw_ostream &OS, DiagnosticsEngine::Level Level, bool ShowColors) {
+ if (ShowColors) {
+ // Print diagnostic category in bold and color
+ switch (Level) {
+ case DiagnosticsEngine::Ignored:
+ llvm_unreachable("Invalid diagnostic type");
+ case DiagnosticsEngine::Note:
+ OS.changeColor(noteColor, true);
+ break;
+ case DiagnosticsEngine::Remark:
+ OS.changeColor(remarkColor, true);
+ break;
+ case DiagnosticsEngine::Warning:
+ OS.changeColor(warningColor, true);
+ break;
+ case DiagnosticsEngine::Error:
+ OS.changeColor(errorColor, true);
+ break;
+ case DiagnosticsEngine::Fatal:
+ OS.changeColor(fatalColor, true);
+ break;
+ }
+ }
+
+ switch (Level) {
+ case DiagnosticsEngine::Ignored:
+ llvm_unreachable("Invalid diagnostic type");
+ case DiagnosticsEngine::Note:
+ OS << "note: ";
+ break;
+ case DiagnosticsEngine::Remark:
+ OS << "remark: ";
+ break;
+ case DiagnosticsEngine::Warning:
+ OS << "warning: ";
+ break;
+ case DiagnosticsEngine::Error:
+ OS << "error: ";
+ break;
+ case DiagnosticsEngine::Fatal:
+ OS << "fatal error: ";
+ break;
+ }
+
+ if (ShowColors)
+ OS.resetColor();
+}
+
+/*static*/
+void SARIFDiagnostic::printDiagnosticMessage(
+ raw_ostream &OS, bool IsSupplemental, StringRef Message,
+ unsigned CurrentColumn, unsigned Columns, bool ShowColors) {
+ bool Bold = false;
+ if (ShowColors && !IsSupplemental) {
+ // Print primary diagnostic messages in bold and without color, to visually
+ // indicate the transition from continuation notes and other output.
+ OS.changeColor(savedColor, true);
+ Bold = true;
+ }
+
+ if (Columns)
+ printWordWrapped(OS, Message, Columns, CurrentColumn, Bold);
+ else {
+ bool Normal = true;
+ applyTemplateHighlighting(OS, Message, Normal, Bold);
+ assert(Normal && "Formatting should have returned to normal");
+ }
+
+ if (ShowColors)
+ OS.resetColor();
+ OS << '\n';
+}
+
+void SARIFDiagnostic::emitFilename(StringRef Filename,
+ const SourceManager &SM) {
+#ifdef _WIN32
+ SmallString<4096> TmpFilename;
+#endif
+ if (DiagOpts->AbsolutePath) {
+ auto File = SM.getFileManager().getFile(Filename);
+ if (File) {
+ // We want to print a simplified absolute path, i. e. without "dots".
+ //
+ // The hardest part here are the paths like "<part1>/<link>/../<part2>".
+ // On Unix-like systems, we cannot just collapse "<link>/..", because
+ // paths are resolved sequentially, and, thereby, the path
+ // "<part1>/<part2>" may point to a different location. That is why
+ // we use FileManager::getCanonicalName(), which expands all indirections
+ // with llvm::sys::fs::real_path() and caches the result.
+ //
+ // On the other hand, it would be better to preserve as much of the
+ // original path as possible, because that helps a user to recognize it.
+ // real_path() expands all links, which sometimes too much. Luckily,
+ // on Windows we can just use llvm::sys::path::remove_dots(), because,
+ // on that system, both aforementioned paths point to the same place.
+#ifdef _WIN32
+ TmpFilename = (*File)->getName();
+ llvm::sys::fs::make_absolute(TmpFilename);
+ llvm::sys::path::native(TmpFilename);
+ llvm::sys::path::remove_dots(TmpFilename, /* remove_dot_dot */ true);
+ Filename = StringRef(TmpFilename.data(), TmpFilename.size());
+#else
+ Filename = SM.getFileManager().getCanonicalName(*File);
+#endif
+ }
+ }
+
+ OS << Filename;
+}
+
+/// Print out the file/line/column information and include trace.
+///
+/// This method handlen the emission of the diagnostic location information.
+/// This includes extracting as much location information as is present for
+/// the diagnostic and printing it, as well as any include stack or source
+/// ranges necessary.
+void SARIFDiagnostic::emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
+ DiagnosticsEngine::Level Level,
+ ArrayRef<CharSourceRange> Ranges) {
+ if (PLoc.isInvalid()) {
+ // At least print the file name if available:
+ FileID FID = Loc.getFileID();
+ if (FID.isValid()) {
+ if (const FileEntry *FE = Loc.getFileEntry()) {
+ emitFilename(FE->getName(), Loc.getManager());
+ OS << ": ";
+ }
+ }
+ return;
+ }
+ unsigned LineNo = PLoc.getLine();
+
+ if (!DiagOpts->ShowLocation)
+ return;
+
+ if (DiagOpts->ShowColors)
+ OS.changeColor(savedColor, true);
+
+ emitFilename(PLoc.getFilename(), Loc.getManager());
+ switch (DiagOpts->getFormat()) {
+ case DiagnosticOptions::SARIF:
+ case DiagnosticOptions::Clang:
+ if (DiagOpts->ShowLine)
+ OS << ':' << LineNo;
+ break;
+ case DiagnosticOptions::MSVC:
+ OS << '(' << LineNo;
+ break;
+ case DiagnosticOptions::Vi:
+ OS << " +" << LineNo;
+ break;
+ }
+
+ if (DiagOpts->ShowColumn)
+ // Compute the column number.
+ if (unsigned ColNo = PLoc.getColumn()) {
+ if (DiagOpts->getFormat() == DiagnosticOptions::MSVC) {
+ OS << ',';
+ // Visual Studio 2010 or earlier expects column number to be off by one
+ if (LangOpts.MSCompatibilityVersion &&
+ !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012))
+ ColNo--;
+ } else
+ OS << ':';
+ OS << ColNo;
+ }
+ switch (DiagOpts->getFormat()) {
+ case DiagnosticOptions::SARIF:
+ case DiagnosticOptions::Clang:
+ case DiagnosticOptions::Vi:
+ OS << ':';
+ break;
+ case DiagnosticOptions::MSVC:
+ // MSVC2013 and before print 'file(4) : error'. MSVC2015 gets rid of the
+ // space and prints 'file(4): error'.
+ OS << ')';
+ if (LangOpts.MSCompatibilityVersion &&
+ !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2015))
+ OS << ' ';
+ OS << ':';
+ break;
+ }
+
+ if (DiagOpts->ShowSourceRanges && !Ranges.empty()) {
+ FileID CaretFileID = Loc.getExpansionLoc().getFileID();
+ bool PrintedRange = false;
+
+ for (ArrayRef<CharSourceRange>::const_iterator RI = Ranges.begin(),
+ RE = Ranges.end();
+ RI != RE; ++RI) {
+ // Ignore invalid ranges.
+ if (!RI->isValid())
+ continue;
+
+ auto &SM = Loc.getManager();
+ SourceLocation B = SM.getExpansionLoc(RI->getBegin());
+ CharSourceRange ERange = SM.getExpansionRange(RI->getEnd());
+ SourceLocation E = ERange.getEnd();
+ bool IsTokenRange = ERange.isTokenRange();
+
+ std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(B);
+ std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(E);
+
+ // If the start or end of the range is in another file, just discard
+ // it.
+ if (BInfo.first != CaretFileID || EInfo.first != CaretFileID)
+ continue;
+
+ // Add in the length of the token, so that we cover multi-char
+ // tokens.
+ unsigned TokSize = 0;
+ if (IsTokenRange)
+ TokSize = Lexer::MeasureTokenLength(E, SM, LangOpts);
+
+ FullSourceLoc BF(B, SM), EF(E, SM);
+ OS << '{' << BF.getLineNumber() << ':' << BF.getColumnNumber() << '-'
+ << EF.getLineNumber() << ':' << (EF.getColumnNumber() + TokSize)
+ << '}';
+ PrintedRange = true;
+ }
+
+ if (PrintedRange)
+ OS << ':';
+ }
+ OS << ' ';
+}
+
+void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) {
+ if (DiagOpts->ShowLocation && PLoc.isValid())
+ OS << "In file included from " << PLoc.getFilename() << ':'
+ << PLoc.getLine() << ":\n";
+ else
+ OS << "In included file:\n";
+}
+
+void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
+ StringRef ModuleName) {
+ if (DiagOpts->ShowLocation && PLoc.isValid())
+ OS << "In module '" << ModuleName << "' imported from "
+ << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";
+ else
+ OS << "In module '" << ModuleName << "':\n";
+}
+
+void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc,
+ PresumedLoc PLoc,
+ StringRef ModuleName) {
+ if (DiagOpts->ShowLocation && PLoc.isValid())
+ OS << "While building module '" << ModuleName << "' imported from "
+ << PLoc.getFilename() << ':' << PLoc.getLine() << ":\n";
+ else
+ OS << "While building module '" << ModuleName << "':\n";
+}
+
+/// Find the suitable set of lines to show to include a set of ranges.
+static llvm::Optional<std::pair<unsigned, unsigned>>
+findLinesForRange(const CharSourceRange &R, FileID FID,
+ const SourceManager &SM) {
+ if (!R.isValid())
+ return None;
+
+ SourceLocation Begin = R.getBegin();
+ SourceLocation End = R.getEnd();
+ if (SM.getFileID(Begin) != FID || SM.getFileID(End) != FID)
+ return None;
+
+ return std::make_pair(SM.getExpansionLineNumber(Begin),
+ SM.getExpansionLineNumber(End));
+}
+
+/// Add as much of range B into range A as possible without exceeding a maximum
+/// size of MaxRange. Ranges are inclusive.
+static std::pair<unsigned, unsigned>
+maybeAddRange(std::pair<unsigned, unsigned> A, std::pair<unsigned, unsigned> B,
+ unsigned MaxRange) {
+ // If A is already the maximum size, we're done.
+ unsigned Slack = MaxRange - (A.second - A.first + 1);
+ if (Slack == 0)
+ return A;
+
+ // Easy case: merge succeeds within MaxRange.
+ unsigned Min = std::min(A.first, B.first);
+ unsigned Max = std::max(A.second, B.second);
+ if (Max - Min + 1 <= MaxRange)
+ return {Min, Max};
+
+ // If we can't reach B from A within MaxRange, there's nothing to do.
+ // Don't add lines to the range that contain nothing interesting.
+ if ((B.first > A.first && B.first - A.first + 1 > MaxRange) ||
+ (B.second < A.second && A.second - B.second + 1 > MaxRange))
+ return A;
+
+ // Otherwise, expand A towards B to produce a range of size MaxRange. We
+ // attempt to expand by the same amount in both directions if B strictly
+ // contains A.
+
+ // Expand downwards by up to half the available amount, then upwards as
+ // much as possible, then downwards as much as possible.
+ A.second = std::min(A.second + (Slack + 1) / 2, Max);
+ Slack = MaxRange - (A.second - A.first + 1);
+ A.first = std::max(Min + Slack, A.first) - Slack;
+ A.second = std::min(A.first + MaxRange - 1, Max);
+ return A;
+}
+
+/// Highlight a SourceRange (with ~'s) for any characters on LineNo.
+static void highlightRange(const CharSourceRange &R, unsigned LineNo,
+ FileID FID, const SourceColumnMap &map,
+ std::string &CaretLine, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ if (!R.isValid())
+ return;
+
+ SourceLocation Begin = R.getBegin();
+ SourceLocation End = R.getEnd();
+
+ unsigned StartLineNo = SM.getExpansionLineNumber(Begin);
+ if (StartLineNo > LineNo || SM.getFileID(Begin) != FID)
+ return; // No intersection.
+
+ unsigned EndLineNo = SM.getExpansionLineNumber(End);
+ if (EndLineNo < LineNo || SM.getFileID(End) != FID)
+ return; // No intersection.
+
+ // Compute the column number of the start.
+ unsigned StartColNo = 0;
+ if (StartLineNo == LineNo) {
+ StartColNo = SM.getExpansionColumnNumber(Begin);
+ if (StartColNo)
+ --StartColNo; // Zero base the col #.
+ }
+
+ // Compute the column number of the end.
+ unsigned EndColNo = map.getSourceLine().size();
+ if (EndLineNo == LineNo) {
+ EndColNo = SM.getExpansionColumnNumber(End);
+ if (EndColNo) {
+ --EndColNo; // Zero base the col #.
+
+ // Add in the length of the token, so that we cover multi-char tokens if
+ // this is a token range.
+ if (R.isTokenRange())
+ EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts);
+ } else {
+ EndColNo = CaretLine.size();
+ }
+ }
+
+ assert(StartColNo <= EndColNo && "Invalid range!");
+
+ // Check that a token range does not highlight only whitespace.
+ if (R.isTokenRange()) {
+ // Pick the first non-whitespace column.
+ while (StartColNo < map.getSourceLine().size() &&
+ (map.getSourceLine()[StartColNo] == ' ' ||
+ map.getSourceLine()[StartColNo] == '\t'))
+ StartColNo = map.startOfNextColumn(StartColNo);
+
+ // Pick the last non-whitespace column.
+ if (EndColNo > map.getSourceLine().size())
+ EndColNo = map.getSourceLine().size();
+ while (EndColNo && (map.getSourceLine()[EndColNo - 1] == ' ' ||
+ map.getSourceLine()[EndColNo - 1] == '\t'))
+ EndColNo = map.startOfPreviousColumn(EndColNo);
+
+ // If the start/end passed each other, then we are trying to highlight a
+ // range that just exists in whitespace. That most likely means we have
+ // a multi-line highlighting range that covers a blank line.
+ if (StartColNo > EndColNo) {
+ assert(StartLineNo != EndLineNo && "trying to highlight whitespace");
+ StartColNo = EndColNo;
+ }
+ }
+
+ assert(StartColNo <= map.getSourceLine().size() && "Invalid range!");
+ assert(EndColNo <= map.getSourceLine().size() && "Invalid range!");
+
+ // Fill the range with ~'s.
+ StartColNo = map.byteToContainingColumn(StartColNo);
+ EndColNo = map.byteToContainingColumn(EndColNo);
+
+ assert(StartColNo <= EndColNo && "Invalid range!");
+ if (CaretLine.size() < EndColNo)
+ CaretLine.resize(EndColNo, ' ');
+ std::fill(CaretLine.begin() + StartColNo, CaretLine.begin() + EndColNo, '~');
+}
+
+static std::string buildFixItInsertionLine(FileID FID, unsigned LineNo,
+ const SourceColumnMap &map,
+ ArrayRef<FixItHint> Hints,
+ const SourceManager &SM,
+ const DiagnosticOptions *DiagOpts) {
+ std::string FixItInsertionLine;
+ if (Hints.empty() || !DiagOpts->ShowFixits)
+ return FixItInsertionLine;
+ unsigned PrevHintEndCol = 0;
+
+ for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); I != E;
+ ++I) {
+ if (!I->CodeToInsert.empty()) {
+ // We have an insertion hint. Determine whether the inserted
+ // code contains no newlines and is on the same line as the caret.
+ std::pair<FileID, unsigned> HintLocInfo =
+ SM.getDecomposedExpansionLoc(I->RemoveRange.getBegin());
+ if (FID == HintLocInfo.first &&
+ LineNo == SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) &&
+ StringRef(I->CodeToInsert).find_first_of("\n\r") == StringRef::npos) {
+ // Insert the new code into the line just below the code
+ // that the user wrote.
+ // Note: When modifying this function, be very careful about what is a
+ // "column" (printed width, platform-dependent) and what is a
+ // "byte offset" (SourceManager "column").
+ unsigned HintByteOffset =
+ SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second) - 1;
+
+ // The hint must start inside the source or right at the end
+ assert(HintByteOffset < static_cast<unsigned>(map.bytes()) + 1);
+ unsigned HintCol = map.byteToContainingColumn(HintByteOffset);
+
+ // If we inserted a long previous hint, push this one forwards, and add
+ // an extra space to show that this is not part of the previous
+ // completion. This is sort of the best we can do when two hints appear
+ // to overlap.
+ //
+ // Note that if this hint is located immediately after the previous
+ // hint, no space will be added, since the location is more important.
+ if (HintCol < PrevHintEndCol)
+ HintCol = PrevHintEndCol + 1;
+
+ // This should NOT use HintByteOffset, because the source might have
+ // Unicode characters in earlier columns.
+ unsigned NewFixItLineSize = FixItInsertionLine.size() +
+ (HintCol - PrevHintEndCol) +
+ I->CodeToInsert.size();
+ if (NewFixItLineSize > FixItInsertionLine.size())
+ FixItInsertionLine.resize(NewFixItLineSize, ' ');
+
+ std::copy(I->CodeToInsert.begin(), I->CodeToInsert.end(),
+ FixItInsertionLine.end() - I->CodeToInsert.size());
+
+ PrevHintEndCol =
+ HintCol + llvm::sys::locale::columnWidth(I->CodeToInsert);
+ }
+ }
+ }
+
+ expandTabs(FixItInsertionLine, DiagOpts->TabStop);
+
+ return FixItInsertionLine;
+}
+
+/// Emit a code snippet and caret line.
+///
+/// This routine emits a single line's code snippet and caret line..
+///
+/// \param Loc The location for the caret.
+/// \param Ranges The underlined ranges for this code snippet.
+/// \param Hints The FixIt hints active for this diagnostic.
+void SARIFDiagnostic::emitSnippetAndCaret(
+ FullSourceLoc Loc, DiagnosticsEngine::Level Level,
+ SmallVectorImpl<CharSourceRange> &Ranges, ArrayRef<FixItHint> Hints) {
+ assert(Loc.isValid() && "must have a valid source location here");
+ assert(Loc.isFileID() && "must have a file location here");
+
+ // If caret diagnostics are enabled and we have location, we want to
+ // emit the caret. However, we only do this if the location moved
+ // from the last diagnostic, if the last diagnostic was a note that
+ // was part of a different warning or error diagnostic, or if the
+ // diagnostic has ranges. We don't want to emit the same caret
+ // multiple times if one loc has multiple diagnostics.
+ if (!DiagOpts->ShowCarets)
+ return;
+ if (Loc == LastLoc && Ranges.empty() && Hints.empty() &&
+ (LastLevel != DiagnosticsEngine::Note || Level == LastLevel))
+ return;
+
+ // Decompose the location into a FID/Offset pair.
+ std::pair<FileID, unsigned> LocInfo = Loc.getDecomposedLoc();
+ FileID FID = LocInfo.first;
+ const SourceManager &SM = Loc.getManager();
+
+ // Get information about the buffer it points into.
+ bool Invalid = false;
+ StringRef BufData = Loc.getBufferData(&Invalid);
+ if (Invalid)
+ return;
+
+ unsigned CaretLineNo = Loc.getLineNumber();
+ unsigned CaretColNo = Loc.getColumnNumber();
+
+ // Arbitrarily stop showing snippets when the line is too long.
+ static const size_t MaxLineLengthToPrint = 4096;
+ if (CaretColNo > MaxLineLengthToPrint)
+ return;
+
+ // Find the set of lines to include.
+ const unsigned MaxLines = DiagOpts->SnippetLineLimit;
+ std::pair<unsigned, unsigned> Lines = {CaretLineNo, CaretLineNo};
+ for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(),
+ E = Ranges.end();
+ I != E; ++I)
+ if (auto OptionalRange = findLinesForRange(*I, FID, SM))
+ Lines = maybeAddRange(Lines, *OptionalRange, MaxLines);
+
+ for (unsigned LineNo = Lines.first; LineNo != Lines.second + 1; ++LineNo) {
+ const char *BufStart = BufData.data();
+ const char *BufEnd = BufStart + BufData.size();
+
+ // Rewind from the current position to the start of the line.
+ const char *LineStart =
+ BufStart +
+ SM.getDecomposedLoc(SM.translateLineCol(FID, LineNo, 1)).second;
+ if (LineStart == BufEnd)
+ break;
+
+ // Compute the line end.
+ const char *LineEnd = LineStart;
+ while (*LineEnd != '\n' && *LineEnd != '\r' && LineEnd != BufEnd)
+ ++LineEnd;
+
+ // Arbitrarily stop showing snippets when the line is too long.
+ // FIXME: Don't print any lines in this case.
+ if (size_t(LineEnd - LineStart) > MaxLineLengthToPrint)
+ return;
+
+ // Trim trailing null-bytes.
+ StringRef Line(LineStart, LineEnd - LineStart);
+ while (!Line.empty() && Line.back() == '\0' &&
+ (LineNo != CaretLineNo || Line.size() > CaretColNo))
+ Line = Line.drop_back();
+
+ // Copy the line of code into an std::string for ease of manipulation.
+ std::string SourceLine(Line.begin(), Line.end());
+
+ // Build the byte to column map.
+ const SourceColumnMap sourceColMap(SourceLine, DiagOpts->TabStop);
+
+ // Create a line for the caret that is filled with spaces that is the same
+ // number of columns as the line of source code.
+ std::string CaretLine(sourceColMap.columns(), ' ');
+
+ // Highlight all of the characters covered by Ranges with ~ characters.
+ for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(),
+ E = Ranges.end();
+ I != E; ++I)
+ highlightRange(*I, LineNo, FID, sourceColMap, CaretLine, SM, LangOpts);
+
+ // Next, insert the caret itself.
+ if (CaretLineNo == LineNo) {
+ CaretColNo = sourceColMap.byteToContainingColumn(CaretColNo - 1);
+ if (CaretLine.size() < CaretColNo + 1)
+ CaretLine.resize(CaretColNo + 1, ' ');
+ CaretLine[CaretColNo] = '^';
+ }
+
+ std::string FixItInsertionLine = buildFixItInsertionLine(
+ FID, LineNo, sourceColMap, Hints, SM, DiagOpts.get());
+
+ // If the source line is too long for our terminal, select only the
+ // "interesting" source region within that line.
+ unsigned Columns = DiagOpts->MessageLength;
+ if (Columns)
+ selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine,
+ Columns, sourceColMap);
+
+ // If we are in -fdiagnostics-print-source-range-info mode, we are trying
+ // to produce easily machine parsable output. Add a space before the
+ // source line and the caret to make it trivial to tell the main diagnostic
+ // line from what the user is intended to see.
+ if (DiagOpts->ShowSourceRanges) {
+ SourceLine = ' ' + SourceLine;
+ CaretLine = ' ' + CaretLine;
+ }
+
+ // Finally, remove any blank spaces from the end of CaretLine.
+ while (!CaretLine.empty() && CaretLine[CaretLine.size() - 1] == ' ')
+ CaretLine.erase(CaretLine.end() - 1);
+
+ // Emit what we have computed.
+ emitSnippet(SourceLine);
+
+ if (!CaretLine.empty()) {
+ if (DiagOpts->ShowColors)
+ OS.changeColor(caretColor, true);
+ OS << CaretLine << '\n';
+ if (DiagOpts->ShowColors)
+ OS.resetColor();
+ }
+
+ if (!FixItInsertionLine.empty()) {
+ if (DiagOpts->ShowColors)
+ // Print fixit line in color
+ OS.changeColor(fixitColor, false);
+ if (DiagOpts->ShowSourceRanges)
+ OS << ' ';
+ OS << FixItInsertionLine << '\n';
+ if (DiagOpts->ShowColors)
+ OS.resetColor();
+ }
+ }
+
+ // Print out any parseable fixit information requested by the options.
+ emitParseableFixits(Hints, SM);
+}
+
+void SARIFDiagnostic::emitSnippet(StringRef line) {
+ if (line.empty())
+ return;
+
+ size_t i = 0;
+
+ std::string to_print;
+ bool print_reversed = false;
+
+ while (i < line.size()) {
+ std::pair<SmallString<16>, bool> res =
+ printableTextForNextCharacter(line, &i, DiagOpts->TabStop);
+ bool was_printable = res.second;
+
+ if (DiagOpts->ShowColors && was_printable == print_reversed) {
+ if (print_reversed)
+ OS.reverseColor();
+ OS << to_print;
+ to_print.clear();
+ if (DiagOpts->ShowColors)
+ OS.resetColor();
+ }
+
+ print_reversed = !was_printable;
+ to_print += res.first.str();
+ }
+
+ if (print_reversed && DiagOpts->ShowColors)
+ OS.reverseColor();
+ OS << to_print;
+ if (print_reversed && DiagOpts->ShowColors)
+ OS.resetColor();
+
+ OS << '\n';
+}
+
+void SARIFDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints,
+ const SourceManager &SM) {
+ if (!DiagOpts->ShowParseableFixits)
+ return;
+
+ // We follow FixItRewriter's example in not (yet) handling
+ // fix-its in macros.
+ for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); I != E;
+ ++I) {
+ if (I->RemoveRange.isInvalid() || I->RemoveRange.getBegin().isMacroID() ||
+ I->RemoveRange.getEnd().isMacroID())
+ return;
+ }
+
+ for (ArrayRef<FixItHint>::iterator I = Hints.begin(), E = Hints.end(); I != E;
+ ++I) {
+ SourceLocation BLoc = I->RemoveRange.getBegin();
+ SourceLocation ELoc = I->RemoveRange.getEnd();
+
+ std::pair<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);
+ std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(ELoc);
+
+ // Adjust for token ranges.
+ if (I->RemoveRange.isTokenRange())
+ EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts);
+
+ // We specifically do not do word-wrapping or tab-expansion here,
+ // because this is supposed to be easy to parse.
+ PresumedLoc PLoc = SM.getPresumedLoc(BLoc);
+ if (PLoc.isInvalid())
+ break;
+
+ OS << "fix-it:\"";
+ OS.write_escaped(PLoc.getFilename());
+ OS << "\":{" << SM.getLineNumber(BInfo.first, BInfo.second) << ':'
+ << SM.getColumnNumber(BInfo.first, BInfo.second) << '-'
+ << SM.getLineNumber(EInfo.first, EInfo.second) << ':'
+ << SM.getColumnNumber(EInfo.first, EInfo.second) << "}:\"";
+ OS.write_escaped(I->CodeToInsert);
+ OS << "\"\n";
+ }
+}
Index: clang/lib/Frontend/FrontendAction.cpp
===================================================================
--- clang/lib/Frontend/FrontendAction.cpp
+++ clang/lib/Frontend/FrontendAction.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclGroup.h"
#include "clang/Basic/Builtins.h"
+#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/LangStandard.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
@@ -18,6 +19,7 @@
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Frontend/LayoutOverrideSource.h"
#include "clang/Frontend/MultiplexConsumer.h"
+#include "clang/Frontend/SARIFDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/LiteralSupport.h"
@@ -717,8 +719,14 @@
return false;
}
}
- if (!CI.hasSourceManager())
+ if (!CI.hasSourceManager()) {
CI.createSourceManager(CI.getFileManager());
+ if (CI.getDiagnosticOpts().getFormat() == DiagnosticOptions::SARIF) {
+ auto *Writer = new SarifDocumentWriter(CI.getSourceManager());
+ static_cast<SARIFDiagnosticPrinter *>(&CI.getDiagnosticClient())
+ ->setSarifWriter(Writer);
+ }
+ }
// Set up embedding for any specified files. Do this before we load any
// source files, including the primary module map for the compilation.
Index: clang/lib/Frontend/CompilerInstance.cpp
===================================================================
--- clang/lib/Frontend/CompilerInstance.cpp
+++ clang/lib/Frontend/CompilerInstance.cpp
@@ -12,6 +12,7 @@
#include "clang/AST/Decl.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangStandard.h"
#include "clang/Basic/SourceManager.h"
@@ -25,6 +26,7 @@
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Frontend/LogDiagnosticPrinter.h"
+#include "clang/Frontend/SARIFDiagnosticPrinter.h"
#include "clang/Frontend/SerializedDiagnosticPrinter.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
@@ -346,6 +348,8 @@
// implementing -verify.
if (Client) {
Diags->setClient(Client, ShouldOwnClient);
+ } else if (Opts->getFormat() == DiagnosticOptions::SARIF) {
+ Diags->setClient(new SARIFDiagnosticPrinter(llvm::errs(), Opts));
} else
Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), Opts));
Index: clang/lib/Frontend/CMakeLists.txt
===================================================================
--- clang/lib/Frontend/CMakeLists.txt
+++ clang/lib/Frontend/CMakeLists.txt
@@ -31,6 +31,8 @@
MultiplexConsumer.cpp
PrecompiledPreamble.cpp
PrintPreprocessedOutput.cpp
+ SARIFDiagnostic.cpp
+ SARIFDiagnosticPrinter.cpp
SerializedDiagnosticPrinter.cpp
SerializedDiagnosticReader.cpp
TestModuleFileExtension.cpp
Index: clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
===================================================================
--- /dev/null
+++ clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
@@ -0,0 +1,75 @@
+//===--- SARIFDiagnosticPrinter.h - Text Diagnostic Client -------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a concrete diagnostic client, which prints the diagnostics to
+// standard error.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_FRONTEND_SARIFDIAGNOSTICPRINTER_H
+#define LLVM_CLANG_FRONTEND_SARIFDIAGNOSTICPRINTER_H
+
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/Sarif.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include <memory>
+
+namespace clang {
+class DiagnosticOptions;
+class LangOptions;
+class SARIFDiagnostic;
+class SarifDocumentWriter;
+
+class SARIFDiagnosticPrinter : public DiagnosticConsumer {
+ raw_ostream &OS;
+ IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts;
+
+ /// Handle to the currently active text diagnostic emitter.
+ std::unique_ptr<SARIFDiagnostic> SARIFDiag;
+
+ /// A string to prefix to error messages.
+ std::string Prefix;
+
+ SarifDocumentWriter *Writer = nullptr;
+
+ unsigned OwnsOutputStream : 1;
+
+public:
+ SARIFDiagnosticPrinter(raw_ostream &os, DiagnosticOptions *diags,
+ bool OwnsOutputStream = false);
+ ~SARIFDiagnosticPrinter() override;
+
+ /// setPrefix - Set the diagnostic printer prefix string, which will be
+ /// printed at the start of any diagnostics. If empty, no prefix string is
+ /// used.
+ void setPrefix(std::string Value) {
+ Prefix = std::move(Value);
+ } // TODO: In case we need this
+
+ bool hasSarifWriter() const { return Writer != nullptr; }
+
+ SarifDocumentWriter &getSarifWriter() const {
+ assert(Writer && "SarifWriter not set!");
+ return *Writer;
+ }
+
+ void setSarifWriter(SarifDocumentWriter *SarifWriter) {
+ Writer = SarifWriter;
+ }
+
+ void BeginSourceFile(const LangOptions &LO, const Preprocessor *PP) override;
+ void EndSourceFile() override;
+ void HandleDiagnostic(DiagnosticsEngine::Level Level,
+ const Diagnostic &Info) override;
+};
+
+} // end namespace clang
+
+#endif
Index: clang/include/clang/Frontend/SARIFDiagnostic.h
===================================================================
--- /dev/null
+++ clang/include/clang/Frontend/SARIFDiagnostic.h
@@ -0,0 +1,106 @@
+//===--- SARIFDiagnostic.h - Text Diagnostic Pretty-Printing -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a utility class that provides support for textual pretty-printing of
+// diagnostics. It is used to implement the different code paths which require
+// such functionality in a consistent way.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_FRONTEND_SARIFDIAGNOSTIC_H
+#define LLVM_CLANG_FRONTEND_SARIFDIAGNOSTIC_H
+
+#include "clang/Basic/Sarif.h"
+#include "clang/Frontend/DiagnosticRenderer.h"
+#include "clang/Frontend/TextDiagnostic.h"
+
+namespace clang {
+
+class SARIFDiagnostic : public DiagnosticRenderer {
+ raw_ostream &OS;
+
+ SarifDocumentWriter *Writer;
+
+public:
+ SARIFDiagnostic(raw_ostream &OS,
+ const LangOptions &LangOpts,
+ DiagnosticOptions *DiagOpts,
+ SarifDocumentWriter *Writer);
+
+ ~SARIFDiagnostic() = default;
+
+ /// Print the diagonstic level to a raw_ostream.
+ ///
+ /// This is a static helper that handles colorizing the level and formatting
+ /// it into an arbitrary output stream. This is used internally by the
+ /// SARIFDiagnostic emission code, but it can also be used directly by
+ /// consumers that don't have a source manager or other state that the full
+ /// SARIFDiagnostic logic requires.
+ static void printDiagnosticLevel(raw_ostream &OS,
+ DiagnosticsEngine::Level Level,
+ bool ShowColors);
+
+ /// Pretty-print a diagnostic message to a raw_ostream.
+ ///
+ /// This is a static helper to handle the line wrapping, colorizing, and
+ /// rendering of a diagnostic message to a particular ostream. It is
+ /// publicly visible so that clients which do not have sufficient state to
+ /// build a complete SARIFDiagnostic object can still get consistent
+ /// formatting of their diagnostic messages.
+ ///
+ /// \param OS Where the message is printed
+ /// \param IsSupplemental true if this is a continuation note diagnostic
+ /// \param Message The text actually printed
+ /// \param CurrentColumn The starting column of the first line, accounting
+ /// for any prefix.
+ /// \param Columns The number of columns to use in line-wrapping, 0 disables
+ /// all line-wrapping.
+ /// \param ShowColors Enable colorizing of the message.
+ static void printDiagnosticMessage(raw_ostream &OS, bool IsSupplemental,
+ StringRef Message, unsigned CurrentColumn,
+ unsigned Columns, bool ShowColors);
+
+protected:
+ void emitDiagnosticMessage(FullSourceLoc Loc, PresumedLoc PLoc,
+ DiagnosticsEngine::Level Level, StringRef Message,
+ ArrayRef<CharSourceRange> Ranges,
+ DiagOrStoredDiag D) override;
+
+ void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc,
+ DiagnosticsEngine::Level Level,
+ ArrayRef<CharSourceRange> Ranges) override;
+
+ void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
+ SmallVectorImpl<CharSourceRange> &Ranges,
+ ArrayRef<FixItHint> Hints) override {
+ emitSnippetAndCaret(Loc, Level, Ranges, Hints);
+ }
+
+ void emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) override;
+
+ void emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc,
+ StringRef ModuleName) override;
+
+ void emitBuildingModuleLocation(FullSourceLoc Loc, PresumedLoc PLoc,
+ StringRef ModuleName) override;
+
+private:
+ void emitFilename(StringRef Filename, const SourceManager &SM);
+
+ void emitSnippetAndCaret(FullSourceLoc Loc, DiagnosticsEngine::Level Level,
+ SmallVectorImpl<CharSourceRange> &Ranges,
+ ArrayRef<FixItHint> Hints);
+
+ void emitSnippet(StringRef SourceLine);
+
+ void emitParseableFixits(ArrayRef<FixItHint> Hints, const SourceManager &SM);
+};
+
+} // end namespace clang
+
+#endif
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits