diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -113,6 +113,7 @@ JSONCompilationDatabase.cpp Refactoring.cpp RefactoringCallbacks.cpp + SarifLinker.cpp StandaloneExecution.cpp NodeIntrospection.cpp ${BINARY_INCLUDE_DIR}/NodeIntrospection.inc diff --git a/clang/lib/Tooling/SarifLinker.cpp b/clang/lib/Tooling/SarifLinker.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/SarifLinker.cpp @@ -0,0 +1,125 @@ +#include "clang/Basic/Sarif.h" +#include "clang/Config/config.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Process.h" +#include +#include +#include +#include +#include +#include +#include + +namespace llvm::sarif_linker { +[[noreturn]] void ReportErrorAndExit(llvm::errc error_code, StringRef Message) { + llvm::errs() << "sarif-ld: error: " << Message << '\n'; + std::exit(static_cast(error_code)); +} + +static json::Value ReadSarifDiagnostics(StringRef Path) { + std::ifstream in(Path.data()); + if (!in) { + ReportErrorAndExit(llvm::errc::io_error, (Path + " not found").str()); + } + + in >> std::noskipws; + std::string data(std::istream_iterator(in), {}); + assert(!in); + if (!in.eof()) { + ReportErrorAndExit(llvm::errc::io_error, "Unable to read " + Path.str()); + } + + llvm::Expected value = json::parse(data); + if (!value) { + ReportErrorAndExit(llvm::errc::invalid_argument, + "SARIF requires a valid JSON format"); + } + + // FIXME: we should eventually replace this with a proper SARIF validation + // algorithm. For now, we're just checking that a few fields are present + // (namely to ensure it "looks" like a SARIF file, and to check the fields + // we need are around). + json::Object *object = value->getAsObject(); + if (object == nullptr) { + ReportErrorAndExit(llvm::errc::invalid_argument, + "SARIF requires the top-level value to be an object"); + } + + if (object->getString("$schema") == std::nullopt) { + ReportErrorAndExit(llvm::errc::invalid_argument, + "SARIF requires there to be a `$schema` string field"); + } + + if (object->getString("version") == std::nullopt) { + ReportErrorAndExit(llvm::errc::invalid_argument, + "SARIF requires there to be a `version` string field"); + } + + if (object->getArray("runs") == nullptr) { + ReportErrorAndExit(llvm::errc::invalid_argument, + "SARIF requires there to be a `runs` array field"); + } + + return *value; +} + +template static auto &getRuns(JsonValue &value) { + return *value.getAsObject()->getArray("runs")->front().getAsObject(); +} + +static json::Value ReduceSarif(json::Value Init, + ArrayRef SarifObjects) { + json::Object &FirstRuns = getRuns(Init); + json::Array &FirstArtifacts = *FirstRuns.getArray("artifacts"); + json::Array &FirstResults = *FirstRuns.getArray("results"); + + for (const json::Value &Other : SarifObjects) { + const json::Object &OtherRuns = getRuns(Other); + const json::Array &Artifacts = *OtherRuns.getArray("artifacts"); + FirstArtifacts.insert(FirstArtifacts.end(), Artifacts.begin(), + Artifacts.end()); + + const json::Array &Results = *OtherRuns.getArray("results"); + FirstResults.insert(FirstResults.end(), Results.begin(), Results.end()); + } + + return Init; +} + +std::error_code LinkSarifDiagnostics(const opt::ArgStringList &InputPaths, + StringRef OutputPath) { + assert(InputPaths.size() > 0 && "There needs to be at least one Input path."); + std::vector SarifDiagnosticPaths; + llvm::copy_if(InputPaths, std::back_inserter(SarifDiagnosticPaths), + [](StringRef File) { return File.ends_with(".o"); }); + llvm::transform(SarifDiagnosticPaths, SarifDiagnosticPaths.begin(), + [](const std::string &Temp) { return Temp + ".sarif"; }); + + std::vector JSONData; + llvm::transform(SarifDiagnosticPaths, std::back_inserter(JSONData), + [](StringRef Path) { + json::Value SarifDiagnostics = ReadSarifDiagnostics(Path); + return SarifDiagnostics; + }); + + json::Value Output = ReduceSarif( + std::move(JSONData[0]), ArrayRef(JSONData).drop_front()); + + std::error_code EC; + llvm::raw_fd_ostream Out(OutputPath.str() + ".sarif", EC); + if (EC) { + return EC; + } + + Out << Output; + return EC; +} +} // namespace llvm::sarif_linker diff --git a/clang/test/Frontend/sarif-diagnostics.cpp b/clang/test/Frontend/sarif-diagnostics.cpp --- a/clang/test/Frontend/sarif-diagnostics.cpp +++ b/clang/test/Frontend/sarif-diagnostics.cpp @@ -29,9 +29,26 @@ #include "sarif-diagnostics.hpp" -// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif %s > %t.txt 2>&1 || true +// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif-stderr %s > %t.txt 2>&1 || true // RUN: FileCheck -dump-input=always %s --input-file=%t.txt --check-prefixes=STDERR,SARIF +// RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif-file %s > %t.txt 2>&1 || true +// RUN: FileCheck -dump-input=always --check-prefix=STDERR %s --input-file=%t.txt +// RUN: FileCheck -dump-input=always --check-prefix=SARIF %s --input-file=sarif-diagnostics.cpp.sarif + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %clang -Wall -Wextra -fdiagnostics-format=sarif-file %s -o %t/sarif-output > %t.txt 2>&1 || true +// RUN: FileCheck -dump-input=always --check-prefix=STDERR %s --input-file=%t.txt +// RUN: FileCheck -dump-input=always --check-prefix=SARIF %s --input-file=%t/sarif-output.sarif + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %clang -Wall -Wextra -fdiagnostics-format=sarif-file %s %s -o %t/sarif-output > %t.txt 2>&1 || true +// RUN: FileCheck -dump-input=always --check-prefix=STDERR %s --input-file=%t.txt +// RUN: FileCheck -dump-input=always --check-prefix=SARIF %s --input-file=%t/sarif-output.sarif +// RUN: FileCheck -dump-input=always --check-prefix=SARIF2 %s --input-file=%t/sarif-output.sarif + // STDERR: warning: diagnostic formatting in SARIF mode is currently unstable [-Wsarif-format-unstable] // SARIF: { // SARIF: "$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", @@ -489,6 +506,460 @@ // SARIF: } // SARIF: } // SARIF: } +// SARIF2: , +// SARIF2: { +// SARIF2: "artifacts":[ +// SARIF2: { +// SARIF2: "length":{{[0-9]+}}, +// SARIF2: "location":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "mimeType":"text/plain", +// SARIF2: "roles":[ +// SARIF2: "resultFile" +// SARIF2: ] +// SARIF2: }, +// SARIF2: { +// SARIF2: "length":{{[0-9]+}}, +// SARIF2: "location":{ +// SARIF2: "index":1, +// SARIF2: "uri":"file://{{[^"]+test/Frontend/sarif-diagnostics.hpp}}" +// SARIF2: }, +// SARIF2: "mimeType":"text/plain", +// SARIF2: "roles":[ +// SARIF2: "resultFile" +// SARIF2: ] +// SARIF2: } +// SARIF2: ], +// SARIF2: "columnKind":"unicodeCodePoints", +// SARIF2: "results":[ +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":1, +// SARIF2: "startColumn":1, +// SARIF2: "startLine":12 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"'main' must return 'int'" +// SARIF2: }, +// SARIF2: "ruleId":"3485", +// SARIF2: "ruleIndex":0 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":11, +// SARIF2: "startColumn":11, +// SARIF2: "startLine":13 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"use of undeclared identifier 'hello'" +// SARIF2: }, +// SARIF2: "ruleId":"4632", +// SARIF2: "ruleIndex":1 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":17, +// SARIF2: "startColumn":17, +// SARIF2: "startLine":15 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"invalid digit 'a' in decimal constant" +// SARIF2: }, +// SARIF2: "ruleId":"898", +// SARIF2: "ruleIndex":2 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"warning", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":5, +// SARIF2: "startColumn":5, +// SARIF2: "startLine":19 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"misleading indentation; statement is not part of the previous 'if'" +// SARIF2: }, +// SARIF2: "ruleId":"1826", +// SARIF2: "ruleIndex":3 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"note", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":3, +// SARIF2: "startColumn":3, +// SARIF2: "startLine":17 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"previous statement is here" +// SARIF2: }, +// SARIF2: "ruleId":"1746", +// SARIF2: "ruleIndex":4 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"warning", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":10, +// SARIF2: "startColumn":10, +// SARIF2: "startLine":18 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"unused variable 'Yes'" +// SARIF2: }, +// SARIF2: "ruleId":"6593", +// SARIF2: "ruleIndex":5 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":12, +// SARIF2: "startColumn":12, +// SARIF2: "startLine":21 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"use of undeclared identifier 'hi'" +// SARIF2: }, +// SARIF2: "ruleId":"4632", +// SARIF2: "ruleIndex":6 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":1, +// SARIF2: "startColumn":1, +// SARIF2: "startLine":23 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"extraneous closing brace ('}')" +// SARIF2: }, +// SARIF2: "ruleId":"1400", +// SARIF2: "ruleIndex":7 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":6, +// SARIF2: "endLine":27, +// SARIF2: "startColumn":5, +// SARIF2: "startLine":27 +// SARIF2: } +// SARIF2: } +// SARIF2: }, +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":10, +// SARIF2: "endLine":27, +// SARIF2: "startColumn":9, +// SARIF2: "startLine":27 +// SARIF2: } +// SARIF2: } +// SARIF2: }, +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":7, +// SARIF2: "startColumn":7, +// SARIF2: "startLine":27 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"invalid operands to binary expression ('t1' and 't1')" +// SARIF2: }, +// SARIF2: "ruleId":"4567", +// SARIF2: "ruleIndex":8 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"note", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":0, +// SARIF2: "uri":{{"file://[^"]+/clang/test/Frontend/sarif-diagnostics.cpp"}} +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":10, +// SARIF2: "startColumn":10, +// SARIF2: "startLine":30 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"in file included from {{[^"]+test/Frontend/sarif-diagnostics.cpp:30:}}\n" +// SARIF2: }, +// SARIF2: "ruleId":"-1", +// SARIF2: "ruleIndex":9 +// SARIF2: }, +// SARIF2: { +// SARIF2: "level":"error", +// SARIF2: "locations":[ +// SARIF2: { +// SARIF2: "physicalLocation":{ +// SARIF2: "artifactLocation":{ +// SARIF2: "index":1, +// SARIF2: "uri":"file:///{{[^"]+/test/Frontend/sarif-diagnostics.hpp}}" +// SARIF2: }, +// SARIF2: "region":{ +// SARIF2: "endColumn":1, +// SARIF2: "startColumn":1, +// SARIF2: "startLine":1 +// SARIF2: } +// SARIF2: } +// SARIF2: } +// SARIF2: ], +// SARIF2: "message":{ +// SARIF2: "text":"unknown type name 'Test'" +// SARIF2: }, +// SARIF2: "ruleId":"4657", +// SARIF2: "ruleIndex":10 +// SARIF2: } +// SARIF2: ], +// SARIF2: "tool":{ +// SARIF2: "driver":{ +// SARIF2: "fullName":"", +// SARIF2: "informationUri":"https://clang.llvm.org/docs/UsersManual.html", +// SARIF2: "language":"en-US", +// SARIF2: "name":"clang", +// SARIF2: "rules":[ +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"3485", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"4632", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"898", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"warning", +// SARIF2: "rank":-1 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"1826", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"note", +// SARIF2: "rank":-1 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"1746", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"warning", +// SARIF2: "rank":-1 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"6593", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"4632", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"1400", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"4567", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"note", +// SARIF2: "rank":-1 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"-1", +// SARIF2: "name":"" +// SARIF2: }, +// SARIF2: { +// SARIF2: "defaultConfiguration":{ +// SARIF2: "enabled":true, +// SARIF2: "level":"error", +// SARIF2: "rank":50 +// SARIF2: }, +// SARIF2: "fullDescription":{ +// SARIF2: "text":"" +// SARIF2: }, +// SARIF2: "id":"4657", +// SARIF2: "name":"" +// SARIF2: } +// SARIF2: ], +// SARIF2: "version":"17.0.0" +// SARIF2: } +// SARIF2: } +// SARIF2: } // SARIF: ], // SARIF: "version":"2.1.0" // SARIF: } diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -51,3 +51,4 @@ add_clang_subdirectory(amdgpu-arch) add_clang_subdirectory(nvptx-arch) +add_clang_subdirectory(sarif-ld) diff --git a/clang/tools/driver/CMakeLists.txt b/clang/tools/driver/CMakeLists.txt --- a/clang/tools/driver/CMakeLists.txt +++ b/clang/tools/driver/CMakeLists.txt @@ -43,6 +43,7 @@ clangFrontend clangFrontendTool clangSerialization + clangTooling ) if(WIN32 AND NOT CYGWIN) diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -372,6 +372,11 @@ return 1; } +namespace llvm::sarif_linker { + [[noreturn]] void ReportErrorAndExit(llvm::errc error_code, StringRef Message); + std::error_code LinkSarifDiagnostics(const opt::ArgStringList& InputPaths, StringRef OutputPath); +} + int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) { noteBottomOfStack(); llvm::InitLLVM X(Argc, Argv); @@ -634,6 +639,14 @@ Res = 1; #endif + // Finally, we need to move any SARIF diagnostics into the build directory. + const DerivedArgList& ArgList = C->getArgs(); + if (Arg* DF = ArgList.getLastArg(options::OPT_fdiagnostics_format_EQ); DF && DF->containsValue("sarif-file") && !C->getTempFiles().empty()) { + Arg* o = ArgList.getLastArg(options::OPT_o); + const char* Out = o ? o->getValue(0) : TheDriver.getDefaultImageName(); + if (auto EC = llvm::sarif_linker::LinkSarifDiagnostics(C->getTempFiles(), Out)) {} + } + // If we have multiple failing commands, we return the result of the first // failing command. return Res; diff --git a/clang/tools/sarif-ld/CMakeLists.txt b/clang/tools/sarif-ld/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/tools/sarif-ld/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_tool( + sarif-ld + SarifLinker.cpp +) + +clang_target_link_libraries( + sarif-ld + PRIVATE clangTooling +) + +install( + PROGRAMS sarif-ld + DESTINATION "${CMAKE_INSTALL_BINDIR}" + COMPONENT sarif-ld +) diff --git a/clang/tools/sarif-ld/SarifLinker.cpp b/clang/tools/sarif-ld/SarifLinker.cpp new file mode 100644 --- /dev/null +++ b/clang/tools/sarif-ld/SarifLinker.cpp @@ -0,0 +1,92 @@ +#include "clang/Basic/Sarif.h" +#include "clang/Config/config.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Process.h" +#include +#include +#include +#include +#include +#include +#include + +namespace json = llvm::json; +namespace cl = llvm::cl; +using llvm::ArrayRef, llvm::StringRef; + +static cl::opt Help("h", cl::desc("Alias for --help"), cl::Hidden); +static cl::opt Target("o", cl::init(""), + cl::desc("Write output to .sarif")); +static cl::list Files(cl::Positional, "files", + cl::desc("[ ...]")); + +static void PrintVersion(llvm::raw_ostream &OS) { + OS << clang::getClangToolFullVersion("sarif-ld") << '\n'; +} + +namespace { +struct ParseArgsResult { + llvm::opt::ArgStringList Input; + std::string Output; +}; +} // namespace + +static ParseArgsResult ParseArgs(int argc, char *argv[]) { + llvm::cl::ParseCommandLineOptions( + argc, argv, + "A tool that links separate SARIF files into a single SARIF file."); + + if (Help) { + cl::PrintHelpMessage(); + std::exit(0); + } + + if (Files.empty()) { + llvm::errs() << argv[0] << ": error: no input files\n"; + cl::PrintHelpMessage(); + std::exit(1); + } + + if (Target.empty()) { + llvm::errs() << argv[0] << ": error: no outpt file\n"; + cl::PrintHelpMessage(); + std::exit(2); + } + + ParseArgsResult Result{llvm::opt::ArgStringList(), Target.getValue()}; + llvm::transform(Files, std::back_inserter(Result.Input), + [](StringRef File) { return File.data(); }); + return Result; +} + +namespace llvm::sarif_linker { +[[noreturn]] void ReportErrorAndExit(llvm::errc error_code, StringRef Message); +std::error_code LinkSarifDiagnostics(const opt::ArgStringList &InputPaths, + StringRef OutputPath); +} // namespace llvm::sarif_linker + +int main(int argc, char *argv[]) { + llvm::InitLLVM X(argc, argv); + llvm::setBugReportMsg("PLEASE submit a bug report to " BUG_REPORT_URL + " and include the crash backtrace, preprocessed " + "source, and associated run script.\n"); + + if (llvm::sys::Process::FixupStandardFileDescriptors()) + return 1; + + llvm::cl::SetVersionPrinter(PrintVersion); + auto [Input, Output] = ParseArgs(argc, argv); + + std::error_code EC = llvm::sarif_linker::LinkSarifDiagnostics(Input, Output); + if (EC) + llvm::sarif_linker::ReportErrorAndExit(static_cast(EC.value()), + EC.message()); +}