diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -710,6 +710,12 @@ def warn_drv_sarif_format_unstable : Warning< "diagnostic formatting in SARIF mode is currently unstable">, InGroup>; +def warn_drv_implicit_diagnostics_format_file : Warning< + "'-fdiagnostics-file-path' should be set when using '-fdiagnostics-format=%0'">, + InGroup>; +def err_drv_diagnostics_format_file_fsyntax_only : Error< + "'-fdiagnostics-file-path' must be set when combining '-fsyntax-only' with " + "'-fdiagnostics-format=%0'">; def err_drv_riscv_unsupported_with_linker_relaxation : Error< "%0 is unsupported with RISC-V linker relaxation (-mrelax)">; diff --git a/clang/include/clang/Basic/DiagnosticOptions.h b/clang/include/clang/Basic/DiagnosticOptions.h --- a/clang/include/clang/Basic/DiagnosticOptions.h +++ b/clang/include/clang/Basic/DiagnosticOptions.h @@ -74,7 +74,7 @@ friend class CompilerInvocation; public: - enum TextDiagnosticFormat { Clang, MSVC, Vi, SARIF }; + enum TextDiagnosticFormat { Clang, MSVC, Vi, SARIFStderr, SARIFFile }; // Default values. enum { @@ -106,6 +106,9 @@ /// The file to serialize diagnostics to (non-appending). std::string DiagnosticSerializationFile; + /// The file to serialise text diagnostics to (non-appending). + std::string FilePath; + /// The list of -W... options used to alter the diagnostic mappings, with the /// prefixes removed. std::vector Warnings; diff --git a/clang/include/clang/Basic/DiagnosticOptions.def b/clang/include/clang/Basic/DiagnosticOptions.def --- a/clang/include/clang/Basic/DiagnosticOptions.def +++ b/clang/include/clang/Basic/DiagnosticOptions.def @@ -63,7 +63,7 @@ VALUE_DIAGOPT(ShowCategories, 2, 0) /// Show categories: 0 -> none, 1 -> Number, /// 2 -> Full Name. -ENUM_DIAGOPT(Format, TextDiagnosticFormat, 2, Clang) /// Format for diagnostics: +ENUM_DIAGOPT(Format, TextDiagnosticFormat, 3, Clang) /// Format for diagnostics: DIAGOPT(ShowColors, 1, 0) /// Show diagnostics with ANSI color sequences. DIAGOPT(UseANSIEscapeCodes, 1, 0) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1555,6 +1555,7 @@ PosFlag, NegFlag, BothFlags<[CC1Option]>>; def fdiagnostics_format_EQ : Joined<["-"], "fdiagnostics-format=">, Group; +def fdiagnostics_file_path_EQ : Joined<["-"], "fdiagnostics-file-path=">, Group; def fdiagnostics_show_category_EQ : Joined<["-"], "fdiagnostics-show-category=">, Group; def fdiagnostics_show_template_tree : Flag<["-"], "fdiagnostics-show-template-tree">, Group, Flags<[CC1Option]>, @@ -5843,9 +5844,12 @@ def fdiagnostics_format : Separate<["-"], "fdiagnostics-format">, HelpText<"Change diagnostic formatting to match IDE and command line tools">, - Values<"clang,msvc,vi,sarif,SARIF">, - NormalizedValuesScope<"DiagnosticOptions">, NormalizedValues<["Clang", "MSVC", "Vi", "SARIF", "SARIF"]>, + Values<"clang,msvc,vi,sarif-stderr,sarif-file">, + NormalizedValuesScope<"DiagnosticOptions">, NormalizedValues<["Clang", "MSVC", "Vi", "SARIFStderr", "SARIFFile"]>, MarshallingInfoEnum, "Clang">; +def fdiagnostics_file_path : Separate<["-"], "fdiagnostics-file-path">, + HelpText<"FIX BEFORE MERGING">, + MarshallingInfoString>; def fdiagnostics_show_category : Separate<["-"], "fdiagnostics-show-category">, HelpText<"Print diagnostic category">, Values<"none,id,name">, diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -606,8 +606,10 @@ /// /// \param ShouldOwnClient If Client is non-NULL, specifies whether /// the diagnostic object should take ownership of the client. + /// + /// \param ToFile Determines if Clang should write diagnostics to a file. void createDiagnostics(DiagnosticConsumer *Client = nullptr, - bool ShouldOwnClient = true); + bool ShouldOwnClient = true, StringRef Output = ""); /// Create a DiagnosticsEngine object with a the TextDiagnosticPrinter. /// @@ -626,12 +628,16 @@ /// \param CodeGenOpts If non-NULL, the code gen options in use, which may be /// used by some diagnostics printers (for logging purposes only). /// + /// \param TargetPath Path to the target that Clang is currently producing. + /// This will be used as the prefix of any file that Clang may write + /// diagnostics to (e.g. `-fdiagnostics-format=sarif-file -o /tmp/hello` would + /// lead to there being a file called `/tmp/hello.sarif`). + /// /// \return The new object on success, or null on failure. - static IntrusiveRefCntPtr - createDiagnostics(DiagnosticOptions *Opts, - DiagnosticConsumer *Client = nullptr, - bool ShouldOwnClient = true, - const CodeGenOptions *CodeGenOpts = nullptr); + static IntrusiveRefCntPtr createDiagnostics( + DiagnosticOptions *Opts, DiagnosticConsumer *Client = nullptr, + bool ShouldOwnClient = true, const CodeGenOptions *CodeGenOpts = nullptr, + StringRef TargetPath = ""); /// Create the file manager and replace any existing one with it. /// diff --git a/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h b/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h --- a/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h +++ b/clang/include/clang/Frontend/SARIFDiagnosticPrinter.h @@ -20,6 +20,8 @@ #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/StringRef.h" #include +#include +#include namespace clang { class DiagnosticOptions; @@ -30,6 +32,12 @@ class SARIFDiagnosticPrinter : public DiagnosticConsumer { public: SARIFDiagnosticPrinter(raw_ostream &OS, DiagnosticOptions *Diags); + + /// Constructs a SARIFDiagnosticPrinter so that it writes to a file mimicing + /// Clang's current output target. The diagnostics file target has the same + /// name suffixed with `.sarif`. + SARIFDiagnosticPrinter(StringRef TargetPath, DiagnosticOptions *Diags); + ~SARIFDiagnosticPrinter() = default; SARIFDiagnosticPrinter &operator=(const SARIFDiagnosticPrinter &&) = delete; @@ -59,7 +67,8 @@ const Diagnostic &Info) override; private: - raw_ostream &OS; + std::optional EC; + std::variant> OS; IntrusiveRefCntPtr DiagOpts; /// Handle to the currently active SARIF diagnostic emitter. @@ -69,6 +78,16 @@ std::string Prefix; std::unique_ptr Writer; + + raw_ostream &getOS() { + // Deliberately not using std::visit due to there being exactly two known + // types. + if (std::holds_alternative(OS)) { + return *std::get(OS); + } else { + return *std::get>(OS); + } + } }; } // end namespace clang diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4029,12 +4029,26 @@ CmdArgs.push_back(Args.MakeArgString(Opt)); } - if (const Arg *A = Args.getLastArg(options::OPT_fdiagnostics_format_EQ)) { + if (const Arg *Format = + Args.getLastArg(options::OPT_fdiagnostics_format_EQ)) { CmdArgs.push_back("-fdiagnostics-format"); - CmdArgs.push_back(A->getValue()); - if (StringRef(A->getValue()) == "sarif" || - StringRef(A->getValue()) == "SARIF") + CmdArgs.push_back(Format->getValue()); + if (StringRef(Format->getValue()).starts_with("sarif")) D.Diag(diag::warn_drv_sarif_format_unstable); + + const Arg *Path = Args.getLastArg(options::OPT_fdiagnostics_file_path_EQ); + if (StringRef(Format->getValue()).ends_with("-file")) { + if (Path != nullptr) { + CmdArgs.push_back("-fdiagnostics-file-path"); + CmdArgs.push_back(Path->getValue()); + } else if (const Arg *SyntaxOnly = + Args.getLastArg(options::OPT_fsyntax_only)) + D.Diag(diag::err_drv_diagnostics_format_file_fsyntax_only) + << Format->getValue(); + else + D.Diag(diag::warn_drv_implicit_diagnostics_format_file) + << Format->getValue(); + } } if (const Arg *A = Args.getLastArg( 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 @@ -333,16 +333,15 @@ } void CompilerInstance::createDiagnostics(DiagnosticConsumer *Client, - bool ShouldOwnClient) { - Diagnostics = createDiagnostics(&getDiagnosticOpts(), Client, - ShouldOwnClient, &getCodeGenOpts()); + bool ShouldOwnClient, + StringRef Output) { + Diagnostics = createDiagnostics(&getDiagnosticOpts(), Client, ShouldOwnClient, + &getCodeGenOpts(), Output); } -IntrusiveRefCntPtr -CompilerInstance::createDiagnostics(DiagnosticOptions *Opts, - DiagnosticConsumer *Client, - bool ShouldOwnClient, - const CodeGenOptions *CodeGenOpts) { +IntrusiveRefCntPtr CompilerInstance::createDiagnostics( + DiagnosticOptions *Opts, DiagnosticConsumer *Client, bool ShouldOwnClient, + const CodeGenOptions *CodeGenOpts, StringRef OutputPath) { IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr Diags(new DiagnosticsEngine(DiagID, Opts)); @@ -351,10 +350,16 @@ // implementing -verify. if (Client) { Diags->setClient(Client, ShouldOwnClient); - } else if (Opts->getFormat() == DiagnosticOptions::SARIF) { + } else if (Opts->getFormat() == DiagnosticOptions::SARIFStderr) { Diags->setClient(new SARIFDiagnosticPrinter(llvm::errs(), Opts)); - } else + } else if (Opts->getFormat() == DiagnosticOptions::SARIFFile) { + if (OutputPath.empty()) + goto Default; + Diags->setClient(new SARIFDiagnosticPrinter(OutputPath, Opts)); + } else { + Default: Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), Opts)); + } // Chain in -verify checker, if requested. if (Opts->VerifyDiagnostics) 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 @@ -724,7 +724,9 @@ } if (!CI.hasSourceManager()) { CI.createSourceManager(CI.getFileManager()); - if (CI.getDiagnosticOpts().getFormat() == DiagnosticOptions::SARIF) { + if (TextDiagnosticFormat Format = CI.getDiagnosticOpts().getFormat(); + Format == DiagnosticOptions::SARIFStderr || + Format == DiagnosticOptions::SARIFFile) { static_cast(&CI.getDiagnosticClient()) ->setSarifWriter( std::make_unique(CI.getSourceManager())); diff --git a/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp --- a/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp +++ b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp @@ -22,19 +22,37 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include +#include namespace clang { SARIFDiagnosticPrinter::SARIFDiagnosticPrinter(raw_ostream &OS, DiagnosticOptions *Diags) - : OS(OS), DiagOpts(Diags) {} + : OS(&OS), DiagOpts(Diags) {} + +static std::unique_ptr +OpenDiagnosticFile(StringRef TargetPath, std::error_code &EC) { + assert(!TargetPath.empty()); + return std::make_unique( + std::string(TargetPath) + ".sarif", EC); +} + +SARIFDiagnosticPrinter::SARIFDiagnosticPrinter(StringRef TargetPath, + DiagnosticOptions *Diags) + : EC(std::error_code()), OS(OpenDiagnosticFile(TargetPath, *EC)), + DiagOpts(Diags) { + if (*EC) { + assert(false and "not implemented yet: not even sure what to do"); + } +} void SARIFDiagnosticPrinter::BeginSourceFile(const LangOptions &LO, const Preprocessor *PP) { // Build the SARIFDiagnostic utility. assert(hasSarifWriter() && "Writer not set!"); assert(!SARIFDiag && "SARIFDiagnostic already set."); - SARIFDiag = std::make_unique(OS, LO, &*DiagOpts, &*Writer); + SARIFDiag = + std::make_unique(getOS(), LO, &*DiagOpts, &*Writer); // Initialize the SARIF object. Writer->createRun("clang", Prefix); } @@ -43,8 +61,13 @@ assert(SARIFDiag && "SARIFDiagnostic has not been set."); Writer->endRun(); llvm::json::Value Value(Writer->createDocument()); - OS << "\n" << Value << "\n\n"; - OS.flush(); + getOS() << '\n' << Value << '\n'; + getOS().flush(); + if (EC and *EC) { + llvm::errs() << "error: " << EC->message() << '\n'; + llvm::errs() << "outputting to stderr as a backup\n"; + llvm::errs() << Value << '\n'; + } SARIFDiag.reset(); } diff --git a/clang/lib/Frontend/TextDiagnostic.cpp b/clang/lib/Frontend/TextDiagnostic.cpp --- a/clang/lib/Frontend/TextDiagnostic.cpp +++ b/clang/lib/Frontend/TextDiagnostic.cpp @@ -815,7 +815,8 @@ emitFilename(PLoc.getFilename(), Loc.getManager()); switch (DiagOpts->getFormat()) { - case DiagnosticOptions::SARIF: + case DiagnosticOptions::SARIFStderr: + case DiagnosticOptions::SARIFFile: case DiagnosticOptions::Clang: if (DiagOpts->ShowLine) OS << ':' << LineNo; @@ -838,7 +839,8 @@ OS << ColNo; } switch (DiagOpts->getFormat()) { - case DiagnosticOptions::SARIF: + case DiagnosticOptions::SARIFStderr: + case DiagnosticOptions::SARIFFile: case DiagnosticOptions::Clang: case DiagnosticOptions::Vi: OS << ':'; break; case DiagnosticOptions::MSVC: 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/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -684,7 +684,7 @@ if (!Invocation.run()) return nullptr; - + assert(ASTs.size() == 1); return std::move(ASTs[0]); } diff --git a/clang/test/Driver/fdiagnostics-format-sarif.cpp b/clang/test/Driver/fdiagnostics-format-sarif.cpp --- a/clang/test/Driver/fdiagnostics-format-sarif.cpp +++ b/clang/test/Driver/fdiagnostics-format-sarif.cpp @@ -1,5 +1,6 @@ -// RUN: %clang -fsyntax-only -fdiagnostics-format=sarif %s -### 2>&1 | FileCheck %s --check-prefix=WARN +// RUN: %clang -fsyntax-only -fdiagnostics-format=sarif-stderr %s -### 2>&1 | FileCheck %s --check-prefix=WARN +// RUN: %clang -fsyntax-only -fdiagnostics-format=sarif-file %s -### 2>&1 | FileCheck %s --check-prefixes=WARN,ERROR +// RUN: %clang -fsyntax-only -fdiagnostics-file-path=test %s -### 2>&1 | FileCheck %s --check-prefixes=WARN2 // WARN: warning: diagnostic formatting in SARIF mode is currently unstable [-Wsarif-format-unstable] - -// RUN: %clang -fsyntax-only -fdiagnostics-format=SARIF %s -### 2>&1 | FileCheck %s --check-prefix=WARN2 -// WARN2: warning: diagnostic formatting in SARIF mode is currently unstable [-Wsarif-format-unstable] +// ERROR: error: '-fdiagnostics-file-path' must be set when combining '-fsyntax-only' with '-fdiagnostics-format=sarif-file' +// WARN2: warning: argument unused during compilation: '-fdiagnostics-file-path=test' [-Wunused-command-line-argument] 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,10 +29,35 @@ #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 -fdiagnostics-file-path=%t %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=%t.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-prefixes=STDERR,NOFILE %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-prefixes=STDERR,NOFILE %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 + +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: %clang -Wall -Wextra -fdiagnostics-format=sarif-file -fdiagnostics-file-path=%t/diags %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/diags.sarif +// RUN: grep -v "warning: '-fdiagnostics-file-path' should be set when using '-fdiagnostics-format=sarif-file'" %t.txt + // STDERR: warning: diagnostic formatting in SARIF mode is currently unstable [-Wsarif-format-unstable] +// NOFILE: warning: '-fdiagnostics-file-path' should be set when using '-fdiagnostics-format=sarif-file' // SARIF: { // SARIF: "$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", // SARIF: "runs":[ @@ -489,6 +514,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/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp --- a/clang/tools/driver/cc1_main.cpp +++ b/clang/tools/driver/cc1_main.cpp @@ -230,7 +230,13 @@ CompilerInvocation::GetResourcesPath(Argv0, MainAddr); // Create the actual diagnostics engine. - Clang->createDiagnostics(); + auto DiagnosticsFilePathOption = + llvm::find_last(Argv, StringRef("-fdiagnostics-file-path")); + std::string DiagnosticsFilePath = + std::distance(DiagnosticsFilePathOption, Argv.end()) > 1 + ? *std::next(DiagnosticsFilePathOption) + : Clang->getFrontendOpts().OutputFile; + Clang->createDiagnostics(nullptr, true, DiagnosticsFilePath); if (!Clang->hasDiagnostics()) return 1; 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,12 @@ 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); +} // namespace llvm::sarif_linker + int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) { noteBottomOfStack(); llvm::InitLLVM X(Argc, Argv); @@ -634,6 +640,17 @@ 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()); +} diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h --- a/llvm/include/llvm/ADT/STLExtras.h +++ b/llvm/include/llvm/ADT/STLExtras.h @@ -1804,6 +1804,34 @@ return std::find(adl_begin(Range), adl_end(Range), Val); } +/// std::ranges::find_last means that we don't need to work with +/// reverse_iterators as often, and can also find the last matching element of a +/// non-bidirectional range. +template +auto find_last(I First, I Last, const T &Val) { + using iterator_category = typename std::iterator_traits::iterator_category; + static_assert(std::is_base_of_v, + "llvm::find_last requires forward iterators at minimum."); + if constexpr (std::is_base_of_v) { + for (auto Current = Last; Current-- != First;) + if (*Current == Val) + return Current; + + return Last; + } else { + auto Result = Last; + for (; First != Last; ++First) + if (*First == Val) + Result = First; + return Result; + } +} + +template auto find_last(R &&Range, const T &Val) { + return llvm::find_last(adl_begin(Range), adl_end(Range), Val); +} + /// Provide wrappers to std::find_if which take ranges instead of having to pass /// begin/end explicitly. template