diff --git a/clang/include/clang/Frontend/SARIFDiagnostic.h b/clang/include/clang/Frontend/SARIFDiagnostic.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Frontend/SARIFDiagnostic.h @@ -0,0 +1,81 @@ +//===--- SARIFDiagnostic.h - SARIF Diagnostic Formatting -----*- 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 constructing a SARIF object +// containing diagnostics. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_FRONTEND_SARIFDIAGNOSTIC_H +#define LLVM_CLANG_FRONTEND_SARIFDIAGNOSTIC_H + +#include "clang/Basic/Sarif.h" +#include "clang/Frontend/DiagnosticRenderer.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; + + /// Add the diagnostic level to a diagnostic's SARIF rule. + /// + /// 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 Ranges, + DiagOrStoredDiag D) override; + + void emitDiagnosticLoc(FullSourceLoc Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, + ArrayRef Ranges) override; + + void emitCodeContext(FullSourceLoc Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl &Ranges, + ArrayRef 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 Ranges, + const Diagnostic &Diag); + + llvm::StringRef emitFilename(StringRef Filename, const SourceManager &SM); + + void emitSnippetAndCaret(FullSourceLoc Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl &Ranges, + ArrayRef Hints); +}; + +} // end namespace clang + +#endif diff --git a/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h b/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h @@ -0,0 +1,75 @@ +//===--- SARIFDiagnosticPrinter.h - SARIF 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 + +namespace clang { +class DiagnosticOptions; +class LangOptions; +class SARIFDiagnostic; +class SarifDocumentWriter; + +class SARIFDiagnosticPrinter : public DiagnosticConsumer { + raw_ostream &OS; + IntrusiveRefCntPtr DiagOpts; + + /// Handle to the currently active text diagnostic emitter. + std::unique_ptr 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); + } + + 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 diff --git a/clang/lib/Frontend/CMakeLists.txt b/clang/lib/Frontend/CMakeLists.txt --- a/clang/lib/Frontend/CMakeLists.txt +++ b/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 diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/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)); diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/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(&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. diff --git a/clang/lib/Frontend/SARIFDiagnostic.cpp b/clang/lib/Frontend/SARIFDiagnostic.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Frontend/SARIFDiagnostic.cpp @@ -0,0 +1,224 @@ +//===------- 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 +#include + +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 Ranges, + DiagOrStoredDiag D) { + + auto *Diag = D.dyn_cast(); + + 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 Ranges, const Diagnostic &Diag) { + SmallVector 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::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 BInfo = SM.getDecomposedLoc(B); + std::pair 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 = Loc.getManager(); + auto FID = PLoc.getFileID(); + // Visual Studio 2010 or earlier expects column number to be off by one + unsigned int 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 "//../". + // On Unix-like systems, we cannot just collapse "/..", because + // paths are resolved sequentially, and, thereby, the path + // "/" 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 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 &Ranges, ArrayRef Hints) {} diff --git a/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp @@ -0,0 +1,87 @@ +//===------- 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 +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(); +} + +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); + +} diff --git a/clang/test/Frontend/sarif-diagnostics.cpp b/clang/test/Frontend/sarif-diagnostics.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/sarif-diagnostics.cpp @@ -0,0 +1,28 @@ +// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif %s > %t 2>&1 || true +// RUN: FileCheck -dump-input=always %s --input-file=%t +// CHECK: warning: diagnostic formatting in SARIF mode is currently unstable [-Wsarif-format-unstable] +// CHECK: {"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length": +// Omit exact length of this file +// CHECK: ,"location":{"index":0,"uri":"file:// +// Omit filepath to llvm project directory +// CHECK: 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":12}}}],"message":{"text":"'main' must return 'int'"},"ruleId":"3463","ruleIndex":0},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":11,"startColumn":11,"startLine":13}}}],"message":{"text":"use of undeclared identifier 'hello'"},"ruleId":"4601","ruleIndex":1},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":17,"startColumn":17,"startLine":15}}}],"message":{"text":"invalid digit 'a' in decimal constant"},"ruleId":"898","ruleIndex":2},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":5,"startColumn":5,"startLine":19}}}],"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":17}}}],"message":{"text":"previous statement is here"},"ruleId":"1730","ruleIndex":4},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":10,"startColumn":10,"startLine":18}}}],"message":{"text":"unused variable 'Yes'"},"ruleId":"6536","ruleIndex":5},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":12,"startColumn":12,"startLine":21}}}],"message":{"text":"use of undeclared identifier 'hi'"},"ruleId":"4601","ruleIndex":6},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":1,"startColumn":1,"startLine":23}}}],"message":{"text":"extraneous closing brace ('}')"},"ruleId":"1399","ruleIndex":7},{"locations":[{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":6,"endLine":27,"startColumn":5,"startLine":27}}},{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":10,"endLine":27,"startColumn":9,"startLine":27}}},{"physicalLocation":{"artifactLocation":{"index":0},"region":{"endColumn":7,"startColumn":7,"startLine":27}}}],"message":{"text":"invalid operands to binary expression ('t1' and 't1')"},"ruleId":"4536","ruleIndex":8}],"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":""},{"fullDescription":{"text":""},"id":"4536","name":""}],"version":"16.0.0"}}}],"version":"2.1.0"} +// CHECK: 2 warnings and 6 errors generated. + + +void main() { + int i = hello; + + float test = 1a.0; + + if (true) + bool Yes = true; + return; + + bool j = hi; +} +} + +struct t1 { }; +void f1(t1 x, t1 y) { + x + y; +} diff --git a/clang/unittests/Frontend/CMakeLists.txt b/clang/unittests/Frontend/CMakeLists.txt --- a/clang/unittests/Frontend/CMakeLists.txt +++ b/clang/unittests/Frontend/CMakeLists.txt @@ -12,6 +12,7 @@ ParsedSourceLocationTest.cpp PCHPreambleTest.cpp OutputStreamTest.cpp + SARIFDiagnosticTest.cpp TextDiagnosticTest.cpp UtilsTest.cpp ) diff --git a/clang/unittests/Frontend/SARIFDiagnosticTest.cpp b/clang/unittests/Frontend/SARIFDiagnosticTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Frontend/SARIFDiagnosticTest.cpp @@ -0,0 +1,123 @@ +// 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 +#include + +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"); + + llvm::ErrorOr ClangPathOrErr = + llvm::sys::findProgramByName("clang", {SearchDir}); + ASSERT_TRUE(ClangPathOrErr); + const std::string &ClangPath = *ClangPathOrErr; + + llvm::ErrorOr 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 Redirects[] = { + EchoInputFile.str(), EchoOutputFile.str(), llvm::StringRef("")}; + + int RunResult = llvm::sys::ExecuteAndWait(EchoPath, {"echo", BrokenProgram}, + llvm::None, Redirects); + ASSERT_EQ(RunResult, 0); + + llvm::SmallString<32> ClangErrFile; + llvm::sys::fs::createTemporaryFile("clang-err", "", ClangErrFile); + llvm::FileRemover ClangErrRemover(ClangErrFile.c_str()); + + llvm::Optional 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 ClangErrBuf = llvm::MemoryBuffer::getFile(ClangErrFile.c_str()); + ASSERT_TRUE(ClangErrBuf); + llvm::StringRef ClangErr = ClangErrBuf.get()->getBuffer(); + ASSERT_EQ(ClangErr.str(), "hi"); + + llvm::Expected 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://"); + + // 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 IDs; + +} + +} // namespace