abrahamcd updated this revision to Diff 452833. abrahamcd retitled this revision from "[WIP] Enable SARIF Diagnostics" to "[clang] Enable output of SARIF diagnostics". abrahamcd edited the summary of this revision. abrahamcd added a comment.
Removed unused remaining parts of traditional text diagnostics. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D131632/new/ 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/test/Frontend/sarif-diagnostics.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,137 @@ +// 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").has_value()); + ASSERT_TRUE(Location->getString("uri").has_value()); + + EXPECT_EQ(Location->getInteger("index").value(), 0); + EXPECT_EQ(Location->getString("uri").value(), "file://<stdin>"); + + // Check Driver + const llvm::json::Object *Driver = + TheRun->getObject("tool")->getObject("driver"); + + ASSERT_TRUE(Driver->getString("name").has_value()); + ASSERT_TRUE(Driver->getString("fullName").has_value()); + + EXPECT_EQ(Driver->getString("name").value(), "clang"); + EXPECT_EQ(Driver->getString("fullName").value(), "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/test/Frontend/sarif-diagnostics.cpp =================================================================== --- /dev/null +++ clang/test/Frontend/sarif-diagnostics.cpp @@ -0,0 +1,16 @@ +// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif %s > %t 2>&1 || true +// RUN: FileCheck -dump-input=always %s --input-file=%t +// CHECK: {"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":2953,"location":{"index":0,"uri":"file:///usr/local/google/home/abrahamcd/projects/llvm-project/clang/test/Frontend/sarif-diagnostics.cpp"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":[{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":1,"startColumn":1,"startLine":5}}}],"message":{"text":"'main' must return 'int'"},"ruleId":"3463","ruleIndex":0},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":11,"startColumn":11,"startLine":6}}}],"message":{"text":"use of undeclared identifier 'hello'"},"ruleId":"4601","ruleIndex":1},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":17,"startColumn":17,"startLine":8}}}],"message":{"text":"invalid digit 'a' in decimal constant"},"ruleId":"898","ruleIndex":2},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":5,"startColumn":5,"startLine":12}}}],"message":{"text":"misleading indentation; statement is not part of the previous 'if'"},"ruleId":"1806","ruleIndex":3},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":3,"startColumn":3,"startLine":10}}}],"message":{"text":"previous statement is here"},"ruleId":"1730","ruleIndex":4},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":10,"startColumn":10,"startLine":11}}}],"message":{"text":"unused variable 'Yes'"},"ruleId":"6536","ruleIndex":5},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":12,"startColumn":12,"startLine":14}}}],"message":{"text":"use of undeclared identifier 'hi'"},"ruleId":"4601","ruleIndex":6},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":1,"startColumn":1,"startLine":16}}}],"message":{"text":"extraneous closing brace ('}')"},"ruleId":"1399","ruleIndex":7}],"tool":{"driver":{"fullName":"","informationUri":"https://clang.llvm.org/docs/UsersManual.html","language":"en-US","name":"clang","rules":[{"fullDescription":{"text":""},"id":"3463","name":""},{"fullDescription":{"text":""},"id":"4601","name":""},{"fullDescription":{"text":""},"id":"898","name":""},{"fullDescription":{"text":""},"id":"1806","name":""},{"fullDescription":{"text":""},"id":"1730","name":""},{"fullDescription":{"text":""},"id":"6536","name":""},{"fullDescription":{"text":""},"id":"4601","name":""},{"fullDescription":{"text":""},"id":"1399","name":""}],"version":"16.0.0"}}}],"version":"2.1.0"} + +void main() { + int i = hello; + + float test = 1a.0; + + if (true) + bool Yes = true; + return; + + bool j = hi; +} +} Index: clang/lib/Frontend/SARIFDiagnosticPrinter.cpp =================================================================== --- /dev/null +++ clang/lib/Frontend/SARIFDiagnosticPrinter.cpp @@ -0,0 +1,90 @@ +//===--- SARIFDiagnosticPrinter.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 in SARIF format. +// +//===----------------------------------------------------------------------===// + +#include "clang/Frontend/SARIFDiagnosticPrinter.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/Sarif.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/DiagnosticRenderer.h" +#include "clang/Frontend/SARIFDiagnostic.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.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); +} + +void SARIFDiagnosticPrinter::EndSourceFile() { + Writer->endRun(); + llvm::json::Value value(Writer->createDocument()); + OS << "\n" << value << "\n\n"; + OS.flush(); + SARIFDiag.reset(); +} + +// printDiagnosticInformation() + +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 add the diagnostic to the SARIF object. + SmallString<100> OutStr; + Info.FormatDiagnostic(OutStr); + + llvm::raw_svector_ostream DiagMessageStream(OutStr); + + // 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()) { // TODO: What is this case? + // SARIFDiag->addDiagnosticWithoutLocation( + // DiagMessageStream.str(), Info.getID()); + 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"); + + SARIFDiag->emitDiagnostic( + FullSourceLoc(Info.getLocation(), Info.getSourceManager()), Level, + DiagMessageStream.str(), Info.getRanges(), Info.getFixItHints(), &Info); + +} Index: clang/lib/Frontend/SARIFDiagnostic.cpp =================================================================== --- /dev/null +++ clang/lib/Frontend/SARIFDiagnostic.cpp @@ -0,0 +1,229 @@ +//===--- SARIFDiagnostic.cpp - SARIF Diagnostic Formatting +//-------------===// +// +// 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/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.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> +#include <string> + +using namespace clang; + +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) { + + auto *Diag = D.dyn_cast<const Diagnostic *>(); + + if (!Diag) { + return; + } + + const SarifRule &Rule = + SarifRule::create().setRuleId(std::to_string(Diag->getID())); + + unsigned RuleIdx = Writer->createRule(Rule); + + SarifResult Result = + SarifResult::create(RuleIdx).setDiagnosticMessage(Message); + + if (Loc.isValid()) { + Result = addLocationToResult(Result, Loc, PLoc, Ranges, *Diag); + } + + Writer->appendResult(Result); +} + +SarifResult SARIFDiagnostic::addLocationToResult( + SarifResult Result, FullSourceLoc Loc, PresumedLoc PLoc, + ArrayRef<CharSourceRange> Ranges, const Diagnostic &Diag) { + SmallVector<CharSourceRange> Locations = {}; + + if (PLoc.isInvalid()) { + // At least add the file name if available: + FileID FID = Loc.getFileID(); + if (FID.isValid()) { + if (const FileEntry *FE = Loc.getFileEntry()) { + llvm::StringRef Filename = + emitFilename(FE->getName(), Loc.getManager()); + // FIXME: No current way to add file-only location to SARIF object + } + } + return Result; + } + + FileID CaretFileID = Loc.getExpansionLoc().getFileID(); + + 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); + SourceLocation BeginLoc = SM.translateLineCol( + BF.getFileID(), BF.getLineNumber(), BF.getColumnNumber()); + SourceLocation EndLoc = SM.translateLineCol( + EF.getFileID(), EF.getLineNumber(), EF.getColumnNumber() + TokSize); + + Locations.push_back( + CharSourceRange{SourceRange{BeginLoc, EndLoc}, /* ITR = */ false}); + } + + auto &SM = Diag.getSourceManager(); + auto FID = PLoc.getFileID(); + // Visual Studio 2010 or earlier expects column number to be off by one + auto ColNo = (LangOpts.MSCompatibilityVersion && + !LangOpts.isCompatibleWithMSVC(LangOptions::MSVC2012)) + ? PLoc.getColumn() - 1 + : PLoc.getColumn(); + SourceLocation DiagLoc = SM.translateLineCol(FID, PLoc.getLine(), ColNo); + + Locations.push_back( + CharSourceRange{SourceRange{DiagLoc, DiagLoc}, /* ITR = */ false}); + + return Result.setLocations(Locations); +} + +/*static*/ void +SARIFDiagnostic::printDiagnosticLevel(raw_ostream &OS, + DiagnosticsEngine::Level Level) { + // TODO: Change to set Level in Rule once implemented + switch (Level) { + case DiagnosticsEngine::Ignored: + llvm_unreachable("Invalid diagnostic type"); + case DiagnosticsEngine::Note: + break; + case DiagnosticsEngine::Remark: + break; + case DiagnosticsEngine::Warning: + break; + case DiagnosticsEngine::Error: + break; + case DiagnosticsEngine::Fatal: + break; + } +} + +llvm::StringRef 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 + } + } + + return 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) {} + +void SARIFDiagnostic::emitIncludeLocation(FullSourceLoc Loc, PresumedLoc PLoc) { +} + +void SARIFDiagnostic::emitImportLocation(FullSourceLoc Loc, PresumedLoc PLoc, + StringRef ModuleName) {} + +void SARIFDiagnostic::emitBuildingModuleLocation(FullSourceLoc Loc, + PresumedLoc PLoc, + StringRef ModuleName) {} + +/// 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) {} + +void SARIFDiagnostic::emitSnippet(StringRef line) {} + +void SARIFDiagnostic::emitParseableFixits(ArrayRef<FixItHint> Hints, + const SourceManager &SM) {} 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,88 @@ +//===--- 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" +#include "llvm/ADT/StringRef.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); + +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: + SarifResult addLocationToResult(SarifResult Result, FullSourceLoc Loc, PresumedLoc PLoc, ArrayRef<CharSourceRange> Ranges, const Diagnostic &Diag); + + llvm::StringRef 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 cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits