cjdb updated this revision to Diff 504309.
cjdb added a comment.
Herald added a project: LLVM.
Herald added a subscriber: llvm-commits.

merges in D145438 <https://reviews.llvm.org/D145438>. This patch is ready for 
review now.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D145284/new/

https://reviews.llvm.org/D145284

Files:
  clang/include/clang/Basic/DiagnosticDriverKinds.td
  clang/include/clang/Basic/DiagnosticOptions.def
  clang/include/clang/Basic/DiagnosticOptions.h
  clang/include/clang/Driver/Options.td
  clang/include/clang/Frontend/CompilerInstance.h
  clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
  clang/lib/Driver/ToolChains/Clang.cpp
  clang/lib/Frontend/CompilerInstance.cpp
  clang/lib/Frontend/FrontendAction.cpp
  clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
  clang/lib/Frontend/TextDiagnostic.cpp
  clang/lib/Tooling/CMakeLists.txt
  clang/lib/Tooling/SarifLinker.cpp
  clang/lib/Tooling/Tooling.cpp
  clang/test/Driver/fdiagnostics-format-sarif.cpp
  clang/test/Frontend/sarif-diagnostics.cpp
  clang/tools/CMakeLists.txt
  clang/tools/driver/CMakeLists.txt
  clang/tools/driver/cc1_main.cpp
  clang/tools/driver/driver.cpp
  clang/tools/sarif-ld/CMakeLists.txt
  clang/tools/sarif-ld/SarifLinker.cpp
  llvm/include/llvm/ADT/STLExtras.h

Index: llvm/include/llvm/ADT/STLExtras.h
===================================================================
--- llvm/include/llvm/ADT/STLExtras.h
+++ 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 <typename I, typename T>
+auto find_last(I First, I Last, const T &Val) {
+  using iterator_category = typename std::iterator_traits<I>::iterator_category;
+  static_assert(std::is_base_of_v<std::forward_iterator_tag, iterator_category>,
+                "llvm::find_last requires forward iterators at minimum.");
+  if constexpr (std::is_base_of_v<std::bidirectional_iterator_tag,
+                                  iterator_category>) {
+    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 <typename R, typename T> 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 <typename R, typename UnaryPredicate>
Index: clang/tools/sarif-ld/SarifLinker.cpp
===================================================================
--- /dev/null
+++ 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 <cassert>
+#include <cstdlib>
+#include <fstream>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <system_error>
+
+namespace json = llvm::json;
+namespace cl = llvm::cl;
+using llvm::ArrayRef, llvm::StringRef;
+
+static cl::opt<bool> Help("h", cl::desc("Alias for --help"), cl::Hidden);
+static cl::opt<std::string> Target("o", cl::init("<file>"),
+                                   cl::desc("Write output to <file>.sarif"));
+static cl::list<std::string> Files(cl::Positional, "files",
+                                   cl::desc("[<file> ...]"));
+
+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<llvm::errc>(EC.value()),
+                                           EC.message());
+}
Index: clang/tools/sarif-ld/CMakeLists.txt
===================================================================
--- /dev/null
+++ 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
+)
Index: clang/tools/driver/driver.cpp
===================================================================
--- clang/tools/driver/driver.cpp
+++ 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;
Index: clang/tools/driver/cc1_main.cpp
===================================================================
--- clang/tools/driver/cc1_main.cpp
+++ 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;
 
Index: clang/tools/driver/CMakeLists.txt
===================================================================
--- clang/tools/driver/CMakeLists.txt
+++ clang/tools/driver/CMakeLists.txt
@@ -43,6 +43,7 @@
   clangFrontend
   clangFrontendTool
   clangSerialization
+  clangTooling
   )
 
 if(WIN32 AND NOT CYGWIN)
Index: clang/tools/CMakeLists.txt
===================================================================
--- clang/tools/CMakeLists.txt
+++ clang/tools/CMakeLists.txt
@@ -51,3 +51,4 @@
 
 add_clang_subdirectory(amdgpu-arch)
 add_clang_subdirectory(nvptx-arch)
+add_clang_subdirectory(sarif-ld)
Index: clang/test/Frontend/sarif-diagnostics.cpp
===================================================================
--- clang/test/Frontend/sarif-diagnostics.cpp
+++ 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: }
Index: clang/test/Driver/fdiagnostics-format-sarif.cpp
===================================================================
--- clang/test/Driver/fdiagnostics-format-sarif.cpp
+++ 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]
Index: clang/lib/Tooling/Tooling.cpp
===================================================================
--- clang/lib/Tooling/Tooling.cpp
+++ clang/lib/Tooling/Tooling.cpp
@@ -684,7 +684,7 @@
 
   if (!Invocation.run())
     return nullptr;
- 
+
   assert(ASTs.size() == 1);
   return std::move(ASTs[0]);
 }
Index: clang/lib/Tooling/SarifLinker.cpp
===================================================================
--- /dev/null
+++ 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 <cassert>
+#include <cstdlib>
+#include <fstream>
+#include <iterator>
+#include <string>
+#include <string_view>
+#include <system_error>
+
+namespace llvm::sarif_linker {
+[[noreturn]] void ReportErrorAndExit(llvm::errc error_code, StringRef Message) {
+  llvm::errs() << "sarif-ld: error: " << Message << '\n';
+  std::exit(static_cast<int>(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<char>(in), {});
+  assert(!in);
+  if (!in.eof()) {
+    ReportErrorAndExit(llvm::errc::io_error, "Unable to read " + Path.str());
+  }
+
+  llvm::Expected<json::Value> 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 <class JsonValue> static auto &getRuns(JsonValue &value) {
+  return *value.getAsObject()->getArray("runs")->front().getAsObject();
+}
+
+static json::Value ReduceSarif(json::Value Init,
+                               ArrayRef<json::Value> 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<std::string> 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<json::Value> 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<json::Value>(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
Index: clang/lib/Tooling/CMakeLists.txt
===================================================================
--- clang/lib/Tooling/CMakeLists.txt
+++ 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
Index: clang/lib/Frontend/TextDiagnostic.cpp
===================================================================
--- clang/lib/Frontend/TextDiagnostic.cpp
+++ 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:
Index: clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
===================================================================
--- clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
+++ clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
@@ -22,19 +22,37 @@
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
+#include <memory>
 
 namespace clang {
 
 SARIFDiagnosticPrinter::SARIFDiagnosticPrinter(raw_ostream &OS,
                                                DiagnosticOptions *Diags)
-    : OS(OS), DiagOpts(Diags) {}
+    : OS(&OS), DiagOpts(Diags) {}
+
+static std::unique_ptr<llvm::raw_ostream>
+OpenDiagnosticFile(StringRef TargetPath, std::error_code &EC) {
+  assert(!TargetPath.empty());
+  return std::make_unique<llvm::raw_fd_ostream>(
+      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<SARIFDiagnostic>(OS, LO, &*DiagOpts, &*Writer);
+  SARIFDiag =
+      std::make_unique<SARIFDiagnostic>(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();
 }
 
Index: clang/lib/Frontend/FrontendAction.cpp
===================================================================
--- clang/lib/Frontend/FrontendAction.cpp
+++ 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<SARIFDiagnosticPrinter *>(&CI.getDiagnosticClient())
           ->setSarifWriter(
               std::make_unique<SarifDocumentWriter>(CI.getSourceManager()));
Index: clang/lib/Frontend/CompilerInstance.cpp
===================================================================
--- clang/lib/Frontend/CompilerInstance.cpp
+++ 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<DiagnosticsEngine>
-CompilerInstance::createDiagnostics(DiagnosticOptions *Opts,
-                                    DiagnosticConsumer *Client,
-                                    bool ShouldOwnClient,
-                                    const CodeGenOptions *CodeGenOpts) {
+IntrusiveRefCntPtr<DiagnosticsEngine> CompilerInstance::createDiagnostics(
+    DiagnosticOptions *Opts, DiagnosticConsumer *Client, bool ShouldOwnClient,
+    const CodeGenOptions *CodeGenOpts, StringRef OutputPath) {
   IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
   IntrusiveRefCntPtr<DiagnosticsEngine>
       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)
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ 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(
Index: clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
===================================================================
--- clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
+++ clang/include/clang/Frontend/SARIFDiagnosticPrinter.h
@@ -20,6 +20,8 @@
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/StringRef.h"
 #include <memory>
+#include <optional>
+#include <variant>
 
 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<std::error_code> EC;
+  std::variant<raw_ostream *, std::unique_ptr<raw_ostream>> OS;
   IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts;
 
   /// Handle to the currently active SARIF diagnostic emitter.
@@ -69,6 +78,16 @@
   std::string Prefix;
 
   std::unique_ptr<SarifDocumentWriter> Writer;
+
+  raw_ostream &getOS() {
+    // Deliberately not using std::visit due to there being exactly two known
+    // types.
+    if (std::holds_alternative<raw_ostream *>(OS)) {
+      return *std::get<raw_ostream *>(OS);
+    } else {
+      return *std::get<std::unique_ptr<raw_ostream>>(OS);
+    }
+  }
 };
 
 } // end namespace clang
Index: clang/include/clang/Frontend/CompilerInstance.h
===================================================================
--- clang/include/clang/Frontend/CompilerInstance.h
+++ 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<DiagnosticsEngine>
-  createDiagnostics(DiagnosticOptions *Opts,
-                    DiagnosticConsumer *Client = nullptr,
-                    bool ShouldOwnClient = true,
-                    const CodeGenOptions *CodeGenOpts = nullptr);
+  static IntrusiveRefCntPtr<DiagnosticsEngine> 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.
   ///
Index: clang/include/clang/Driver/Options.td
===================================================================
--- clang/include/clang/Driver/Options.td
+++ clang/include/clang/Driver/Options.td
@@ -1555,6 +1555,7 @@
     PosFlag<SetTrue, [], "Display include stacks for diagnostic notes">,
     NegFlag<SetFalse>, BothFlags<[CC1Option]>>;
 def fdiagnostics_format_EQ : Joined<["-"], "fdiagnostics-format=">, Group<f_clang_Group>;
+def fdiagnostics_file_path_EQ : Joined<["-"], "fdiagnostics-file-path=">, Group<f_clang_Group>;
 def fdiagnostics_show_category_EQ : Joined<["-"], "fdiagnostics-show-category=">, Group<f_clang_Group>;
 def fdiagnostics_show_template_tree : Flag<["-"], "fdiagnostics-show-template-tree">,
     Group<f_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<DiagnosticOpts<"Format">, "Clang">;
+def fdiagnostics_file_path : Separate<["-"], "fdiagnostics-file-path">,
+  HelpText<"FIX BEFORE MERGING">,
+  MarshallingInfoString<DiagnosticOpts<"FilePath">>;
 def fdiagnostics_show_category : Separate<["-"], "fdiagnostics-show-category">,
   HelpText<"Print diagnostic category">,
   Values<"none,id,name">,
Index: clang/include/clang/Basic/DiagnosticOptions.h
===================================================================
--- clang/include/clang/Basic/DiagnosticOptions.h
+++ 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<std::string> Warnings;
Index: clang/include/clang/Basic/DiagnosticOptions.def
===================================================================
--- clang/include/clang/Basic/DiagnosticOptions.def
+++ 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)
Index: clang/include/clang/Basic/DiagnosticDriverKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ 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<DiagGroup<"sarif-format-unstable">>;
+def warn_drv_implicit_diagnostics_format_file : Warning<
+  "'-fdiagnostics-file-path' should be set when using '-fdiagnostics-format=%0'">,
+  InGroup<DiagGroup<"implicit-diagnostics-format-file">>;
+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)">;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to