diff mbox series

[7/7] libdiagnostics: add a "sarif-replay" command-line tool [PR96032]

Message ID 20240815181156.1815075-8-dmalcolm@redhat.com
State New
Headers show
Series v3 of libdiagnostics | expand

Commit Message

David Malcolm Aug. 15, 2024, 6:11 p.m. UTC
This patch adds a new "sarif-replay" command-line tool for
viewing .sarif files.  It uses libdiagnostics to "replay"
any diagnostics found in the .sarif files in text form as if
they were GCC diagnostics.

contrib/ChangeLog:
	PR other/96032
	* regenerate-sarif-spec-index.py: New file.

gcc/ChangeLog:
	PR other/96032
	* Makefile.in (lang_checks): If libdiagnostics is enabled, add
	check-sarif-replay.
	(SARIF_REPLAY_OBJS): New.
	(ALL_HOST_OBJS): If libdiagnostics is enabled, add
	$(SARIF_REPLAY_OBJS).
	(sarif-replay): New.
	(install-libdiagnostics): Add sarif-replay to deps, and install
	it.
	* configure: Regenerate.
	* configure.ac (check_languages): If libdiagnostics is enabled,
	add check-sarif-replay.
	(LIBDIAGNOSTICS): If libdiagnostics is enabled, add sarif-replay.
	* doc/install.texi (--enable-libdiagnostics): Note that it also
	enables sarif-replay.
	* libsarifreplay.cc: New file.
	* libsarifreplay.h: New file.
	* sarif-replay.cc: New file.
	* sarif-spec-urls.def: New file.

gcc/testsuite/ChangeLog:
	PR other/96032
	* lib/gcc-dg.exp (gcc-dg-test-1): Add "replay-sarif".
	* lib/sarif-replay-dg.exp: New file.
	* lib/sarif-replay.exp: New file.
	* sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif: New test.
	* sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif:
	New test.
	* sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif:
	New test.
	* sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif:
	New test.
	* sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif: New test.
	* sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif:
	New test.
	* sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif: New test.
	* sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif: New test.
	* sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif:
	New test.
	* sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif: New test.
	* sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/error-with-note.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/escaped-braces.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/null-runs.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/signal-1.c.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/spec-example-1.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/spec-example-2.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/spec-example-3.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/spec-example-4.sarif: New test.
	* sarif-replay.dg/2.1.0-valid/tutorial-example.sarif: New test.
	* sarif-replay.dg/dg.exp: New script.
	* sarif-replay.dg/malformed-json/array-missing-comma.sarif: New test.
	* sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif:
	New test.
	* sarif-replay.dg/malformed-json/bad-token.sarif: New test.
	* sarif-replay.dg/malformed-json/object-missing-comma.sarif: New test.
	* sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif:
	New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 contrib/regenerate-sarif-spec-index.py        |   60 +
 gcc/Makefile.in                               |   16 +-
 gcc/configure                                 |    4 +-
 gcc/configure.ac                              |    4 +-
 gcc/doc/install.texi                          |    6 +
 gcc/libsarifreplay.cc                         | 1747 +++++++++++++++++
 gcc/libsarifreplay.h                          |   59 +
 gcc/sarif-replay.cc                           |  239 +++
 gcc/sarif-spec-urls.def                       |  496 +++++
 gcc/testsuite/lib/gcc-dg.exp                  |    4 +
 gcc/testsuite/lib/sarif-replay-dg.exp         |   90 +
 gcc/testsuite/lib/sarif-replay.exp            |  204 ++
 .../2.1.0-invalid/3.1-not-an-object.sarif     |    6 +
 .../3.11.11-malformed-placeholder.sarif       |   15 +
 ...1-missing-arguments-for-placeholders.sarif |   14 +
 ...ot-enough-arguments-for-placeholders.sarif |   14 +
 .../2.1.0-invalid/3.13.2-no-version.sarif     |    6 +
 .../3.13.2-version-not-a-string.sarif         |    6 +
 .../2.1.0-invalid/3.13.4-bad-runs.sarif       |    7 +
 .../2.1.0-invalid/3.13.4-no-runs.sarif        |    6 +
 .../3.13.4-non-object-in-runs.sarif           |    7 +
 .../2.1.0-invalid/3.27.10-bad-level.sarif     |   25 +
 .../2.1.0-unhandled/3.27.10-none-level.sarif  |   25 +
 .../2.1.0-valid/error-with-note.sarif         |   34 +
 .../2.1.0-valid/escaped-braces.sarif          |   17 +
 .../2.1.0-valid/null-runs.sarif               |    2 +
 .../2.1.0-valid/signal-1.c.sarif              |  193 ++
 .../2.1.0-valid/spec-example-1.sarif          |   15 +
 .../2.1.0-valid/spec-example-2.sarif          |   73 +
 .../2.1.0-valid/spec-example-3.sarif          |   65 +
 .../2.1.0-valid/spec-example-4.sarif          |  766 ++++++++
 .../2.1.0-valid/tutorial-example.sarif        |  117 ++
 gcc/testsuite/sarif-replay.dg/dg.exp          |   46 +
 .../malformed-json/array-missing-comma.sarif  |    6 +
 .../array-with-trailing-comma.sarif           |    6 +
 .../malformed-json/bad-token.sarif            |    6 +
 .../malformed-json/object-missing-comma.sarif |    7 +
 .../object-with-trailing-comma.sarif          |    6 +
 38 files changed, 4412 insertions(+), 7 deletions(-)
 create mode 100644 contrib/regenerate-sarif-spec-index.py
 create mode 100644 gcc/libsarifreplay.cc
 create mode 100644 gcc/libsarifreplay.h
 create mode 100644 gcc/sarif-replay.cc
 create mode 100644 gcc/sarif-spec-urls.def
 create mode 100644 gcc/testsuite/lib/sarif-replay-dg.exp
 create mode 100644 gcc/testsuite/lib/sarif-replay.exp
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/dg.exp
 create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif
 create mode 100644 gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif
diff mbox series

Patch

diff --git a/contrib/regenerate-sarif-spec-index.py b/contrib/regenerate-sarif-spec-index.py
new file mode 100644
index 000000000000..da9dfb59379d
--- /dev/null
+++ b/contrib/regenerate-sarif-spec-index.py
@@ -0,0 +1,60 @@ 
+#!/usr/bin/env python3
+
+# Copyright (C) 2024 Free Software Foundation, Inc.
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Unfortunately the SARIF 2.1.0 spec doesn't have memorable anchors
+# for its subsections
+# (filed as https://github.com/oasis-tcs/sarif-spec/issues/533)
+#
+# In the meantime, use this script to generate a table mapping subsections
+# to anchors.
+
+from pprint import pprint
+import re
+
+spec_url = 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html'
+filename_in = 'sarif-v2.1.0-errata01-os-complete.html'
+
+d = {}
+with open(filename_in, encoding='windows-1252') as infile:
+    for line in infile:
+        m = re.match(r'<p class=MsoToc[0-9]+><a href="#(.*)">(.*)<span.*\n', line)
+        if m:
+            #print('MATCH')
+            #print(repr(line))
+            #print(m.groups())
+            m2 = re.match(r'([0-9.]+) .*', m.group(2))
+            if m2:
+                #print(m2.groups())
+                d[m2.group(1)] = m.group(1)
+
+filename_out = '../gcc/sarif-spec-urls.def'
+with open(filename_out, 'w') as outfile:
+    outfile.write('/* Generated by regenerate-sarif-spec-index.py.  */\n\n')
+
+    outfile.write(f'static const char * const sarif_spec_base_url\n  = "{spec_url}";\n\n')
+
+    outfile.write('static const struct ref_anchor\n'
+                  '{\n'
+                  '  const char *m_ref;\n'
+                  '  const char *m_anchor;\n'
+                  '} sarif_spec_anchor_arr[] = {\n');
+    for ref, anchor in d.items():
+        outfile.write(f'  {{ "{ref}", "{anchor}" }},\n')
+    outfile.write('};')
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 64dcaddfdfbe..560949f62424 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -618,7 +618,7 @@  xm_defines=@xm_defines@
 lang_checks=
 lang_checks_parallelized=
 ifeq (@enable_libdiagnostics@,yes)
-lang_checks += check-libdiagnostics
+lang_checks += check-libdiagnostics check-sarif-replay
 endif
 lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
 lang_specs_files=@lang_specs_files@
@@ -1294,6 +1294,8 @@  RUST_TARGET_OBJS=@rust_target_objs@
 # Object files for gcc many-languages driver.
 GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o options-urls.o
 
+SARIF_REPLAY_OBJS = sarif-replay.o libsarifreplay.o
+
 c-family-warn = $(STRICT_WARN)
 
 # Language-specific object files shared by all C-family front ends.
@@ -1879,7 +1881,7 @@  endif
 ALL_HOST_OBJS = $(ALL_HOST_FRONTEND_OBJS) $(ALL_HOST_BACKEND_OBJS)
 
 ifeq (@enable_libdiagnostics@,yes)
-ALL_HOST_OBJS += $(libdiagnostics_OBJS)
+ALL_HOST_OBJS += $(libdiagnostics_OBJS) $(SARIF_REPLAY_OBJS)
 endif
 
 BACKEND = libbackend.a main.o libcommon-target.a libcommon.a \
@@ -2400,6 +2402,12 @@  $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
 	ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
 endif
 
+# sarif-replay: a command-line tool that uses libdiagnostics to
+# replay SARIF files
+sarif-replay: $(SARIF_REPLAY_OBJS) $(LIBDIAGNOSTICS_FILENAME)
+	+$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ \
+	  $(SARIF_REPLAY_OBJS) $(LIBDIAGNOSTICS_FILENAME) $(LIBS)
+
 # Dump a specs file to make -B./ read these specs over installed ones.
 $(SPECS): xgcc$(exeext)
 	$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
@@ -4159,7 +4167,9 @@  libdiagnostics.install-common: installdirs libdiagnostics.install-headers
 endif
 endif
 
-install-libdiagnostics: libdiagnostics.install-common
+install-libdiagnostics: libdiagnostics.install-common sarif-replay
+	-rm -f $(DESTDIR)$(bindir)/sarif-replay
+	-$(INSTALL_PROGRAM) sarif-replay $(DESTDIR)$(bindir)/sarif-replay
 
 # Install the info files.
 # $(INSTALL_DATA) might be a relative pathname, so we can't cd into srcdir
diff --git a/gcc/configure b/gcc/configure
index 5e2f4644c528..cafdf1300955 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -33793,7 +33793,7 @@  do
 	check_languages="$check_languages check-$language"
 done
 if test x$enable_libdiagnostics = xyes; then
-	check_languages="$check_languages check-libdiagnostics"
+	check_languages="$check_languages check-libdiagnostics check-sarif-replay"
 fi
 
 selftest_languages=
@@ -34241,7 +34241,7 @@  fi
 
 
 if test "$enable_libdiagnostics" = "yes"; then
-  LIBDIAGNOSTICS='libdiagnostics'
+  LIBDIAGNOSTICS='libdiagnostics sarif-replay'
 else
   LIBDIAGNOSTICS=''
 fi
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 0edd1b92f5fb..16f91dc552dd 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -7372,7 +7372,7 @@  do
 	check_languages="$check_languages check-$language"
 done
 if test x$enable_libdiagnostics = xyes; then
-	check_languages="$check_languages check-libdiagnostics"
+	check_languages="$check_languages check-libdiagnostics check-sarif-replay"
 fi
 
 selftest_languages=
@@ -7612,7 +7612,7 @@  AC_ARG_ENABLE(libdiagnostics,
 AC_SUBST(enable_libdiagnostics)
 
 if test "$enable_libdiagnostics" = "yes"; then
-  LIBDIAGNOSTICS='libdiagnostics'
+  LIBDIAGNOSTICS='libdiagnostics sarif-replay'
 else
   LIBDIAGNOSTICS=''
 fi
diff --git a/gcc/doc/install.texi b/gcc/doc/install.texi
index f1d64833cc8a..3624b544e27b 100644
--- a/gcc/doc/install.texi
+++ b/gcc/doc/install.texi
@@ -1233,6 +1233,12 @@  GCC's diagnostics capabilities via a C API, and a C++ wrapper API adding
 
 This option requires @option{--enable-host-shared} on non-Windows hosts.
 
+This option also enables @code{sarif-replay}, a command-line tool for
+viewing @uref{https://sarif.info/,,SARIF files}.  @code{sarif-replay} takes
+one or more @code{.sarif} files as input and attempts to replay any
+diagnostics within them to stderr (via @code{libdiagnostics}) in the style
+of GCC's diagnostics.
+
 @item --disable-gcov
 Specify that the run-time library used for coverage analysis
 and associated host tools should not be built.
diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc
new file mode 100644
index 000000000000..e726e101c61b
--- /dev/null
+++ b/gcc/libsarifreplay.cc
@@ -0,0 +1,1747 @@ 
+/* A library for re-emitting diagnostics saved in SARIF form
+   via libdiagnostics.
+   Copyright (C) 2022-2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_VECTOR
+#define INCLUDE_MAP
+#define INCLUDE_MEMORY
+#define INCLUDE_STRING
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "libdiagnostics++.h"
+#include "json-parsing.h"
+#include "intl.h"
+#include "sarif-spec-urls.def"
+#include "libsarifreplay.h"
+#include "label-text.h"
+
+namespace {
+
+/* Read the contents of PATH into memory.
+   Issue an error to MGR and return nullptr if there are any problems.  */
+
+static std::unique_ptr<std::vector<char>>
+read_file (const char *path, libdiagnostics::manager &mgr)
+{
+  FILE *f_in = fopen (path, "r");
+  if (!f_in)
+    {
+      char *errmsg = xstrerror (errno);
+      auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+      err.finish ("cannot open %qs: %s", path, errmsg);
+      return nullptr;
+    }
+
+  /* Read content, allocating a buffer for it.  */
+  auto result = ::make_unique<std::vector<char>> ();
+  char buf[4096];
+  size_t iter_sz_in;
+
+  while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
+    {
+      size_t old_total_sz = result->size ();
+      size_t new_total_sz = old_total_sz + iter_sz_in;
+      size_t old_alloc_sz = result->capacity ();
+      if (new_total_sz > old_alloc_sz)
+	{
+	  size_t new_alloc_sz = std::max (old_alloc_sz * 2, new_total_sz);
+	  result->reserve (new_alloc_sz);
+	}
+      gcc_assert (result->capacity () >= new_total_sz);
+      result->resize (new_total_sz);
+      memcpy (result->data () + old_total_sz, buf, iter_sz_in);
+    }
+
+  if (!feof (f_in))
+    {
+      char *errmsg = xstrerror (errno);
+      auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+      err.finish ("error reading from %qs: %s", path, errmsg);
+      return nullptr;
+    }
+
+  fclose (f_in);
+
+  return result;
+}
+
+static libdiagnostics::physical_location
+make_physical_location (libdiagnostics::manager &mgr,
+			libdiagnostics::file f,
+			const json::location_map::point &point)
+{
+  /* json::location_map::point uses 0-based columns,
+     whereas libdiagnostics uses 1-based columns.  */
+  return mgr.new_location_from_file_line_column (f,
+						 point.m_line,
+						 point.m_column + 1);
+}
+
+static libdiagnostics::physical_location
+make_physical_location (libdiagnostics::manager &mgr,
+			libdiagnostics::file f,
+			const json::location_map::range &range)
+{
+  libdiagnostics::physical_location start
+    = make_physical_location (mgr, f, range.m_start);
+  libdiagnostics::physical_location end
+    = make_physical_location (mgr, f, range.m_end);
+  return mgr.new_location_from_range (start, start, end);
+}
+
+enum class status
+{
+  ok,
+  err_reading_file,
+  err_malformed_json,
+  err_invalid_sarif,
+  err_unhandled_sarif
+};
+
+/* A reference to the SARIF specification.  */
+
+class spec_ref
+{
+public:
+  spec_ref (const char *section)
+    : m_section (section)
+  {}
+
+  virtual char *make_description () const
+  {
+    /* 'SECTION SIGN' (U+00A7).  */
+#define SECTION_SIGN_UTF8 "\xC2\xA7"
+    return xasprintf ("SARIF v2.1.0 " SECTION_SIGN_UTF8 "%s", m_section);
+  }
+
+  char *make_url () const
+  {
+    const char *anchor = get_anchor_for_section (m_section);
+    if (!anchor)
+      return nullptr;
+    return xasprintf ("%s#%s", sarif_spec_base_url, anchor);
+  }
+
+private:
+  static const char *
+  get_anchor_for_section (const char *section)
+  {
+    /* Linear search, but the array is only a few hundred entries .  */
+    for (size_t i = 0; i < ARRAY_SIZE (sarif_spec_anchor_arr); i++)
+      {
+	if (strcmp (sarif_spec_anchor_arr[i].m_ref, section) == 0)
+	  return sarif_spec_anchor_arr[i].m_anchor;
+      }
+    return nullptr;
+  }
+
+  /* e.g. "3.1" for section 3.1 of the spec.  */
+  const char *m_section;
+};
+
+/* A reference to the SARIF specification for a particular kind of object.  */
+
+class object_spec_ref : public spec_ref
+{
+public:
+  object_spec_ref (const char *obj_name, const char *section)
+  : spec_ref (section),
+    m_obj_name (obj_name)
+  {}
+
+  const char *get_obj_name () const { return m_obj_name; }
+
+private:
+  const char *m_obj_name;
+};
+
+/* A reference to the SARIF specification for a particular property
+   of a particular kind of object.  */
+
+class property_spec_ref : public object_spec_ref
+{
+public:
+  property_spec_ref (const char *obj_name,
+		     const char *property_name,
+		     const char *section)
+  : object_spec_ref (obj_name, section),
+    m_property_name (property_name)
+  {}
+
+  const char *get_property_name () const { return m_property_name; }
+
+private:
+  const char *m_property_name;
+};
+
+template <typename ValueType>
+struct string_property_value
+{
+  const char *m_string;
+  ValueType m_value;
+};
+
+class sarif_replayer
+{
+public:
+  sarif_replayer (libdiagnostics::manager &&output_manager,
+		  libdiagnostics::manager &&control_manager)
+  : m_output_mgr (std::move (output_manager)),
+    m_control_mgr (std::move (control_manager)),
+    m_driver_obj (nullptr),
+    m_artifacts_arr (nullptr)
+  {
+  }
+
+  enum status replay_file (const char *filename,
+			   const replay_options &replay_opts);
+
+private:
+  class replayer_location_map : public json::location_map
+  {
+  public:
+    void record_range_for_value (json::value *jv,
+				 const range &r) final override
+    {
+      m_map_jv_to_range[jv] = r;
+    }
+
+    const json::location_map::range &
+    get_range_for_value (const json::value &jv) const
+    {
+      auto iter = m_map_jv_to_range.find (&jv);
+      gcc_assert (iter != m_map_jv_to_range.end ());
+      return iter->second;
+    }
+
+  private:
+    std::map<const json::value *, range> m_map_jv_to_range;
+  };
+
+  enum status emit_sarif_as_diagnostics (const json::value &jv);
+
+  label_text
+  make_plain_text_within_result_message (const json::object *tool_component_obj,
+					 const json::object &message_obj,
+					 const json::object *rule_obj);
+
+  /* Handlers for specific parts of the SARIF spec.
+     Keep this in the same order as the spec.  */
+
+  // "artifactLocation" object (§3.4)
+  enum status
+  handle_artifact_location_object (const json::object &artifact_loc,
+				   libdiagnostics::file &out);
+
+  // Message string lookup algorithm (§3.11.7)
+  const char *
+  lookup_plain_text_within_result_message (const json::object *tool_component_obj,
+					   const json::object &message_obj,
+					   const json::object *rule_obj);
+
+  // "multiformatMessageString" object (§3.12).
+  const char *
+  get_plain_text_from_mfms (json::value &mfms_val,
+			    const property_spec_ref &prop);
+
+  // "run" object (§3.14)
+  enum status
+  handle_run_obj (const json::object &run_obj);
+
+  // "tool" object (§3.18)
+  enum status
+  handle_tool_obj (const json::object &tool_obj);
+
+  // "result" object (§3.27)
+  enum status
+  handle_result_obj (const json::object &result_obj,
+		     const json::object &tool_obj);
+  json::result<enum diagnostic_level, enum status>
+  get_level_from_level_str (const json::string &level_str);
+
+  // "location" object (§3.28)
+  enum status
+  handle_location_object (const json::object &location_obj,
+			  libdiagnostics::physical_location &out_physical_loc,
+			  libdiagnostics::logical_location &out_logical_loc);
+
+  // "physicalLocation" object (§3.29)
+  enum status
+  handle_physical_location_object (const json::object &phys_loc_obj,
+				   libdiagnostics::physical_location &out);
+
+  // "region" object (§3.30)
+  enum status
+  handle_region_object (const json::object &region_obj,
+			libdiagnostics::file file,
+			libdiagnostics::physical_location &out);
+
+  // "logicalLocation" object (§3.33)
+  enum status
+  handle_logical_location_object (const json::object &logical_loc_obj,
+				  libdiagnostics::logical_location &out);
+
+  // "threadFlow" object (§3.37)
+  enum status
+  handle_thread_flow_object (const json::object &thread_flow_obj,
+			     libdiagnostics::execution_path &out);
+
+  // "threadFlowLocation" object (§3.38)
+  enum status
+  handle_thread_flow_location_object (const json::object &tflow_loc_obj,
+				      libdiagnostics::execution_path &out);
+
+  // reportingDescriptor lookup (§3.52.3)
+  const json::object *
+  lookup_rule_by_id_in_tool (const char *rule_id,
+			     const json::object &tool_obj,
+			     const json::object *&tool_component_obj);
+
+  const json::object *
+  lookup_rule_by_id_in_component (const char *rule_id,
+				  const json::object &tool_component_obj);
+
+  /* Support functions.  */
+
+  /* Report an error to m_control_mgr about JV violating REF,
+     and return status::err_invalid_sarif.  */
+
+  enum status
+  report_invalid_sarif (const json::value &jv,
+			const spec_ref &ref,
+			const char *gmsgid, ...)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5)
+  {
+    va_list ap;
+    va_start (ap, gmsgid);
+    report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_ERROR);
+    va_end (ap);
+    return status::err_invalid_sarif;
+  }
+
+  /* Report a "sorry" to m_control_mgr inability to handle JV and REF,
+     and return status::err_unhandled_sarif.  */
+
+  enum status
+  report_unhandled_sarif (const json::value &jv,
+			  const spec_ref &ref,
+			  const char *gmsgid, ...)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5)
+  {
+    va_list ap;
+    va_start (ap, gmsgid);
+    report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_SORRY);
+    va_end (ap);
+    return status::err_unhandled_sarif;
+  }
+
+  void
+  report_problem (const json::value &jv,
+		  const spec_ref &ref,
+		  const char *gmsgid,
+		  va_list *args,
+		  enum diagnostic_level level)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 0)
+  {
+    auto diag (m_control_mgr.begin_diagnostic (level));
+
+    /* Add rule specifying the pertinent section of the specification.
+       There doesn't seem to be a systematic mapping from spec sections to
+       HTML anchors, so we can't provide URLs
+       (filed as https://github.com/oasis-tcs/sarif-spec/issues/533 ).  */
+    char *ref_desc = ref.make_description ();
+    char *ref_url = ref.make_url ();
+    diag.add_rule (ref_desc, ref_url);
+    free (ref_desc);
+    free (ref_url);
+
+    auto loc_range
+      = make_physical_location (m_control_mgr,
+				m_loaded_file,
+				m_json_location_map.get_range_for_value (jv));
+    diag.set_location (loc_range);
+
+    diag.finish_va (gmsgid, args);
+  }
+
+  /* Require OBJ to have at least one of OBJ_PROP1 or OBJ_PROP2.
+     If successful, result status::ok.
+     Otherwise, complain about OBJ_CONSTRAINTS and return
+     status::invalid_sarif.  */
+  enum status
+  report_invalid_sarif_at_least_one_of (const json::object &obj,
+					const object_spec_ref &obj_constraints,
+					const property_spec_ref &obj_prop_1,
+					const property_spec_ref &obj_prop_2)
+  {
+      return report_invalid_sarif
+	(obj, obj_constraints,
+	 "expected SARIF %qs object to contain at least one of %qs or %qs",
+	 obj_constraints.get_obj_name (),
+	 obj_prop_1.get_property_name (),
+	 obj_prop_2.get_property_name ());
+  }
+
+  /* Require VAL to be a json::object.
+     If successful, return it as an object.
+     Otherwise, complain using REF and return nullptr.  */
+  const json::object *
+  require_object (const json::value &val, const property_spec_ref &ref)
+  {
+    const json::object *obj = dyn_cast <const json::object *> (&val);
+    if (!obj)
+      {
+	report_invalid_sarif (val, ref, "expected %s.%s to be an object",
+			      ref.get_obj_name (), ref.get_property_name ());
+	return nullptr;
+      }
+    return obj;
+  }
+
+  /* Require VAL to be a json::string
+     If successful, return it as an string.
+     Otherwise, complain using REF and return nullptr.  */
+  const json::string *
+  require_string (const json::value &val, const property_spec_ref &ref)
+  {
+    const json::string *str = dyn_cast <const json::string *> (&val);
+    if (!str)
+      {
+	report_invalid_sarif (val, ref, "expected %s.%s to be an string",
+			      ref.get_obj_name (), ref.get_property_name ());
+	return nullptr;
+      }
+    return str;
+  }
+  /* Look for an optional property within OBJ based on REF.  */
+  const json::value *
+  get_optional_property (const json::object &obj,
+			 const property_spec_ref &ref)
+  {
+    return obj.get (ref.get_property_name ());
+  }
+
+  /* Look for a property of VAL based on REF.
+     If present, it must be of kind JsonType.
+     If found and valid, return the property's value.
+     If not found, silently return nullptr.
+     Otherwise, complain and return nullptr.  */
+  template <typename JsonType>
+  const JsonType *
+  get_optional_property (const json::object &obj,
+			 const property_spec_ref &ref)
+  {
+    const json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      return nullptr;
+    const JsonType *sub = dyn_cast<const JsonType *> (property_val);
+    if (!sub)
+      {
+	/* Property is wrong kind of value.  */
+	report_bad_property_kind<JsonType> (obj, ref, *property_val);
+	return nullptr;
+      }
+    return sub;
+  }
+
+  /* Require VAL to be a json::object.
+     Look for a property of VAL based on REF, which must be of
+     kind JsonType.
+     If successful, return the property's value.
+     Otherwise, complain and return nullptr.  */
+  template <typename JsonType>
+  const JsonType *
+  get_required_property (const json::value &val,
+			 const property_spec_ref &ref)
+  {
+    const json::object *obj = require_object (val, ref);
+    if (!obj)
+      return nullptr;
+    return get_required_property<JsonType> (*obj, ref);
+  }
+
+  /* Look for a property of VAL based on REF, which must be of
+     kind JsonType.
+     If successful, return the property's value.
+     Otherwise, complain and return nullptr.  */
+  template <typename JsonType>
+  const JsonType *
+  get_required_property (const json::object &obj,
+			 const property_spec_ref &ref)
+  {
+    const json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      {
+	/* Property not present.  */
+	report_invalid_sarif (obj, ref,
+			      "expected %s object to have a %qs property",
+			      ref.get_obj_name (), ref.get_property_name ());
+	return nullptr;
+      }
+    const JsonType *sub = dyn_cast<const JsonType *> (property_val);
+    if (!sub)
+      {
+	/* Property is wrong kind of value.  */
+	report_bad_property_kind<JsonType> (obj, ref, *property_val);
+	return nullptr;
+      }
+    return sub;
+  }
+
+  template <typename JsonType>
+  void
+  report_bad_property_kind (const json::object &obj,
+			    const property_spec_ref &ref,
+			    const json::value &property_val);
+
+  const json::object *
+  require_object_for_element (const json::value &jv,
+			      const property_spec_ref &ref)
+  {
+    const json::object *obj = dyn_cast <const json::object *> (&jv);
+    if (!obj)
+      {
+	report_invalid_sarif (jv, ref,
+			      "expected element of %s.%s array to be an object",
+			      ref.get_obj_name (), ref.get_property_name ());
+	return nullptr;
+      }
+    return obj;
+  }
+
+  template <typename ValueType>
+  json::result<ValueType, enum status>
+  get_value_from_json_string (const json::string &json_str,
+			      const property_spec_ref &prop,
+			      const string_property_value<ValueType> *value_arr,
+			      size_t num_values);
+
+  /* The manager to replay the SARIF files to.  */
+  libdiagnostics::manager m_output_mgr;
+
+  /* The manager for reporting issues loading SARIF files.  */
+  libdiagnostics::manager m_control_mgr;
+
+  /* The file within m_control_mgr representing the .sarif file.  */
+  libdiagnostics::file m_loaded_file;
+
+  replayer_location_map m_json_location_map;
+
+  const json::object *m_driver_obj;
+  const json::value *m_artifacts_arr;
+};
+
+static const char *
+describe_kind (const json::value &val)
+{
+  switch (val.get_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case json::JSON_OBJECT:
+      return _("JSON object");
+
+    case json::JSON_ARRAY:
+      return _("JSON array");
+
+    case json::JSON_INTEGER:
+    case json::JSON_FLOAT:
+      return _("JSON number");
+
+    case json::JSON_STRING:
+      return _("JSON string");
+
+    case json::JSON_TRUE:
+    case json::JSON_FALSE:
+    case json::JSON_NULL:
+      return _("JSON literal");
+    }
+}
+
+/* class sarif_replayer.  */
+
+template <>
+void
+sarif_replayer::
+report_bad_property_kind<json::value> (const json::object &,
+				       const property_spec_ref &,
+				       const json::value &)
+{
+  gcc_unreachable ();
+}
+
+template <>
+void
+sarif_replayer::
+report_bad_property_kind<json::integer_number> (const json::object &,
+						const property_spec_ref &ref,
+						const json::value &propval)
+{
+  report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON integer; got %s",
+			ref.get_obj_name (), ref.get_property_name (),
+			describe_kind (propval));
+}
+
+template <>
+void
+sarif_replayer::
+report_bad_property_kind<json::string> (const json::object &,
+					const property_spec_ref &ref,
+					const json::value &propval)
+{
+  report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON string; got %s",
+			ref.get_obj_name (), ref.get_property_name (),
+			describe_kind (propval));
+}
+
+template <>
+void
+sarif_replayer::
+report_bad_property_kind<json::array> (const json::object &,
+				       const property_spec_ref &ref,
+				       const json::value &propval)
+{
+  report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON array; got %s",
+			ref.get_obj_name (), ref.get_property_name (),
+			describe_kind (propval));
+}
+
+template <>
+void
+sarif_replayer::
+report_bad_property_kind<json::object> (const json::object &,
+					const property_spec_ref &ref,
+					const json::value &propval)
+{
+  report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON object; got %s",
+			ref.get_obj_name (), ref.get_property_name (),
+			describe_kind (propval));
+}
+
+enum status
+sarif_replayer::replay_file (const char *filename,
+			     const replay_options &replay_opts)
+{
+  std::unique_ptr<std::vector<char>> buf = read_file (filename, m_control_mgr);
+  if (!buf)
+    return status::err_reading_file;
+
+  /* Use "sarif" as the sourceLanguage for SARIF itself; see
+     https://github.com/oasis-tcs/sarif-spec/issues/654 */
+  const char * const source_language = "sarif";
+  m_loaded_file = m_control_mgr.new_file (filename, source_language);
+
+  if (replay_opts.m_echo_file)
+    {
+      fprintf (stderr, "%s: (%li bytes)\n",
+	       filename, (long)buf->size ());
+      for (size_t i = 0; i < buf->size(); i++)
+	fputc ((*buf)[i], stderr);
+    }
+
+  json::parser_result_t result
+    (json::parse_utf8_string (buf->size (),
+			      (const char *)buf->data (),
+			      replay_opts.m_json_comments,
+			      &m_json_location_map));
+
+  if (auto json_err = result.m_err.get ())
+    {
+      gcc_assert (!result.m_val.get ());
+      auto file = m_control_mgr.new_file (filename, source_language);
+      auto loc_range = make_physical_location (m_control_mgr,
+					       file,
+					       json_err->get_range ());
+      auto err (m_control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+      err.set_location (loc_range);
+      err.finish ("%s", json_err->get_msg ());
+
+      return status::err_malformed_json;
+    }
+
+  gcc_assert (result.m_val.get ());
+  return emit_sarif_as_diagnostics (*result.m_val.get ());
+}
+
+#define PROP_sarifLog_version \
+  property_spec_ref ("sarifLog", "version", "3.13.2")
+
+#define PROP_sarifLog_runs \
+  property_spec_ref ("sarifLog", "runs", "3.13.4")
+
+enum status
+sarif_replayer::emit_sarif_as_diagnostics (const json::value &jv)
+{
+  /* We expect a sarifLog object as the top-level value
+     (SARIF v2.1.0 section 3.13).  */
+  const json::object *toplev_obj = dyn_cast <const json::object *> (&jv);
+  if (!toplev_obj)
+    return report_invalid_sarif
+      (jv, spec_ref ("3.1"),
+       "expected a sarifLog object as the top-level value");
+
+  /* sarifLog objects SHALL have a property named "version"
+     (SARIF v2.1.0 section 3.13.2) with a string value.  */
+  if (!get_required_property<json::string> (*toplev_obj,
+					    PROP_sarifLog_version))
+    return status::err_invalid_sarif;
+
+  /* sarifLog.runs must be null or be an array.  */
+  const property_spec_ref prop_runs (PROP_sarifLog_runs);
+  const json::value *runs
+    = get_required_property<json::value> (*toplev_obj, prop_runs);
+  if (!runs)
+    return status::err_invalid_sarif;
+
+  switch (runs->get_kind ())
+    {
+    default:
+      return report_invalid_sarif (*runs, prop_runs,
+				   "expected sarifLog.runs to be"
+				   " %<null%> or an array");
+
+    case json::JSON_NULL:
+      /* Nothing to do.  */
+      break;
+
+    case json::JSON_ARRAY:
+      {
+	const json::array &runs_arr = *as_a <const json::array *> (runs);
+	for (auto element : runs_arr)
+	  {
+	    const json::object *run_obj
+	      = require_object_for_element (*element, prop_runs);
+	    if (!run_obj)
+	      return status::err_invalid_sarif;
+	    enum status s = handle_run_obj (*run_obj);
+	    if (s != status::ok)
+	      return s;
+	  }
+      }
+      break;
+    }
+
+  return status::ok;
+}
+
+/* Process a run object (SARIF v2.1.0 section 3.14).  */
+
+enum status
+sarif_replayer::handle_run_obj (const json::object &run_obj)
+{
+  const json::object *tool_obj
+    = get_required_property<json::object> (run_obj,
+					   property_spec_ref ("run", "tool",
+							      "3.14.6"));
+  if (!tool_obj)
+    return status::err_invalid_sarif;
+  {
+    enum status err = handle_tool_obj (*tool_obj);
+    if (err != status::ok)
+      return err;
+  }
+
+  m_driver_obj
+    = get_required_property<json::object> (*tool_obj,
+					   property_spec_ref ("tool", "driver",
+							      "3.18.2"));
+  if (!m_driver_obj)
+    return status::err_invalid_sarif;
+
+#if 0
+  m_artifacts_arr = get_optional_property<json::array>
+    (run_obj, property_spec_ref ("run", "artifacts","3.14.15"));
+#endif
+
+  /* If present, run.results must be null or be an array.  */
+  const property_spec_ref prop_results ("run", "results", "3.14.23");
+  if (const json::value *results = get_optional_property (run_obj,
+							  prop_results))
+    switch (results->get_kind ())
+      {
+      default:
+	return report_invalid_sarif (*results, prop_results,
+				     "expected run.results to be"
+				     " %<null%> or an array");
+
+      case json::JSON_NULL:
+	/* Nothing to do.  */
+	break;
+      case json::JSON_ARRAY:
+	{
+	  const json::array *results_arr = as_a <const json::array *> (results);
+	  for (auto element : *results_arr)
+	    {
+	      const json::object *result_obj
+		= require_object_for_element (*element, prop_results);
+	      if (!result_obj)
+		return status::err_invalid_sarif;
+	      enum status s = handle_result_obj (*result_obj, *tool_obj);
+	      if (s != status::ok)
+		return s;
+	    }
+	}
+	break;
+      }
+
+  return status::ok;
+}
+
+/* Process a tool object (SARIF v2.1.0 section 3.18).  */
+
+enum status
+sarif_replayer::handle_tool_obj (const json::object &tool_obj)
+{
+  auto driver_obj
+    = get_required_property<json::object> (tool_obj,
+					   property_spec_ref ("tool", "driver",
+							      "3.18.2"));
+  if (!driver_obj)
+    return status::err_invalid_sarif;
+
+  const property_spec_ref name_prop ("toolComponent", "name", "3.19.8");
+  if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
+							    name_prop))
+    m_output_mgr.set_tool_name (name_jstr->get_string ());
+
+  const property_spec_ref full_name_prop
+    ("toolComponent", "fullName", "3.19.9");
+  if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
+							    full_name_prop))
+    m_output_mgr.set_full_name (name_jstr->get_string ());
+
+  const property_spec_ref version_prop ("toolComponent", "version", "3.19.13");
+  if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
+							    version_prop))
+    m_output_mgr.set_version_string (name_jstr->get_string ());
+
+  const property_spec_ref
+    info_uri_prop ("toolComponent", "informationUri", "3.19.17");
+  if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
+							    info_uri_prop))
+    m_output_mgr.set_version_url (name_jstr->get_string ());
+
+  return status::ok;
+}
+
+/* Compare the value of JSON_STR to the values in VALUE_ARR.
+   If found, return it in the result's m_val.
+   Otherwise, complain using PROP and return status::invalid_sarif
+   in results's m_err.  */
+
+template <typename ValueType>
+json::result<ValueType, enum status>
+sarif_replayer::
+get_value_from_json_string (const json::string &json_str,
+			    const property_spec_ref &prop,
+			    const string_property_value<ValueType> *value_arr,
+			    size_t num_values)
+{
+  const char *str = json_str.get_string ();
+  for (size_t i = 0; i < num_values; i++)
+    if (strcmp (str, value_arr[i].m_string) == 0)
+      return value_arr[i].m_value;
+  return report_invalid_sarif (json_str, prop,
+			       "unrecognized value for %qs: %qs",
+			       prop.get_property_name (),
+			       str);
+}
+
+const property_spec_ref prop_result_level ("result", "level", "3.27.10");
+
+/* Handle a value for result's "level" property (§3.27.10).
+   Limitation: doesn't yet support "none".  */
+json::result<enum diagnostic_level, enum status>
+sarif_replayer::get_level_from_level_str (const json::string &level_str)
+{
+  if (strcmp (level_str.get_string (), "none") == 0)
+    return report_unhandled_sarif (level_str, prop_result_level,
+				   "unable to handle value for %qs: %qs",
+				   prop_result_level.get_property_name (),
+				   level_str.get_string ());
+
+  const string_property_value<enum diagnostic_level> level_values[]
+    = { {"warning",
+	 DIAGNOSTIC_LEVEL_WARNING},
+	{"error",
+	 DIAGNOSTIC_LEVEL_ERROR},
+	{"note",
+	 DIAGNOSTIC_LEVEL_NOTE} };
+  return get_value_from_json_string<enum diagnostic_level>
+    (level_str,
+     prop_result_level,
+     level_values, ARRAY_SIZE (level_values));
+}
+
+/* Process a result object (SARIF v2.1.0 section 3.27).
+   Known limitations:
+   - doesn't yet handle "ruleIndex" property (§3.27.6)
+   - doesn't yet handle "taxa" property (§3.27.8)
+   - handling of "level" property (§3.27.10) doesn't yet support the
+     full logic for when "level" is absent.
+   - doesn't yet handle "relatedLocations" property (§3.27.22)
+   - doesn't yet handle "fixes" property (§3.27.30)
+   - doesn't yet support multithreaded flows (§3.36.3)
+*/
+
+#define PROP_result_ruleId \
+  property_spec_ref ("result", "ruleId", "3.27.5")
+
+#define PROP_result_message \
+  property_spec_ref ("result", "message", "3.27.11")
+
+enum status
+sarif_replayer::handle_result_obj (const json::object &result_obj,
+				   const json::object &tool_obj)
+{
+  const json::object *rule_obj = nullptr;
+  const json::object *tool_component_obj = nullptr;
+  const json::string *rule_id
+    = get_optional_property<json::string> (result_obj, PROP_result_ruleId);
+  if (rule_id)
+    {
+      rule_obj = lookup_rule_by_id_in_tool (rule_id->get_string (),
+					    tool_obj,
+					    tool_component_obj);
+      // TODO: error handling
+    }
+
+  enum diagnostic_level level = DIAGNOSTIC_LEVEL_WARNING;
+  if (auto level_str
+	= get_optional_property<json::string> (result_obj,
+					       prop_result_level))
+    {
+      auto result = get_level_from_level_str (*level_str);
+      if (result.m_err != status::ok)
+	return result.m_err;
+      level = result.m_val;
+    }
+
+  // §3.27.11 "message" property
+  label_text text;
+  if (auto message_obj
+	= get_optional_property<json::object> (result_obj, PROP_result_message))
+    text = make_plain_text_within_result_message (nullptr, // TODO: tool_component_obj,
+						  *message_obj,
+						  rule_obj);
+  if (!text.get ())
+    return status::err_invalid_sarif;
+
+  // §3.27.12 "locations" property
+  libdiagnostics::physical_location physical_loc;
+  libdiagnostics::logical_location logical_loc;
+  const property_spec_ref locations_prop ("result", "locations", "3.27.12");
+  const json::array *locations_arr
+    = get_required_property<json::array> (result_obj, locations_prop);
+  if (!locations_arr)
+    return status::err_invalid_sarif;
+  if (locations_arr->length () > 0)
+    {
+      /* Only look at the first, if there's more than one.  */
+      // location objects (§3.28)
+      const json::object *location_obj
+	= require_object_for_element (*locations_arr->get (0), locations_prop);
+      if (!location_obj)
+	return status::err_invalid_sarif;
+      enum status s = handle_location_object (*location_obj,
+					      physical_loc,
+					      logical_loc);
+      if (s != status::ok)
+	return s;
+    }
+
+  // §3.27.18 "codeFlows" property
+  libdiagnostics::execution_path path;
+  const property_spec_ref code_flows ("result", "codeFlows", "3.27.18");
+  if (auto code_flows_arr = get_optional_property<json::array> (result_obj,
+								code_flows))
+    {
+      // TODO: what if more than one?
+      if (code_flows_arr->length () == 1)
+	{
+	  const json::object *code_flow_obj
+	    = require_object_for_element (*code_flows_arr->get (0), code_flows);
+	  if (!code_flow_obj)
+	    return status::err_invalid_sarif;
+
+	  const property_spec_ref prop_thread_flows
+	    ("result", "threadFlows", "3.36.3");
+	  if (auto thread_flows_arr
+	      = get_optional_property<json::array> (*code_flow_obj,
+						    prop_thread_flows))
+	    {
+	      if (thread_flows_arr->length () == 1)
+		{
+		  const json::object *thread_flow_obj
+		    = require_object_for_element (*thread_flows_arr->get (0),
+						  prop_thread_flows);
+		  if (!thread_flow_obj)
+		    return status::err_invalid_sarif;
+		  handle_thread_flow_object (*thread_flow_obj, path);
+		}
+	    }
+	}
+    }
+
+  libdiagnostics::group g (m_output_mgr);
+  auto err (m_output_mgr.begin_diagnostic (level));
+  if (rule_id)
+    err.add_rule (rule_id->get_string (), nullptr);
+  err.set_location (physical_loc);
+  err.set_logical_location (logical_loc);
+  if (path.m_inner)
+    err.take_execution_path (std::move (path));
+  err.finish ("%s", text.get ());
+
+  // §3.27.22 relatedLocations property
+  const property_spec_ref prop_related_locations
+    ("result", "relatedLocations", "3.27.22");
+  if (auto related_locations_arr
+      = get_optional_property<json::array> (result_obj,
+					    prop_related_locations))
+    {
+      for (auto rel_loc : *related_locations_arr)
+	{
+	  libdiagnostics::physical_location physical_loc;
+	  libdiagnostics::logical_location logical_loc;
+	  const json::object *location_obj
+	    = require_object_for_element (*rel_loc,
+					  prop_related_locations);
+	  if (!location_obj)
+	    return status::err_invalid_sarif;
+	  enum status s = handle_location_object (*location_obj,
+						  physical_loc,
+						  logical_loc);
+	  if (s != status::ok)
+	    return s;
+
+	  // §3.28.5 message property
+	  const property_spec_ref prop_message
+	    ("location", "message", "3.28.5");
+	  if (auto message_obj
+	      = get_optional_property<json::object> (*location_obj,
+						     prop_message))
+	    {
+	      /* Treat related locations with a message as a "note".  */
+	      label_text text
+		(make_plain_text_within_result_message
+		 (tool_component_obj,
+		  *message_obj,
+		  rule_obj));
+	      if (!text.get ())
+		return status::err_invalid_sarif;
+	      auto note (m_output_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE));
+	      note.set_location (physical_loc);
+	      note.set_logical_location (logical_loc);
+	      note.finish ("%s", text.get ());
+	    }
+	}
+    }
+
+  return status::ok;
+
+}
+
+/*  If ITER_SRC starts with a placeholder as per §3.11.5, advance ITER_SRC
+    to immediately beyond the placeholder, write to *OUT_ARG_IDX, and
+    return true.
+
+    Otherwise, leave ITER_SRC untouched and return false.  */
+
+static bool
+maybe_consume_placeholder (const char *&iter_src, unsigned *out_arg_idx)
+{
+  if (*iter_src != '{')
+    return false;
+  const char *first_digit = iter_src + 1;
+  const char *iter_digit = first_digit;
+  while (char ch = *iter_digit)
+    switch (ch)
+      {
+      default:
+	return false;
+
+      case '}':
+	if (iter_digit == first_digit)
+	  {
+	    /* No digits, we simply have "{}" which is not a placeholder
+	       (and malformed: the braces should have been escaped).  */
+	    return false;
+	  }
+	*out_arg_idx = atoi (first_digit);
+	iter_src = iter_digit + 1;
+	return true;
+
+      case '0': case '1': case '2': case '3': case '4':
+      case '5': case '6': case '7': case '8': case '9':
+	/* TODO: what about multiple leading zeroes?  */
+	iter_digit++;
+	continue;
+    }
+  return false;
+}
+
+/* Lookup the plain text string within a result.message (§3.27.11),
+   and substitute for any placeholders (§3.11.5).
+
+   Limitations:
+   - we don't yet support embedded links
+
+   MESSAGE_OBJ is "theMessage"
+   RULE_OBJ is "theRule".  */
+
+label_text
+sarif_replayer::
+make_plain_text_within_result_message (const json::object *tool_component_obj,
+				       const json::object &message_obj,
+				       const json::object *rule_obj)
+{
+  const char *original_text
+    = lookup_plain_text_within_result_message (tool_component_obj,
+					       message_obj,
+					       rule_obj);
+  if (!original_text)
+    return label_text::borrow (nullptr);
+
+  /* Look up any arguments for substituting into placeholders.  */
+  const property_spec_ref arguments_prop ("message", "arguments", "3.11.11");
+  const json::array *arguments
+    = get_optional_property<json::array> (message_obj, arguments_prop);
+
+  /* Duplicate original_text, substituting any placeholders.  */
+  std::string accum;
+
+  const char *iter_src = original_text;
+  while (char ch = *iter_src)
+    {
+      unsigned arg_idx;
+      if (maybe_consume_placeholder (iter_src, &arg_idx))
+	{
+	  if (!arguments)
+	    {
+	      report_invalid_sarif
+		(message_obj, arguments_prop,
+		 "message string contains placeholder %<{%i}%>"
+		 " but message object has no %qs property",
+		 (int)arg_idx,
+		 arguments_prop.get_property_name ());
+	      return label_text::borrow (nullptr);
+	    }
+	  if (arg_idx >= arguments->length ())
+	    {
+	      report_invalid_sarif
+		(message_obj, arguments_prop,
+		 "not enough strings in %qs array for"
+		 " placeholder %<{%i}%>",
+		 arguments_prop.get_property_name (),
+		 (int)arg_idx);
+	      // TODO: might be nice to add a note showing the args
+	      return label_text::borrow (nullptr);
+	    }
+	  auto replacement_jstr
+	    = require_string (*arguments->get (arg_idx), arguments_prop);
+	  if (!replacement_jstr)
+	    return label_text::borrow (nullptr);
+	  accum += replacement_jstr->get_string ();
+	}
+      else if (ch == '{' || ch == '}')
+	{
+	  /* '{' and '}' are escaped by repeating them.  */
+	  if (iter_src[1] == ch)
+	    {
+	      accum += ch;
+	      iter_src += 2;
+	    }
+	  else
+	    {
+	      report_invalid_sarif (message_obj, arguments_prop,
+				    "unescaped '%c' within message string",
+				    ch);
+	      return label_text::borrow (nullptr);
+	    }
+	}
+      else
+	{
+	  accum += ch;
+	  iter_src++;
+	}
+    }
+
+  return label_text::take (xstrdup (accum.c_str ()));
+}
+
+/* Handle a value that should be a multiformatMessageString object (§3.12).
+   Complain using prop if MFMS_VAL is not an object.
+   Return get the "text" value (or nullptr, and complain).  */
+
+const char *
+sarif_replayer::get_plain_text_from_mfms (json::value &mfms_val,
+					  const property_spec_ref &prop)
+{
+  auto mfms_obj = require_object (mfms_val, prop);
+  if (!mfms_obj)
+    return nullptr;
+
+  const property_spec_ref text_prop
+    ("multiformatMessageString", "text", "3.12.3");
+  auto text_jstr = get_required_property<json::string> (*mfms_obj, text_prop);
+  if (!text_jstr)
+    return nullptr;
+  return text_jstr->get_string ();
+}
+
+#define PROP_message_text \
+  property_spec_ref ("message", "text", "3.11.8")
+
+#define PROP_message_id \
+  property_spec_ref ("message", "id", "3.11.10")
+
+/* Implement the message string lookup algorithm from
+   SARIF v2.1.0 section 3.11.7, for the case where theMessage
+   is the value of result.message (§3.27.11).
+
+   MESSAGE_OBJ is "theMessage"
+   RULE_OBJ is "theRule".  */
+
+const char *
+sarif_replayer::
+lookup_plain_text_within_result_message (const json::object *tool_component_obj,
+					 const json::object &message_obj,
+					 const json::object *rule_obj)
+{
+  // rule_obj can be NULL
+
+  /* IF theMessage.text is present and the desired language is theRun.language THEN
+       Use the text or markdown property of theMessage as appropriate.  */
+  if (const json::string *str
+      = get_optional_property<json::string> (message_obj, PROP_message_text))
+    // TODO: check language
+    return str->get_string ();
+
+  if (rule_obj)
+    if (auto message_id_jstr
+	= get_optional_property<json::string> (message_obj, PROP_message_id))
+      {
+	const char *message_id = message_id_jstr->get_string ();
+	const property_spec_ref message_strings
+	  ("reportingDescriptor", "messageStrings", "3.49.11");
+	if (auto message_strings_obj
+	    = get_optional_property<json::object> (*rule_obj,
+						   message_strings))
+	  if (json::value *mfms = message_strings_obj->get (message_id))
+	    return get_plain_text_from_mfms (*mfms, message_strings);
+
+	/* Look up by theMessage.id within theComponent.globalMessageStrings
+	   (§3.19.22).  */
+	if (tool_component_obj)
+	  {
+	    const property_spec_ref prop_gms
+	      ("toolComponent", "globalMessageStrings", "3.19.22");
+	    if (auto global_message_strings
+		= get_optional_property<json::object> (*tool_component_obj,
+						       prop_gms))
+	      if (auto mfms = global_message_strings->get (message_id))
+		return get_plain_text_from_mfms (*mfms, prop_gms);
+	  }
+      }
+
+  /* Failure.  */
+  report_invalid_sarif (message_obj, spec_ref ("3.11.7"),
+			"could not find string for %<message%> object");
+  return nullptr;
+}
+
+/* Populate OUT for THREAD_FLOW_OBJ, a
+   SARIF threadFlow object (section 3.37).  */
+
+enum status
+sarif_replayer::handle_thread_flow_object (const json::object &thread_flow_obj,
+					   libdiagnostics::execution_path &out)
+{
+  const property_spec_ref locations ("threadFlow", "locations", "3.37.6");
+  const json::array *locations_arr
+    = get_required_property<json::array> (thread_flow_obj, locations);
+  if (!locations_arr)
+    return status::err_invalid_sarif;
+
+  out = m_output_mgr.new_execution_path ();
+  for (auto location : *locations_arr)
+    {
+      /* threadFlowLocation object (§3.38).  */
+      const json::object *tflow_loc_obj
+	= require_object_for_element (*location, locations);
+      if (!tflow_loc_obj)
+	return status::err_invalid_sarif;
+      handle_thread_flow_location_object (*tflow_loc_obj, out);
+    }
+
+  return status::ok;
+}
+
+/* "threadFlowLocation" object (§3.38).
+   Attempt to add an event for TFLOW_LOC_OBJ to PATH.  */
+
+enum status
+sarif_replayer::
+handle_thread_flow_location_object (const json::object &tflow_loc_obj,
+				    libdiagnostics::execution_path &path)
+{
+  libdiagnostics::physical_location physical_loc;
+  libdiagnostics::logical_location logical_loc;
+  label_text message;
+  int stack_depth = 0;
+
+  const property_spec_ref location_prop
+    ("threadFlowLocation", "location", "3.38.3");
+  if (auto location_obj = get_optional_property<json::object> (tflow_loc_obj,
+							       location_prop))
+    {
+      /* location object (§3.28).  */
+      enum status s
+	= handle_location_object (*location_obj, physical_loc, logical_loc);
+      if (s != status::ok)
+	return s;
+
+      /* Get any message from here.  */
+      const property_spec_ref location_message
+	("location", "message", "3.28.5");
+      if (auto message_obj
+	  = get_optional_property<json::object> (*location_obj,
+						 location_message))
+	{
+	  message = make_plain_text_within_result_message
+	    (nullptr,
+	     *message_obj,
+	     nullptr/* TODO.  */);
+	}
+    }
+
+  // §3.38.8 "kinds" property
+  const property_spec_ref kinds ("threadFlowLocation", "kinds", "3.38.8");
+  if (auto kinds_arr
+	= get_optional_property<json::array> (tflow_loc_obj, kinds))
+    {
+      std::vector<const char *> kind_strs;
+      for (auto iter : *kinds_arr)
+	{
+	  const json::string *kind_str = dyn_cast <const json::string *> (iter);
+	  if (!kind_str)
+	    {
+	    }
+	  kind_strs.push_back (kind_str->get_string ());
+	  // TODO: handle meaning?
+	  /*  TOOD: probably just want to add sarif kinds to
+	      the libdiagnostics event, and have libdiagnostics
+	      turn that back into a "meaning".  */
+	}
+    }
+
+  /* nestingLevel property (§3.38.10).  */
+  const property_spec_ref nesting_level
+    ("threadFlowLocation", "nestingLevel", "3.38.10");
+  if (auto nesting_level_jv
+      = get_optional_property<json::integer_number> (tflow_loc_obj,
+						     nesting_level))
+    {
+      stack_depth = nesting_level_jv->get ();
+      if (stack_depth < 0)
+	{
+	  return report_invalid_sarif (tflow_loc_obj, nesting_level,
+				       "expected a non-negative integer");
+	}
+    }
+
+  if (message.get ())
+    path.add_event (physical_loc,
+		    logical_loc,
+		    stack_depth,
+		    "%s", message.get ());
+  else
+    path.add_event (physical_loc,
+		    logical_loc,
+		    stack_depth,
+		    "");
+
+  return status::ok;
+}
+
+/* Handle LOCATION_OBJ, a "location" (§3.28).  */
+
+enum status
+sarif_replayer::
+handle_location_object (const json::object &location_obj,
+			libdiagnostics::physical_location &out_physical_loc,
+			libdiagnostics::logical_location &out_logical_loc)
+{
+  // §3.28.3 "physicalLocation" property
+  {
+    const property_spec_ref physical_location_prop
+      ("location", "physicalLocation", "3.28.3");
+    if (const json::object *phys_loc_obj
+	= get_optional_property<json::object> (location_obj,
+					       physical_location_prop))
+      {
+	enum status s = handle_physical_location_object (*phys_loc_obj,
+							 out_physical_loc);
+	if (s!= status::ok)
+	  return s;
+      }
+  }
+
+  // §3.28.4 "logicalLocations" property
+  {
+    const property_spec_ref logical_locations_prop
+      ("location", "logicalLocations", "3.28.4");
+    if (const json::array *logical_loc_arr
+	= get_optional_property<json::array> (location_obj,
+					      logical_locations_prop))
+      if (logical_loc_arr->length () > 0)
+	{
+	  /* Only look at the first, if there's more than one.  */
+	  const json::object *logical_loc_obj
+	    = require_object_for_element (*logical_loc_arr->get (0),
+					  logical_locations_prop);
+	  if (!logical_loc_obj)
+	    return status::err_invalid_sarif;
+	  enum status s = handle_logical_location_object (*logical_loc_obj,
+							  out_logical_loc);
+	  if (s != status::ok)
+	    return s;
+	}
+  }
+
+  return status::ok;
+}
+
+/* Handle PHYS_LOC_OBJ, a "physicalLocation" object (§3.29).
+   Limitations:
+   - we don't yet support the "contextRegion" property (§3.29.5)  */
+
+enum status
+sarif_replayer::
+handle_physical_location_object (const json::object &phys_loc_obj,
+				 libdiagnostics::physical_location &out)
+{
+  libdiagnostics::file artifact_file;
+
+  // §3.29.3 "artifactLocation" property
+  const property_spec_ref artifact_location_prop
+    ("physicalLocation", "artifactLocation", "3.29.3");
+  if (const json::object *artifact_loc_obj
+	= get_optional_property<json::object> (phys_loc_obj,
+					       artifact_location_prop))
+    {
+      enum status s = handle_artifact_location_object (*artifact_loc_obj,
+						       artifact_file);
+      if (s != status::ok)
+	return s;
+    }
+
+  // §3.29.6 "address" property
+  const property_spec_ref artifact_address_prop
+    ("physicalLocation", "address", "3.29.6");
+
+  if (!artifact_file.m_inner)
+    {
+      const object_spec_ref constraints ("physicalLocation", "3.29.2");
+      return report_invalid_sarif_at_least_one_of (phys_loc_obj,
+						   constraints,
+						   artifact_address_prop,
+						   artifact_location_prop);
+    }
+
+  //3.29.4 region property
+  const property_spec_ref region_prop ("physicalLocation", "region", "3.29.4");
+  if (const json::object *region_obj
+      = get_optional_property<json::object> (phys_loc_obj, region_prop))
+    {
+      enum status s
+	= handle_region_object (*region_obj, artifact_file, out);
+      if (s != status::ok)
+	return s;
+      // TODO:
+    }
+
+  return status::ok;
+}
+
+/* Handle ARTIFACT_LOC, an "artifactLocation" object (§3.4).  */
+
+enum status
+sarif_replayer::handle_artifact_location_object (const json::object &artifact_loc,
+						 libdiagnostics::file &out)
+{
+  // §3.4.3 "uri" property
+  const property_spec_ref uri_prop ("artifactLocation", "uri", "3.4.3");
+  auto uri = get_optional_property<json::string> (artifact_loc, uri_prop);
+
+  // §3.4.5 "index" property
+  const property_spec_ref index_prop ("artifactLocation", "index", "3.4.5");
+  auto index = get_optional_property<json::integer_number> (artifact_loc,
+							    index_prop);
+  if (uri == nullptr && index == nullptr)
+    {
+      object_spec_ref constraints ("artifactLocation", "3.4.2");
+      return report_invalid_sarif_at_least_one_of (artifact_loc,
+						   constraints,
+						   uri_prop,
+						   index_prop);
+    }
+
+  if (uri)
+    {
+      // TODO: source language
+      out = m_output_mgr.new_file (uri->get_string (), nullptr);
+      return status::ok;
+    }
+
+  return status::ok;
+}
+
+/* Handle a "region" object (§3.30) within FILE, writing to OUT.  */
+
+enum status
+sarif_replayer::
+handle_region_object (const json::object &region_obj,
+		      libdiagnostics::file file,
+		      libdiagnostics::physical_location &out)
+{
+  gcc_assert (file.m_inner);
+
+  // §3.30.5 "startLine" property
+  const property_spec_ref start_line_prop ("region", "startLine", "3.30.5");
+  libdiagnostics::physical_location start;
+  libdiagnostics::physical_location end;
+  if (auto start_line_jnum
+      = get_optional_property<json::integer_number> (region_obj,
+						     start_line_prop))
+    {
+      /* Text region defined by line/column properties.  */
+      const property_spec_ref start_column_prop
+	("region", "startColumn", "3.30.6");
+      if (auto start_column_jnum
+	  = get_optional_property<json::integer_number> (region_obj,
+							 start_column_prop))
+	{
+	start = m_output_mgr.new_location_from_file_line_column
+	  (file, start_line_jnum->get (), start_column_jnum->get ());
+	}
+      else
+	start = m_output_mgr.new_location_from_file_and_line
+	  (file, start_line_jnum->get ());
+
+      int end_line = start_line_jnum->get ();
+      const property_spec_ref end_line_prop ("region", "endLine", "3.30.7");
+      if (auto end_line_jnum
+	  = get_optional_property<json::integer_number> (region_obj,
+							 end_line_prop))
+	end_line = end_line_jnum->get ();
+
+      const property_spec_ref end_column_prop ("region", "endColumn", "3.30.8");
+      if (auto end_column_jnum
+	  = get_optional_property<json::integer_number> (region_obj,
+							 end_column_prop))
+	{
+	  /* SARIF's endColumn is 1 beyond the final column in the region,
+	     whereas GCC's end columns are inclusive.  */
+	  end = m_output_mgr.new_location_from_file_line_column
+	    (file, end_line, end_column_jnum->get ());
+	}
+      else
+	{
+	  // missing "endColumn" means the whole of the rest of the row
+	  end = m_output_mgr.new_location_from_file_and_line
+	    (file, end_line);
+	}
+
+      out = m_output_mgr.new_location_from_range (start, start, end);
+    }
+
+  return status::ok;
+}
+
+/* Handle a "logicalLocation" object (§3.33), using it to populate OUT.
+   Known limitations:
+   - doesn't yet handle "parentIndex" property (§3.33.8)
+*/
+
+enum status
+sarif_replayer::
+handle_logical_location_object (const json::object &logical_loc_obj,
+				libdiagnostics::logical_location &out)
+{
+  const property_spec_ref name_prop ("logicalLocation", "name", "3.33.4");
+  const char *short_name = nullptr;
+  if (auto name_jstr = get_optional_property<json::string> (logical_loc_obj,
+							    name_prop))
+    short_name = name_jstr->get_string ();
+
+  const property_spec_ref fqname_prop
+    ("logicalLocation", "fullyQualifiedName", "3.33.5");
+  const char *fully_qualified_name = nullptr;
+  if (auto fully_qualified_name_jstr
+      = get_optional_property<json::string> (logical_loc_obj, fqname_prop))
+    fully_qualified_name = fully_qualified_name_jstr->get_string ();
+
+  const property_spec_ref decorated_name_prop
+    ("logicalLocation", "decoratedName", "3.33.6");
+  const char *decorated_name = nullptr;
+  if (auto decorated_name_jstr
+	= get_optional_property<json::string> (logical_loc_obj,
+					       decorated_name_prop))
+    decorated_name = decorated_name_jstr->get_string ();
+
+  // §3.33.7 "kind" property
+  const property_spec_ref kind_prop ("logicalLocation", "kind", "3.33.7");
+  enum diagnostic_logical_location_kind_t kind
+    = DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION;
+  if (auto kind_str = get_optional_property<json::string> (logical_loc_obj,
+							   kind_prop))
+    {
+      const string_property_value<enum diagnostic_logical_location_kind_t>
+	kind_values[]
+	= {
+	    { "function",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION },
+	    { "member",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER },
+	    { "module",
+	     DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE },
+	    { "namespace",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE },
+	    { "type",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE },
+	    { "returnType",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE },
+	    { "parameter",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER },
+	    { "variable",
+	      DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE } };
+      auto result
+	= get_value_from_json_string<enum diagnostic_logical_location_kind_t>
+	    (*kind_str, kind_prop, kind_values, ARRAY_SIZE (kind_values));
+      if (result.m_err != status::ok)
+	return result.m_err;
+      kind = result.m_val;
+    }
+
+  libdiagnostics::logical_location parent;
+  out = m_output_mgr.new_logical_location (kind,
+					   parent,
+					   short_name,
+					   fully_qualified_name,
+					   decorated_name);
+
+  return status::ok;
+}
+
+// 3.52.3 reportingDescriptor lookup
+// "For an example of the interaction between ruleId and rule.id, see §3.52.4."
+
+const json::object *
+sarif_replayer::
+lookup_rule_by_id_in_tool (const char *rule_id,
+			   const json::object &tool_obj,
+			   const json::object *&tool_component_obj)
+{
+  auto driver_obj
+    = get_required_property<json::object> (tool_obj,
+					   property_spec_ref ("tool", "driver",
+							      "3.18.2"));
+  if (!driver_obj)
+    return nullptr;
+
+  if (auto rule_obj = lookup_rule_by_id_in_component (rule_id, *driver_obj))
+    {
+      tool_component_obj = driver_obj;
+      return rule_obj;
+    }
+
+  // TODO: also handle extensions
+
+  return NULL;
+}
+
+const json::object *
+sarif_replayer::
+lookup_rule_by_id_in_component (const char *rule_id,
+				const json::object &tool_component_obj)
+{
+  const property_spec_ref rules ("toolComponent", "rules", "3.18.2");
+
+  auto rules_arr
+    = get_optional_property<json::array> (tool_component_obj, rules);
+  if (!rules_arr)
+    return nullptr;
+
+  for (auto element : *rules_arr)
+    {
+      const json::object *reporting_desc_obj
+	= require_object_for_element (*element, rules);
+
+      /* reportingDescriptor objects (§3.49).  */
+      const property_spec_ref id ("reportingDescriptor", "id", "3.49.3");
+      auto desc_id_jstr
+	= get_required_property<json::string> (*reporting_desc_obj, id);
+      if (!desc_id_jstr)
+	return nullptr;
+
+      if (!strcmp (rule_id, desc_id_jstr->get_string ()))
+	return reporting_desc_obj;
+    }
+
+  /* Not found.  */
+  return nullptr;
+}
+
+} // anonymous namespace
+
+/* Error-checking at the API boundary.  */
+
+#define FAIL_IF_NULL(PTR_ARG) \
+  do {						    \
+    GCC_DIAGNOSTIC_PUSH_IGNORED(-Wnonnull-compare)  \
+    if (!(PTR_ARG)) {				    \
+      fprintf (stderr, "%s: %s must be non-NULL\n", \
+	       __func__, #PTR_ARG);		    \
+      abort ();					    \
+    }						    \
+    GCC_DIAGNOSTIC_POP				    \
+  } while (0)
+
+/* Public entrypoint.  */
+
+int
+sarif_replay_path (const char *sarif_file,
+		   diagnostic_manager *output_manager,
+		   diagnostic_manager *control_manager,
+		   const replay_options *options)
+{
+  FAIL_IF_NULL (sarif_file);
+  FAIL_IF_NULL (output_manager);
+  FAIL_IF_NULL (control_manager);
+  FAIL_IF_NULL (options);
+
+  sarif_replayer r (libdiagnostics::manager (output_manager, false),
+		    libdiagnostics::manager (control_manager, false));
+  return (int)r.replay_file (sarif_file, *options);
+}
diff --git a/gcc/libsarifreplay.h b/gcc/libsarifreplay.h
new file mode 100644
index 000000000000..4c6b255166d9
--- /dev/null
+++ b/gcc/libsarifreplay.h
@@ -0,0 +1,59 @@ 
+/* A pure C API for replaying SARIF as diagnostics.
+   Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef LIBSARIFREPLAY_H
+#define LIBSARIFREPLAY_H
+
+#include "libdiagnostics.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct replay_options
+{
+  bool m_echo_file;
+  bool m_json_comments;
+  bool m_verbose;
+  enum diagnostic_colorize m_diagnostics_colorize;
+};
+
+/* Attempt to load a .sarif file from SARIF_FILE, and
+   replay the diagnostics to OUTPUT_MANAGER.
+   Report any problems to CONTROL_MANAGER (such as
+   file-not-found, malformed .sarif, etc).
+   If ALLOW_JSON_COMMENTS is true, then allow C/C++ style comments
+   in the file.
+   If ECHO_FILE, then dump the filename and contents to stderr.  */
+
+extern int
+sarif_replay_path (const char *sarif_file,
+		   diagnostic_manager *output_manager,
+		   diagnostic_manager *control_manager,
+		   const replay_options *options)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (4);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif  /* LIBSARIFREPLAY_H  */
diff --git a/gcc/sarif-replay.cc b/gcc/sarif-replay.cc
new file mode 100644
index 000000000000..e33457e1a67b
--- /dev/null
+++ b/gcc/sarif-replay.cc
@@ -0,0 +1,239 @@ 
+/* A program for re-emitting diagnostics saved in SARIF form.
+   Copyright (C) 2022-2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "version.h"
+#include "intl.h"
+#include "libdiagnostics++.h"
+#include "libsarifreplay.h"
+
+static const char *progname;
+
+static void
+set_defaults (replay_options &replay_opts)
+{
+  /* Defaults.  */
+  replay_opts.m_echo_file = false;
+  replay_opts.m_json_comments = false;
+  replay_opts.m_verbose = false;
+  replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_IF_TTY;
+}
+
+struct options
+{
+  options ()
+  {
+    set_defaults (m_replay_opts);
+  }
+
+  replay_options m_replay_opts;
+  std::vector<const char *> m_sarif_filenames;
+};
+
+static void
+print_version ()
+{
+  printf (_("%s %s%s\n"), progname, pkgversion_string,
+	  version_string);
+  printf ("Copyright %s 2024 Free Software Foundation, Inc.\n",
+	  _("(C)"));
+  fputs (_("This is free software; see the source for copying conditions.  There is NO\n\
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"),
+	 stdout);
+}
+
+static const char *const usage_msg = (
+"sarif-replay [OPTIONS] FILE+\n"
+"\n"
+"  \"Replay\" results from one or more .sarif files as if they were\n"
+"  GCC diagnostics\n"
+"\n"
+"Options:\n"
+"\n"
+"  -fdiagnostics-color={never|always|auto}\n"
+"     Control colorization of diagnostics.  Default: auto.\n"
+"\n"
+"  -fjson-comments\n"
+"     Support C and C++ style comments in .sarif JSON files\n"
+"\n"
+"  --verbose\n"
+"     Print notes about each .sarif file before and after replaying it.\n"
+"\n"
+"  --echo-file\n"
+"     Print the filename and file contents to stderr before replaying it.\n"
+"\n"
+"  -v, --version\n"
+"     Print version and exit.\n"
+"\n"
+"  --usage\n"
+"     Print this message and exit.\n"
+"\n");
+
+static void
+print_usage ()
+{
+  fprintf (stderr, usage_msg);
+}
+
+static bool
+parse_options (int argc, char **argv,
+	       options &opts,
+	       libdiagnostics::text_sink control_text_sink)
+{
+  libdiagnostics::manager options_mgr;
+  options_mgr.set_tool_name ("sarif-replay");
+  options_mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_NO/*IF_TTY*/);
+
+  for (int i = 1; i < argc; ++i)
+    {
+      const char *option = argv[i];
+      bool handled = false;
+      if (strcmp (option, "-fjson-comments") == 0)
+	{
+	  opts.m_replay_opts.m_json_comments = true;
+	  handled = true;
+	}
+      else if (strcmp (option, "--verbose") == 0)
+	{
+	  opts.m_replay_opts.m_verbose = true;
+	  handled = true;
+	}
+      else if (strcmp (option, "--usage") == 0)
+	{
+	  print_usage ();
+	  exit (0);
+	}
+      else if (strcmp (option, "--echo-file") == 0)
+	{
+	  opts.m_replay_opts.m_echo_file = true;
+	  handled = true;
+	}
+      else if (strcmp (option, "-fdiagnostics-color=never") == 0)
+	{
+	  opts.m_replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_NO;
+	  control_text_sink.set_colorize
+	    (opts.m_replay_opts.m_diagnostics_colorize);
+	  handled = true;
+	}
+      else if (strcmp (option, "-fdiagnostics-color=always") == 0)
+	{
+	  opts.m_replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_YES;
+	  control_text_sink.set_colorize
+	    (opts.m_replay_opts.m_diagnostics_colorize);
+	  handled = true;
+	}
+      else if (strcmp (option, "-fdiagnostics-color=auto") == 0)
+	{
+	  opts.m_replay_opts.m_diagnostics_colorize
+	    = DIAGNOSTIC_COLORIZE_IF_TTY;
+	  control_text_sink.set_colorize
+	    (opts.m_replay_opts.m_diagnostics_colorize);
+	  handled = true;
+	}
+      else if (strcmp (option, "-v") == 0
+	       || strcmp (option, "--version") == 0)
+	{
+	  print_version ();
+	  exit (0);
+	}
+
+      if (!handled)
+	{
+	  if (option[0] == '-')
+	    {
+	      auto err = options_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR);
+	      err.finish ("unrecognized option: %qs", option);
+	      return false;
+	    }
+	  else
+	    opts.m_sarif_filenames.push_back (option);
+	}
+    }
+
+  if (opts.m_sarif_filenames.size () == 0)
+    {
+      auto err = options_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR);
+      err.finish ("need at least one .sarif file to dump");
+      return false;
+    }
+  return true;
+}
+
+static const char *
+get_progname (const char *argv0)
+{
+  const char *p = argv0 + strlen (argv0);
+  while (p != argv0 && !IS_DIR_SEPARATOR (p[-1]))
+    --p;
+  return p;
+}
+
+/* Entrypoint to sarif-replay command-line tool.  */
+
+int
+main (int argc, char **argv)
+{
+  progname = get_progname (argv[0]);
+  xmalloc_set_program_name (progname);
+
+  libdiagnostics::manager control_mgr;
+
+  control_mgr.set_tool_name (progname);
+
+  libdiagnostics::text_sink control_text_sink
+    = control_mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+
+  options opts;
+  if (!parse_options (argc, argv, opts, control_text_sink))
+    {
+      print_usage ();
+      return -1;
+    }
+
+  int failures = 0;
+  for (auto filename : opts.m_sarif_filenames)
+    {
+      if (opts.m_replay_opts.m_verbose)
+	{
+	  auto note = control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE);
+	  note.finish ("about to replay %qs...", filename);
+	}
+      libdiagnostics::manager playback_mgr;
+      playback_mgr.add_text_sink (stderr,
+				  opts.m_replay_opts.m_diagnostics_colorize);
+
+      int result = sarif_replay_path (filename,
+				      playback_mgr.m_inner,
+				      control_mgr.m_inner,
+				      &opts.m_replay_opts);
+      if (result)
+	++failures;
+
+      if (opts.m_replay_opts.m_verbose)
+	{
+	  auto note = control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE);
+	  note.finish ("...finished replaying %qs", filename);
+	}
+    }
+  return failures;
+}
diff --git a/gcc/sarif-spec-urls.def b/gcc/sarif-spec-urls.def
new file mode 100644
index 000000000000..2ffa4a84aaa1
--- /dev/null
+++ b/gcc/sarif-spec-urls.def
@@ -0,0 +1,496 @@ 
+/* Generated by regenerate-sarif-spec-index.py.  */
+
+static const char * const sarif_spec_base_url
+  = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html";
+
+static const struct ref_anchor
+{
+  const char *m_ref;
+  const char *m_anchor;
+} sarif_spec_anchor_arr[] = {
+  { "1.1", "_Toc141790658" },
+  { "1.2", "_Toc141790659" },
+  { "1.3", "_Toc141790660" },
+  { "1.4", "_Toc141790661" },
+  { "1.5", "_Toc141790662" },
+  { "2.1", "_Toc141790664" },
+  { "2.2", "_Toc141790665" },
+  { "2.3", "_Toc141790666" },
+  { "2.4", "_Toc141790667" },
+  { "2.5", "_Toc141790668" },
+  { "3.1", "_Toc141790670" },
+  { "3.2", "_Toc141790671" },
+  { "3.3", "_Toc141790672" },
+  { "3.3.1", "_Toc141790673" },
+  { "3.3.2", "_Toc141790674" },
+  { "3.3.3", "_Toc141790675" },
+  { "3.3.4", "_Toc141790676" },
+  { "3.4", "_Toc141790677" },
+  { "3.4.1", "_Toc141790678" },
+  { "3.4.2", "_Toc141790679" },
+  { "3.4.3", "_Toc141790680" },
+  { "3.4.4", "_Toc141790681" },
+  { "3.4.5", "_Toc141790682" },
+  { "3.4.6", "_Toc141790683" },
+  { "3.5", "_Toc141790685" },
+  { "3.5.1", "_Toc141790686" },
+  { "3.5.2", "_Toc141790687" },
+  { "3.5.3", "_Toc141790688" },
+  { "3.5.4", "_Toc141790689" },
+  { "3.5.4.1", "_Toc141790690" },
+  { "3.6", "_Toc141790692" },
+  { "3.7", "_Toc141790693" },
+  { "3.7.1", "_Toc141790694" },
+  { "3.7.2", "_Toc141790695" },
+  { "3.7.4", "_Toc141790697" },
+  { "3.8", "_Toc141790698" },
+  { "3.8.1", "_Toc141790699" },
+  { "3.8.2", "_Toc141790700" },
+  { "3.8.2.1", "_Toc141790701" },
+  { "3.8.2.2", "_Toc141790702" },
+  { "3.9", "_Toc141790703" },
+  { "3.10", "_Toc141790704" },
+  { "3.10.1", "_Toc141790705" },
+  { "3.10.2", "_Toc141790706" },
+  { "3.10.3", "_Toc141790707" },
+  { "3.11", "_Toc141790709" },
+  { "3.11.1", "_Toc141790710" },
+  { "3.11.2", "_Toc141790711" },
+  { "3.11.3", "_Toc141790712" },
+  { "3.11.4", "_Toc141790713" },
+  { "3.11.4.1", "_Toc141790714" },
+  { "3.11.4.2", "_Toc141790715" },
+  { "3.11.5", "_Toc141790716" },
+  { "3.11.6", "_Toc141790717" },
+  { "3.11.7", "_Toc141790718" },
+  { "3.11.8", "_Toc141790719" },
+  { "3.11.9", "_Toc141790720" },
+  { "3.11.10", "_Toc141790721" },
+  { "3.11.11", "_Toc141790722" },
+  { "3.12", "_Toc141790723" },
+  { "3.12.1", "_Toc141790724" },
+  { "3.12.3", "_Toc141790726" },
+  { "3.12.4", "_Toc141790727" },
+  { "3.13", "_Toc141790728" },
+  { "3.13.1", "_Toc141790729" },
+  { "3.13.2", "_Toc141790730" },
+  { "3.13.3", "_Toc141790731" },
+  { "3.13.4", "_Toc141790732" },
+  { "3.14", "_Toc141790734" },
+  { "3.14.1", "_Toc141790735" },
+  { "3.14.3", "_Toc141790737" },
+  { "3.14.4", "_Toc141790738" },
+  { "3.14.5", "_Toc141790739" },
+  { "3.14.6", "_Toc141790740" },
+  { "3.14.7", "_Toc141790741" },
+  { "3.14.8", "_Toc141790742" },
+  { "3.14.9", "_Toc141790743" },
+  { "3.14.10", "_Toc141790744" },
+  { "3.14.11", "_Toc141790745" },
+  { "3.14.12", "_Toc141790746" },
+  { "3.14.14", "_Toc141790748" },
+  { "3.14.15", "_Toc141790749" },
+  { "3.14.16", "_Toc141790750" },
+  { "3.14.17", "_Toc141790751" },
+  { "3.14.18", "_Toc141790752" },
+  { "3.14.19", "_Toc141790753" },
+  { "3.14.20", "_Toc141790754" },
+  { "3.14.21", "_Toc141790755" },
+  { "3.14.22", "_Toc141790756" },
+  { "3.14.23", "_Toc141790757" },
+  { "3.14.24", "_Toc141790758" },
+  { "3.14.26", "_Toc141790760" },
+  { "3.14.27", "_Toc141790761" },
+  { "3.14.28", "_Toc141790762" },
+  { "3.15.1", "_Toc141790764" },
+  { "3.15.2", "_Toc141790765" },
+  { "3.15.3", "_Toc141790766" },
+  { "3.16.1", "_Toc141790768" },
+  { "3.16.2", "_Toc141790769" },
+  { "3.16.3", "_Toc141790770" },
+  { "3.16.4", "_Toc141790771" },
+  { "3.16.5", "_Toc141790772" },
+  { "3.17", "_Toc141790773" },
+  { "3.17.1", "_Toc141790774" },
+  { "3.17.2", "_Toc141790775" },
+  { "3.17.3", "_Toc141790776" },
+  { "3.17.4", "_Toc141790777" },
+  { "3.17.5", "_Toc141790778" },
+  { "3.18", "_Toc141790779" },
+  { "3.18.1", "_Toc141790780" },
+  { "3.18.2", "_Toc141790781" },
+  { "3.18.3", "_Toc141790782" },
+  { "3.19", "_Toc141790783" },
+  { "3.19.1", "_Toc141790784" },
+  { "3.19.2", "_Toc141790785" },
+  { "3.19.3", "_Toc141790786" },
+  { "3.19.4", "_Toc141790787" },
+  { "3.19.5", "_Toc141790788" },
+  { "3.19.6", "_Toc141790789" },
+  { "3.19.7", "_Toc141790790" },
+  { "3.19.8", "_Toc141790791" },
+  { "3.19.9", "_Toc141790792" },
+  { "3.19.10", "_Toc141790793" },
+  { "3.19.11", "_Toc141790794" },
+  { "3.19.12", "_Toc141790795" },
+  { "3.19.13", "_Toc141790796" },
+  { "3.19.15", "_Toc141790798" },
+  { "3.19.16", "_Toc141790799" },
+  { "3.19.17", "_Toc141790800" },
+  { "3.19.18", "_Toc141790801" },
+  { "3.19.19", "_Toc141790802" },
+  { "3.19.20", "_Toc141790803" },
+  { "3.19.21", "_Toc141790804" },
+  { "3.19.22", "_Toc141790805" },
+  { "3.19.23", "_Toc141790806" },
+  { "3.19.24", "_Toc141790807" },
+  { "3.19.25", "_Toc141790808" },
+  { "3.19.26", "_Toc141790809" },
+  { "3.19.27", "_Toc141790810" },
+  { "3.19.28", "_Toc141790811" },
+  { "3.19.29", "_Toc141790812" },
+  { "3.19.30", "_Toc141790813" },
+  { "3.19.33", "_Toc141790816" },
+  { "3.20", "_Toc141790817" },
+  { "3.20.1", "_Toc141790818" },
+  { "3.20.2", "_Toc141790819" },
+  { "3.20.3", "_Toc141790820" },
+  { "3.20.4", "_Toc141790821" },
+  { "3.20.7", "_Toc141790824" },
+  { "3.20.8", "_Toc141790825" },
+  { "3.20.9", "_Toc141790826" },
+  { "3.20.10", "_Toc141790827" },
+  { "3.20.11", "_Toc141790828" },
+  { "3.20.12", "_Toc141790829" },
+  { "3.20.14", "_Toc141790831" },
+  { "3.20.15", "_Toc141790832" },
+  { "3.20.16", "_Toc141790833" },
+  { "3.20.17", "_Toc141790834" },
+  { "3.20.18", "_Toc141790835" },
+  { "3.20.19", "_Toc141790836" },
+  { "3.20.20", "_Toc141790837" },
+  { "3.21", "_Toc141790841" },
+  { "3.21.1", "_Toc141790842" },
+  { "3.21.2", "_Toc141790843" },
+  { "3.21.3", "_Toc141790844" },
+  { "3.21.4", "_Toc141790845" },
+  { "3.21.5", "_Toc141790846" },
+  { "3.22", "_Toc141790847" },
+  { "3.22.1", "_Toc141790848" },
+  { "3.22.2", "_Toc141790849" },
+  { "3.22.3", "_Toc141790850" },
+  { "3.22.4", "_Toc141790851" },
+  { "3.23", "_Toc141790852" },
+  { "3.23.1", "_Toc141790853" },
+  { "3.23.2", "_Toc141790854" },
+  { "3.23.3", "_Toc141790855" },
+  { "3.23.4", "_Toc141790856" },
+  { "3.23.5", "_Toc141790857" },
+  { "3.23.6", "_Toc141790858" },
+  { "3.23.7", "_Toc141790859" },
+  { "3.23.8", "_Toc141790860" },
+  { "3.24", "_Toc141790861" },
+  { "3.24.1", "_Toc141790862" },
+  { "3.24.2", "_Toc141790863" },
+  { "3.24.3", "_Toc141790864" },
+  { "3.24.4", "_Toc141790865" },
+  { "3.24.5", "_Toc141790866" },
+  { "3.24.6", "_Toc141790867" },
+  { "3.24.7", "_Toc141790868" },
+  { "3.24.8", "_Toc141790869" },
+  { "3.24.9", "_Toc141790870" },
+  { "3.24.10", "_Toc141790871" },
+  { "3.24.10.1", "_Toc141790872" },
+  { "3.24.11", "_Toc141790874" },
+  { "3.24.12", "_Toc141790875" },
+  { "3.24.13", "_Toc141790876" },
+  { "3.25", "_Toc141790877" },
+  { "3.25.1", "_Toc141790878" },
+  { "3.25.2", "_Toc141790879" },
+  { "3.26", "_Toc141790880" },
+  { "3.26.1", "_Toc141790881" },
+  { "3.26.2", "_Toc141790882" },
+  { "3.26.3", "_Toc141790883" },
+  { "3.26.4", "_Toc141790884" },
+  { "3.26.5", "_Toc141790885" },
+  { "3.26.6", "_Toc141790886" },
+  { "3.26.7", "_Toc141790887" },
+  { "3.27", "_Toc141790888" },
+  { "3.27.1", "_Toc141790889" },
+  { "3.27.3", "_Toc141790891" },
+  { "3.27.4", "_Toc141790892" },
+  { "3.27.5", "_Toc141790893" },
+  { "3.27.6", "_Toc141790894" },
+  { "3.27.7", "_Toc141790895" },
+  { "3.27.8", "_Toc141790896" },
+  { "3.27.9", "_Toc141790897" },
+  { "3.27.10", "_Toc141790898" },
+  { "3.27.11", "_Toc141790899" },
+  { "3.27.12", "_Toc141790900" },
+  { "3.27.13", "_Toc141790901" },
+  { "3.27.14", "_Toc141790902" },
+  { "3.27.15", "_Toc141790903" },
+  { "3.27.16", "_Toc141790904" },
+  { "3.27.17", "_Toc141790905" },
+  { "3.27.18", "_Toc141790906" },
+  { "3.27.19", "_Toc141790907" },
+  { "3.27.20", "_Toc141790908" },
+  { "3.27.21", "_Toc141790909" },
+  { "3.27.22", "_Toc141790910" },
+  { "3.27.23", "_Toc141790911" },
+  { "3.27.24", "_Toc141790912" },
+  { "3.27.25", "_Toc141790913" },
+  { "3.27.26", "_Toc141790914" },
+  { "3.27.27", "_Toc141790915" },
+  { "3.27.28", "_Toc141790916" },
+  { "3.27.29", "_Toc141790917" },
+  { "3.27.30", "_Toc141790918" },
+  { "3.27.31", "_Toc141790919" },
+  { "3.28", "_Toc141790920" },
+  { "3.28.1", "_Toc141790921" },
+  { "3.28.2", "_Toc141790922" },
+  { "3.28.3", "_Toc141790923" },
+  { "3.28.4", "_Toc141790924" },
+  { "3.28.5", "_Toc141790925" },
+  { "3.28.6", "_Toc141790926" },
+  { "3.28.7", "_Toc141790927" },
+  { "3.29", "_Toc141790928" },
+  { "3.29.1", "_Toc141790929" },
+  { "3.29.2", "_Toc141790930" },
+  { "3.29.3", "_Toc141790931" },
+  { "3.29.4", "_Toc141790932" },
+  { "3.29.5", "_Toc141790933" },
+  { "3.29.6", "_Toc141790934" },
+  { "3.30", "_Toc141790935" },
+  { "3.30.1", "_Toc141790936" },
+  { "3.30.2", "_Toc141790937" },
+  { "3.30.3", "_Toc141790938" },
+  { "3.30.5", "_Toc141790940" },
+  { "3.30.6", "_Toc141790941" },
+  { "3.30.7", "_Toc141790942" },
+  { "3.30.8", "_Toc141790943" },
+  { "3.30.9", "_Toc141790944" },
+  { "3.30.10", "_Toc141790945" },
+  { "3.30.11", "_Toc141790946" },
+  { "3.30.12", "_Toc141790947" },
+  { "3.30.13", "_Toc141790948" },
+  { "3.30.14", "_Toc141790949" },
+  { "3.30.15", "_Toc141790950" },
+  { "3.31", "_Toc141790951" },
+  { "3.31.1", "_Toc141790952" },
+  { "3.31.3", "_Toc141790954" },
+  { "3.32", "_Toc141790955" },
+  { "3.32.1", "_Toc141790956" },
+  { "3.32.2", "_Toc141790957" },
+  { "3.32.3", "_Toc141790958" },
+  { "3.32.4", "_Toc141790959" },
+  { "3.32.5", "_Toc141790960" },
+  { "3.32.6", "_Toc141790961" },
+  { "3.32.7", "_Toc141790962" },
+  { "3.32.8", "_Toc141790963" },
+  { "3.32.9", "_Toc141790964" },
+  { "3.32.10", "_Toc141790965" },
+  { "3.32.11", "_Toc141790966" },
+  { "3.32.12", "_Toc141790967" },
+  { "3.32.13", "_Toc141790968" },
+  { "3.33", "_Toc141790969" },
+  { "3.33.1", "_Toc141790970" },
+  { "3.33.2", "_Toc141790971" },
+  { "3.33.3", "_Toc141790972" },
+  { "3.33.4", "_Toc141790973" },
+  { "3.33.5", "_Toc141790974" },
+  { "3.33.6", "_Toc141790975" },
+  { "3.33.7", "_Toc141790976" },
+  { "3.33.8", "_Toc141790977" },
+  { "3.34", "_Toc141790978" },
+  { "3.34.1", "_Toc141790979" },
+  { "3.34.2", "_Toc141790980" },
+  { "3.34.3", "_Toc141790981" },
+  { "3.34.4", "_Toc141790982" },
+  { "3.35", "_Toc141790983" },
+  { "3.35.1", "_Toc141790984" },
+  { "3.35.2", "_Toc141790985" },
+  { "3.35.3", "_Toc141790986" },
+  { "3.35.4", "_Toc141790987" },
+  { "3.35.5", "_Toc141790988" },
+  { "3.35.6", "_Toc141790989" },
+  { "3.36", "_Toc141790990" },
+  { "3.36.1", "_Toc141790991" },
+  { "3.36.2", "_Toc141790992" },
+  { "3.36.3", "_Toc141790993" },
+  { "3.37", "_Toc141790994" },
+  { "3.37.1", "_Toc141790995" },
+  { "3.37.2", "_Toc141790996" },
+  { "3.37.3", "_Toc141790997" },
+  { "3.37.4", "_Toc141790998" },
+  { "3.37.5", "_Toc141790999" },
+  { "3.37.6", "_Toc141791000" },
+  { "3.38", "_Toc141791001" },
+  { "3.38.1", "_Toc141791002" },
+  { "3.38.2", "_Toc141791003" },
+  { "3.38.3", "_Toc141791004" },
+  { "3.38.4", "_Toc141791005" },
+  { "3.38.5", "_Toc141791006" },
+  { "3.38.6", "_Toc141791007" },
+  { "3.38.7", "_Toc141791008" },
+  { "3.38.8", "_Toc141791009" },
+  { "3.38.9", "_Toc141791010" },
+  { "3.38.10", "_Toc141791011" },
+  { "3.38.11", "_Toc141791012" },
+  { "3.38.12", "_Toc141791013" },
+  { "3.38.13", "_Toc141791014" },
+  { "3.38.14", "_Toc141791015" },
+  { "3.39", "_Toc141791016" },
+  { "3.39.1", "_Toc141791017" },
+  { "3.39.2", "_Toc141791018" },
+  { "3.39.3", "_Toc141791019" },
+  { "3.39.4", "_Toc141791020" },
+  { "3.40", "_Toc141791021" },
+  { "3.40.1", "_Toc141791022" },
+  { "3.40.2", "_Toc141791023" },
+  { "3.40.3", "_Toc141791024" },
+  { "3.40.4", "_Toc141791025" },
+  { "3.40.5", "_Toc141791026" },
+  { "3.41", "_Toc141791027" },
+  { "3.41.1", "_Toc141791028" },
+  { "3.41.2", "_Toc141791029" },
+  { "3.41.3", "_Toc141791030" },
+  { "3.41.4", "_Toc141791031" },
+  { "3.41.5", "_Toc141791032" },
+  { "3.42", "_Toc141791033" },
+  { "3.42.1", "_Toc141791034" },
+  { "3.42.2", "_Toc141791035" },
+  { "3.42.3", "_Toc141791036" },
+  { "3.42.4", "_Toc141791037" },
+  { "3.42.5", "_Toc141791038" },
+  { "3.42.6", "_Toc141791039" },
+  { "3.42.7", "_Toc141791040" },
+  { "3.42.8", "_Toc141791041" },
+  { "3.43", "_Toc141791042" },
+  { "3.43.1", "_Toc141791043" },
+  { "3.43.2", "_Toc141791044" },
+  { "3.43.3", "_Toc141791045" },
+  { "3.43.4", "_Toc141791046" },
+  { "3.43.5", "_Toc141791047" },
+  { "3.44", "_Toc141791048" },
+  { "3.44.1", "_Toc141791049" },
+  { "3.44.2", "_Toc141791050" },
+  { "3.44.3", "_Toc141791051" },
+  { "3.45", "_Toc141791052" },
+  { "3.45.1", "_Toc141791053" },
+  { "3.45.2", "_Toc141791054" },
+  { "3.45.3", "_Toc141791055" },
+  { "3.45.4", "_Toc141791056" },
+  { "3.45.5", "_Toc141791057" },
+  { "3.46", "_Toc141791058" },
+  { "3.46.1", "_Toc141791059" },
+  { "3.46.2", "_Toc141791060" },
+  { "3.46.3", "_Toc141791061" },
+  { "3.46.4", "_Toc141791062" },
+  { "3.46.5", "_Toc141791063" },
+  { "3.46.6", "_Toc141791064" },
+  { "3.46.7", "_Toc141791065" },
+  { "3.46.8", "_Toc141791066" },
+  { "3.46.9", "_Toc141791067" },
+  { "3.47", "_Toc141791068" },
+  { "3.47.1", "_Toc141791069" },
+  { "3.47.2", "_Toc141791070" },
+  { "3.47.3", "_Toc141791071" },
+  { "3.47.4", "_Toc141791072" },
+  { "3.47.5", "_Toc141791073" },
+  { "3.47.6", "_Toc141791074" },
+  { "3.47.7", "_Toc141791075" },
+  { "3.47.8", "_Toc141791076" },
+  { "3.47.9", "_Toc141791077" },
+  { "3.48", "_Toc141791078" },
+  { "3.48.1", "_Toc141791079" },
+  { "3.48.2", "_Toc141791080" },
+  { "3.48.3", "_Toc141791081" },
+  { "3.48.4", "_Toc141791082" },
+  { "3.48.5", "_Toc141791083" },
+  { "3.48.6", "_Toc141791084" },
+  { "3.48.7", "_Toc141791085" },
+  { "3.49", "_Toc141791086" },
+  { "3.49.1", "_Toc141791087" },
+  { "3.49.2", "_Toc141791088" },
+  { "3.49.3", "_Toc141791089" },
+  { "3.49.4", "_Toc141791090" },
+  { "3.49.5", "_Toc141791091" },
+  { "3.49.6", "_Toc141791092" },
+  { "3.49.7", "_Toc141791093" },
+  { "3.49.8", "_Toc141791094" },
+  { "3.49.9", "_Toc141791095" },
+  { "3.49.10", "_Toc141791096" },
+  { "3.49.11", "_Toc141791097" },
+  { "3.49.12", "_Toc141791098" },
+  { "3.49.13", "_Toc141791099" },
+  { "3.49.14", "_Toc141791100" },
+  { "3.49.15", "_Toc141791101" },
+  { "3.50", "_Toc141791102" },
+  { "3.50.1", "_Toc141791103" },
+  { "3.50.2", "_Toc141791104" },
+  { "3.50.3", "_Toc141791105" },
+  { "3.50.4", "_Toc141791106" },
+  { "3.50.5", "_Toc141791107" },
+  { "3.51", "_Toc141791108" },
+  { "3.51.1", "_Toc141791109" },
+  { "3.51.2", "_Toc141791110" },
+  { "3.51.3", "_Toc141791111" },
+  { "3.52.1", "_Toc141791113" },
+  { "3.52.2", "_Toc141791114" },
+  { "3.52.3", "_Toc141791115" },
+  { "3.52.4", "_Toc141791116" },
+  { "3.52.5", "_Toc141791117" },
+  { "3.52.6", "_Toc141791118" },
+  { "3.52.7", "_Toc141791119" },
+  { "3.53.1", "_Toc141791121" },
+  { "3.53.2", "_Toc141791122" },
+  { "3.53.3", "_Toc141791123" },
+  { "3.53.4", "_Toc141791124" },
+  { "3.54", "_Toc141791125" },
+  { "3.54.1", "_Toc141791126" },
+  { "3.54.2", "_Toc141791127" },
+  { "3.54.3", "_Toc141791128" },
+  { "3.54.4", "_Toc141791129" },
+  { "3.54.5", "_Toc141791130" },
+  { "3.55", "_Toc141791131" },
+  { "3.55.1", "_Toc141791132" },
+  { "3.55.2", "_Toc141791133" },
+  { "3.55.3", "_Toc141791134" },
+  { "3.56", "_Toc141791135" },
+  { "3.56.1", "_Toc141791136" },
+  { "3.56.2", "_Toc141791137" },
+  { "3.56.3", "_Toc141791138" },
+  { "3.57", "_Toc141791139" },
+  { "3.57.1", "_Toc141791140" },
+  { "3.57.2", "_Toc141791141" },
+  { "3.57.3", "_Toc141791142" },
+  { "3.57.4", "_Toc141791143" },
+  { "3.58", "_Toc141791144" },
+  { "3.58.1", "_Toc141791145" },
+  { "3.58.2", "_Toc141791146" },
+  { "3.58.3", "_Toc141791147" },
+  { "3.58.4", "_Toc141791148" },
+  { "3.58.5", "_Toc141791149" },
+  { "3.58.6", "_Toc141791150" },
+  { "3.58.7", "_Toc141791151" },
+  { "3.58.8", "_Toc141791152" },
+  { "3.58.9", "_Toc141791153" },
+  { "3.59", "_Toc141791154" },
+  { "3.59.1", "_Toc141791155" },
+  { "3.59.2", "_Toc141791156" },
+  { "3.59.3", "_Toc141791157" },
+  { "3.59.4", "_Toc141791158" },
+  { "3.59.5", "_Toc141791159" },
+  { "4.1", "_Toc141791161" },
+  { "4.3", "_Toc141791163" },
+  { "4.3.1", "_Toc141791164" },
+  { "4.3.2", "_Toc141791165" },
+  { "4.3.3", "_Toc141791166" },
+  { "4.3.4", "_Toc141791167" },
+  { "4.3.5", "_Toc141791168" },
+  { "4.3.6", "_Toc141791169" },
+  { "5.1", "_Toc141791171" },
+  { "5.5", "_Toc141791175" },
+  { "5.8", "_Toc141791178" },
+};
\ No newline at end of file
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 33b7e5a1e2c2..25dbaad1cbbc 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -273,6 +273,10 @@  proc gcc-dg-test-1 { target_compile prog do_what extra_tool_flags } {
 	    # created or not.  If it was, dg.exp will try to run it.
 	    catch { remote_file build delete $output_file }
 	}
+	"replay-sarif" {
+	    set compile_type "none"
+	    set output_file ""
+	}
 	default {
 	    perror "$do_what: not a valid dg-do keyword"
 	    return ""
diff --git a/gcc/testsuite/lib/sarif-replay-dg.exp b/gcc/testsuite/lib/sarif-replay-dg.exp
new file mode 100644
index 000000000000..55084005c637
--- /dev/null
+++ b/gcc/testsuite/lib/sarif-replay-dg.exp
@@ -0,0 +1,90 @@ 
+# Copyright (C) 2012-2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+load_lib gcc-dg.exp
+
+# Define sarif-replay callbacks for dg.exp.
+
+proc sarif-replay-dg-test { prog do_what extra_tool_flags } {
+    upvar dg-do-what dg-do-what
+
+    set result \
+	[gcc-dg-test-1 sarif-replay_target_compile $prog "replay-sarif" $extra_tool_flags]
+
+    set comp_output [lindex $result 0]
+    set output_file [lindex $result 1]
+
+    return [list $comp_output $output_file]
+}
+
+proc sarif-replay-dg-prune { system text } {
+    return [gcc-dg-prune $system $text]
+}
+
+# Utility routines.
+
+#
+# Modified dg-runtest that can cycle through a list of optimization options
+# as c-torture does.
+#
+
+proc sarif-replay-dg-runtest { testcases flags default-extra-flags } {
+    global runtests
+
+    foreach test $testcases {
+	# If we're only testing specific files and this isn't one of
+	# them, skip it.
+	if ![runtest_file_p $runtests $test] {
+	    continue
+	}
+
+	# Use TORTURE_OPTIONS to cycle through an option list.
+	if [torture-options-exist] then {
+	    global torture_with_loops
+	    set option_list $torture_with_loops
+	} else {
+	    set option_list { "" }
+	}
+
+	set nshort [file tail [file dirname $test]]/[file tail $test]
+
+	foreach flags_t $option_list {
+	    verbose "Testing $nshort, $flags $flags_t" 1
+	    dg-test $test "$flags $flags_t" ${default-extra-flags}
+	}
+    }
+}
+
+#
+# sarif-replay_load -- wrapper around default sarif-replay_load to handle tests that
+# require program arguments passed to them.
+#
+
+if { [info procs sarif-replay_load] != [list] \
+      && [info procs prev_sarif-replay_load] == [list] } {
+    rename sarif-replay_load prev_sarif-replay_load
+
+    proc sarif-replay_load { program args } {
+	global SARIF_REPLAY_EXECUTE_ARGS
+	if [info exists SARIF_REPLAY_EXECUTE_ARGS] then {
+	    set args [concat "{$SARIF_REPLAY_EXECUTE_ARGS}"]
+	}
+	#print "Running: $program [lindex $args 0]"
+	set result [eval [list prev_sarif-replay_load $program] $args ]
+	return $result
+    }
+}
+
diff --git a/gcc/testsuite/lib/sarif-replay.exp b/gcc/testsuite/lib/sarif-replay.exp
new file mode 100644
index 000000000000..e757ad8ecfc3
--- /dev/null
+++ b/gcc/testsuite/lib/sarif-replay.exp
@@ -0,0 +1,204 @@ 
+# Copyright (C) 2012-2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+#
+# sarif-replay support library routines
+#
+
+load_lib prune.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+
+#
+# SARIF_REPLAY_UNDER_TEST is the compiler under test.
+#
+
+set sarif-replay_compile_options ""
+
+
+#
+# sarif-replay_version -- extract and print the version number of the compiler
+#
+
+proc sarif-replay_version { } {
+    global SARIF_REPLAY_UNDER_TEST
+
+    sarif-replay_init
+
+    # ignore any arguments after the command
+    set compiler [lindex $SARIF_REPLAY_UNDER_TEST 0]
+
+    # verify that the compiler exists
+    if { [is_remote host] || [which $compiler] != 0 } then {
+	set tmp [remote_exec host "$compiler -v"]
+	set status [lindex $tmp 0]
+	set output [lindex $tmp 1]
+	regexp " version \[^\n\r\]*" $output version
+	if { $status == 0 && [info exists version] } then {
+	    if [is_remote host] {
+		clone_output "$compiler $version\n"
+	    } else {
+		clone_output "[which $compiler] $version\n"
+	    }
+	} else {
+	    clone_output "Couldn't determine version of [which $compiler]\n"
+	}
+    } else {
+	# compiler does not exist (this should have already been detected)
+	warning "$compiler does not exist"
+    }
+}
+
+#
+# sarif-replay_include_flags -- include flags for the gcc tree structure
+#
+
+proc sarif-replay_include_flags { paths } {
+    global srcdir
+    global SARIF-REPLAY_INCLUDE_CXX_FLAGS
+    global TESTING_IN_BUILD_TREE
+
+    set flags ""
+
+    if { [is_remote host] || ![info exists TESTING_IN_BUILD_TREE] } {
+	return "${flags}"
+    }
+
+    if [info exists SARIF-REPLAY_INCLUDE_CXX_FLAGS] {
+	set include_cxx_flags $SARIF-REPLAY_INCLUDE_CXX_FLAGS
+    } else {
+	set include_cxx_flags 0
+    }
+
+    set gccpath ${paths}
+
+    if { $gccpath != "" } {
+	if [file exists "${gccpath}/libphobos/libdruntime"] {
+	    append flags "-I${gccpath}/libphobos/libdruntime "
+	}
+    }
+    append flags "-I${srcdir}/../../libphobos/libdruntime "
+    append flags "-I${srcdir}/../../libphobos/src "
+
+    # For the tests that mix C++ and D, need to know where headers are located.
+    if $include_cxx_flags {
+	set odir [lookfor_file ${gccpath} libstdc++-v3]
+	if { ${odir} != "" && [file exists ${odir}/scripts/testsuite_flags] } {
+	    set cxxflags [exec sh ${odir}/scripts/testsuite_flags --build-includes]
+	    set idx [lsearch $cxxflags "-nostdinc++"]
+	    append flags [lreplace $cxxflags $idx $idx]
+	}
+    }
+
+    return "$flags"
+}
+
+#
+# sarif-replay_init -- called at the start of each subdir of tests
+#
+
+proc sarif-replay_init { args } {
+    global sarif-replay_initialized
+    global base_dir
+    global tmpdir
+    global libdir
+    global gluefile wrap_flags
+    global TOOL_EXECUTABLE
+    global SARIF_REPLAY_UNDER_TEST
+    global TESTING_IN_BUILD_TREE
+    global gcc_warning_prefix
+    global gcc_error_prefix
+
+    # We set LC_ALL and LANG to C so that we get the same error messages as expected.
+    setenv LC_ALL C
+    setenv LANG C
+
+    if ![info exists SARIF_REPLAY_UNDER_TEST] then {
+	if [info exists TOOL_EXECUTABLE] {
+	    set SARIF_REPLAY_UNDER_TEST $TOOL_EXECUTABLE
+	} else {
+	    if { [is_remote host] || ! [info exists TESTING_IN_BUILD_TREE] } {
+		set SARIF_REPLAY_UNDER_TEST [transform sarif-replay]
+	    } else {
+		set SARIF_REPLAY_UNDER_TEST [findfile $base_dir/../../sarif-replay "$base_dir/../../sarif-replay" [findfile $base_dir/sarif-replay "$base_dir/sarif-replay" [transform sarif-replay]]]
+	    }
+	}
+    }
+
+    verbose "SARIF_REPLAY_UNDER_TEST: $SARIF_REPLAY_UNDER_TEST" 2
+
+    if ![is_remote host] {
+	if { [which $SARIF_REPLAY_UNDER_TEST] == 0 } then {
+	    perror "SARIF_REPLAY_UNDER_TEST ($SARIF_REPLAY_UNDER_TEST) does not exist"
+	    exit 1
+	}
+    }
+    if ![info exists tmpdir] {
+	set tmpdir "/tmp"
+    }
+
+    if [info exists gluefile] {
+	unset gluefile
+    }
+
+    set gcc_warning_prefix "warning:"
+    set gcc_error_prefix "(fatal )?error:"
+
+    verbose "sarif-replay is initialized" 3
+}
+
+#
+# sarif-replay_target_compile -- compile a source file
+#
+
+proc sarif-replay_target_compile { source dest type options } {
+    global tmpdir
+    global gluefile wrap_flags
+    global SARIF_REPLAY_UNDER_TEST
+    global TOOL_OPTIONS
+    global TEST_ALWAYS_FLAGS
+
+    if { [target_info needs_status_wrapper] != "" && [info exists gluefile] } {
+	lappend options "libs=${gluefile}"
+	lappend options "ldflags=${wrap_flags}"
+    }
+
+    set always_sarif_replay_flags ""
+
+    # FIXME: strip out -fdiagnostics-plain-output from TEST_ALWAYS_FLAGS
+    # rather than hacking this out:
+    #
+    # TEST_ALWAYS_FLAGS are flags that should be passed to every
+    # compilation.  They are passed first to allow individual
+    # tests to override them.
+    ##  if [info exists TEST_ALWAYS_FLAGS] {
+    ##	lappend always_sarif_replay_flags "additional_flags=$TEST_ALWAYS_FLAGS"
+    ##  }
+
+    if [info exists TOOL_OPTIONS] {
+	lappend always_sarif_replay_flags "additional_flags=$TOOL_OPTIONS"
+    }
+
+    verbose "always_sarif_replay_flags set to: $always_sarif_replay_flags"
+
+    lappend options "timeout=[timeout_value]"
+    lappend options "compiler=$SARIF_REPLAY_UNDER_TEST"
+
+    set options [concat "$always_sarif_replay_flags" $options]
+    set options [dg-additional-files-options $options $source $dest $type]
+    return [target_compile $source $dest $type $options]
+}
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
new file mode 100644
index 000000000000..4743bad3ba3c
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
@@ -0,0 +1,6 @@ 
+[ null ] // { dg-error "expected a sarifLog object as the top-level value \\\[SARIF v2.1.0 §3.1\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    1 | [ null ]
+      | ^~~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif
new file mode 100644
index 000000000000..72da185de0dc
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-malformed-placeholder.sarif
@@ -0,0 +1,15 @@ 
+{
+  "version": "2.1.0",
+  "runs": [{
+    "tool": { "driver": { "name": "example" } },
+    "results": [
+      { "message": { "text" : "before {} after" }, /* { dg-error "unescaped '\\\{' within message string \\\[SARIF v2.1.0 §3.11.11\\\]" } */
+      	"locations": [] }
+    ]
+  }]
+}
+
+/* { dg-begin-multiline-output "" }
+    6 |       { "message": { "text" : "before {} after" },
+      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output ""  }  */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
new file mode 100644
index 000000000000..c4354d4ef23d
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
@@ -0,0 +1,14 @@ 
+{
+  "version": "2.1.0",
+  "runs": [{
+    "tool": { "driver": { "name": "example" } },
+    "results": [
+      { "message": { "text" : "the {0} {1} fox jumps over the {2} dog" } } /* { dg-error "message string contains placeholder '\\{0\\}' but message object has no 'arguments' property \\\[SARIF v2.1.0 §3.11.11\\\]" } */
+    ]
+  }]
+}
+
+/* { dg-begin-multiline-output "" }
+    6 |       { "message": { "text" : "the {0} {1} fox jumps over the {2} dog" } }
+      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" }  */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
new file mode 100644
index 000000000000..e3eb53411101
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
@@ -0,0 +1,14 @@ 
+{
+  "version": "2.1.0",
+  "runs": [{
+    "tool": { "driver": { "name": "example" } },
+    "results": [
+      { "message": { "text" : "the {0} {1} fox jumps over the {2} dog", "arguments": ["quick", "brown"] } } /* { dg-error "not enough strings in 'arguments' array for placeholder '\\{2\\}' \\\[SARIF v2.1.0 §3.11.11\\\]" } */
+    ]
+  }]
+}
+
+/* { dg-begin-multiline-output "" }
+    6 |       { "message": { "text" : "the {0} {1} fox jumps over the {2} dog", "arguments": ["quick", "brown"] } }
+      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" }  */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
new file mode 100644
index 000000000000..771bd9c0c05d
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
@@ -0,0 +1,6 @@ 
+{ } // { dg-error "expected sarifLog object to have a 'version' property \\\[SARIF v2.1.0 §3.13.2\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    1 | { }
+      | ^~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
new file mode 100644
index 000000000000..87bfd0de7898
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
@@ -0,0 +1,6 @@ 
+{ "version" : 42 } // { dg-error "15: expected sarifLog.version to be a JSON string; got JSON number \\\[SARIF v2.1.0 §3.13.2\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    1 | { "version" : 42 }
+      |               ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
new file mode 100644
index 000000000000..c5a26f055164
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
@@ -0,0 +1,7 @@ 
+{ "version": "2.1.0",
+  "runs": 42 } // { dg-error "expected sarifLog.runs to be 'null' or an array \\\[SARIF v2.1.0 §3.13.4\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    2 |   "runs": 42 }
+      |           ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
new file mode 100644
index 000000000000..f142321642c3
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
@@ -0,0 +1,6 @@ 
+{ "version": "2.1.0" } // { dg-error "expected sarifLog object to have a 'runs' property \\\[SARIF v2.1.0 §3.13.4\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    1 | { "version": "2.1.0" }
+      | ^~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
new file mode 100644
index 000000000000..4eeaaaa7b242
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
@@ -0,0 +1,7 @@ 
+{ "version": "2.1.0",
+  "runs" : [42] } // { dg-error "expected element of sarifLog.runs array to be an object \\\[SARIF v2.1.0 §3.13.4\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    2 |   "runs" : [42] }
+      |             ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
new file mode 100644
index 000000000000..b5c3fbe55c37
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
@@ -0,0 +1,25 @@ 
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "foo"
+        }
+      },
+      "results": [
+        {
+	  "level": "mostly harmless", /* { dg-error "unrecognized value for 'level': 'mostly harmless' \\\[SARIF v2.1.0 §3.27.10\\\]" } */
+          "message": {
+            "text": "bar"
+          }
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+   12 |           "level": "mostly harmless",
+      |                    ^~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
new file mode 100644
index 000000000000..7f5867739ffb
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
@@ -0,0 +1,25 @@ 
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "foo"
+        }
+      },
+      "results": [
+        {
+	  "level": "none", /* { dg-message "sorry, unimplemented: unable to handle value for 'level': 'none' \\\[SARIF v2.1.0 §3.27.10\\\]" } */
+          "message": {
+            "text": "bar"
+          }
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+   12 |           "level": "none",
+      |                    ^~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif
new file mode 100644
index 000000000000..98df315cae4a
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/error-with-note.sarif
@@ -0,0 +1,34 @@ 
+{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "FooScanner",
+                               "rules": []}},
+           "artifacts": [{"location": {"uri": "/this/does/not/exist/test.bas"},
+                          "sourceLanguage": "basic",
+                          "contents": {"text": "label: PRINT \"hello world!\"\n       GOTO label\n"},
+                          "roles": ["analysisTarget"]}],
+           "results": [{"ruleId": "error",
+                        "level": "error",
+                        "message": {"text": "'GOTO' is considered harmful"},
+                        "locations": [{"physicalLocation": {"artifactLocation": {"uri": "/this/does/not/exist/test.bas"},
+                                                            "region": {"startLine": 2,
+                                                                       "startColumn": 8,
+                                                                       "endColumn": 19},
+                                                            "contextRegion": {"startLine": 2,
+                                                                              "snippet": {"text": "       GOTO label\n"}}}}],
+                        "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "/this/does/not/exist/test.bas"},
+                                                                   "region": {"startLine": 1,
+                                                                              "startColumn": 1,
+                                                                              "endColumn": 6},
+                                                                   "contextRegion": {"startLine": 1,
+                                                                                     "snippet": {"text": "PRINT \"hello world!\";\n"}}},
+                                              "message": {"text": "this is the target of the 'GOTO'"}}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+/this/does/not/exist/test.bas:2:8: error: 'GOTO' is considered harmful
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+/this/does/not/exist/test.bas:1:1: note: this is the target of the 'GOTO'
+   { dg-end-multiline-output "" } */
+
+// TODO: quote the source
+// TODO: trailing [error]
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif
new file mode 100644
index 000000000000..cbb90511e361
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/escaped-braces.sarif
@@ -0,0 +1,17 @@ 
+{
+  "version": "2.1.0",
+  "runs": [{
+    "tool": { "driver": { "name": "example" } },
+    "results": [
+      { "message": { "text" : "before open '{{' after open" },
+      	"locations": []},
+      { "message": { "text" : "before close '}}' after close" },
+      	"locations": []}
+    ]
+  }]
+}
+
+/* { dg-begin-multiline-output "" }
+example: warning: before open '{' after open
+example: warning: before close '}' after close
+   { dg-end-multiline-output "" }  */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif
new file mode 100644
index 000000000000..5fc630eecb35
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/null-runs.sarif
@@ -0,0 +1,2 @@ 
+{ "version": "2.1.0",
+  "runs": null }
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif
new file mode 100644
index 000000000000..6b76bc0bc594
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif
@@ -0,0 +1,193 @@ 
+/* Test a replay of a .sarif file generated from GCC testsuite.
+
+   The dg directives were stripped out from the generated .sarif
+   to avoid confusing DejaGnu for this test.   */
+
+{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C17",
+                               "fullName": "GNU C17 (GCC) version 15.0.0 20240709 (experimental) (x86_64-pc-linux-gnu)",
+                               "version": "15.0.0 20240709 (experimental)",
+                               "informationUri": "https://gcc.gnu.org/gcc-15/",
+                               "rules": [{"id": "-Wanalyzer-unsafe-call-within-signal-handler",
+                                          "helpUri": "https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html#index-Wanalyzer-unsafe-call-within-signal-handler"}]}},
+           "taxonomies": [{"name": "CWE",
+                           "version": "4.7",
+                           "organization": "MITRE",
+                           "shortDescription": {"text": "The MITRE Common Weakness Enumeration"},
+                           "taxa": [{"id": "479",
+                                     "helpUri": "https://cwe.mitre.org/data/definitions/479.html"}]}],
+           "invocations": [{"executionSuccessful": true,
+                            "toolExecutionNotifications": []}],
+           "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding/gcc-newgit-more-taint/build/gcc/"}},
+           "artifacts": [{"location": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                       "uriBaseId": "PWD"},
+                          "sourceLanguage": "c",
+                          "contents": {"text": "/* Example of a bad call within a signal handler.\n   'handler' calls 'custom_logger' which calls 'fprintf', and 'fprintf' is\n   not allowed from a signal handler.  */\n\n\n#include <stdio.h>\n#include <signal.h>\n\nextern void body_of_program(void);\n\nvoid custom_logger(const char *msg)\n{\n  fprintf(stderr, \"LOG: %s\", msg);\n}\n\nstatic void handler(int signum)\n{\n  custom_logger(\"got signal\");\n}\n\nint main(int argc, const char *argv)\n{\n  custom_logger(\"started\");\n\n  signal(SIGINT, handler);\n\n  body_of_program();\n\n  custom_logger(\"stopped\");\n\n  return 0;\n}\n"},
+                          "roles": ["analysisTarget",
+                                    "tracedFile"]}],
+           "results": [{"ruleId": "-Wanalyzer-unsafe-call-within-signal-handler",
+                        "taxa": [{"id": "479",
+                                  "toolComponent": {"name": "cwe"}}],
+                        "properties": {"gcc/analyzer/saved_diagnostic/sm": "signal",
+                                       "gcc/analyzer/saved_diagnostic/enode": 57,
+                                       "gcc/analyzer/saved_diagnostic/snode": 11,
+                                       "gcc/analyzer/saved_diagnostic/state": "in_signal_handler",
+                                       "gcc/analyzer/saved_diagnostic/idx": 0},
+                        "level": "warning",
+                        "message": {"text": "call to ‘fprintf’ from within signal handler"},
+                        "locations": [{"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                 "uriBaseId": "PWD"},
+                                                            "region": {"startLine": 13,
+                                                                       "startColumn": 3,
+                                                                       "endColumn": 34},
+                                                            "contextRegion": {"startLine": 13,
+                                                                              "snippet": {"text": "  fprintf(stderr, \"LOG: %s\", msg);\n"}}},
+                                       "logicalLocations": [{"name": "custom_logger",
+                                                             "fullyQualifiedName": "custom_logger",
+                                                             "decoratedName": "custom_logger",
+                                                             "kind": "function"}]}],
+                        "codeFlows": [{"threadFlows": [{"id": "main",
+                                                        "locations": [{"properties": {"gcc/analyzer/checker_event/emission_id": "(1)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 21,
+                                                                                                                    "startColumn": 5,
+                                                                                                                    "endColumn": 9},
+                                                                                                         "contextRegion": {"startLine": 21,
+                                                                                                                           "snippet": {"text": "int main(int argc, const char *argv)\n"}}},
+                                                                                    "logicalLocations": [{"name": "main",
+                                                                                                          "fullyQualifiedName": "main",
+                                                                                                          "decoratedName": "main",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "entry to ‘main’"}},
+                                                                       "kinds": ["enter",
+                                                                                 "function"],
+                                                                       "nestingLevel": 1,
+                                                                       "executionOrder": 1},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(2)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_STATE_CHANGE"},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 25,
+                                                                                                                    "startColumn": 3,
+                                                                                                                    "endColumn": 26},
+                                                                                                         "contextRegion": {"startLine": 25,
+                                                                                                                           "snippet": {"text": "  signal(SIGINT, handler);\n"}}},
+                                                                                    "logicalLocations": [{"name": "main",
+                                                                                                          "fullyQualifiedName": "main",
+                                                                                                          "decoratedName": "main",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "registering ‘handler’ as signal handler"}},
+                                                                       "nestingLevel": 1,
+                                                                       "executionOrder": 2},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(3)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_CUSTOM"},
+                                                                       "location": {"message": {"text": "later on, when the signal is delivered to the process"}},
+                                                                       "nestingLevel": 0,
+                                                                       "executionOrder": 3},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(4)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 16,
+                                                                                                                    "startColumn": 13,
+                                                                                                                    "endColumn": 20},
+                                                                                                         "contextRegion": {"startLine": 16,
+                                                                                                                           "snippet": {"text": "static void handler(int signum)\n"}}},
+                                                                                    "logicalLocations": [{"name": "handler",
+                                                                                                          "fullyQualifiedName": "handler",
+                                                                                                          "decoratedName": "handler",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "entry to ‘handler’"}},
+                                                                       "kinds": ["enter",
+                                                                                 "function"],
+                                                                       "nestingLevel": 1,
+                                                                       "executionOrder": 4},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(5)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_CALL_EDGE",
+                                                                                      "gcc/analyzer/superedge_event/superedge": {"kind": "SUPEREDGE_CALL",
+                                                                                                                                 "src_idx": 7,
+                                                                                                                                 "dst_idx": 10,
+                                                                                                                                 "desc": "call"}},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 18,
+                                                                                                                    "startColumn": 3,
+                                                                                                                    "endColumn": 30},
+                                                                                                         "contextRegion": {"startLine": 18,
+                                                                                                                           "snippet": {"text": "  custom_logger(\"got signal\");\n"}}},
+                                                                                    "logicalLocations": [{"name": "handler",
+                                                                                                          "fullyQualifiedName": "handler",
+                                                                                                          "decoratedName": "handler",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "calling ‘custom_logger’ from ‘handler’"}},
+                                                                       "kinds": ["call",
+                                                                                 "function"],
+                                                                       "nestingLevel": 1,
+                                                                       "executionOrder": 5},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(6)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_FUNCTION_ENTRY"},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 11,
+                                                                                                                    "startColumn": 6,
+                                                                                                                    "endColumn": 19},
+                                                                                                         "contextRegion": {"startLine": 11,
+                                                                                                                           "snippet": {"text": "void custom_logger(const char *msg)\n"}}},
+                                                                                    "logicalLocations": [{"name": "custom_logger",
+                                                                                                          "fullyQualifiedName": "custom_logger",
+                                                                                                          "decoratedName": "custom_logger",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "entry to ‘custom_logger’"}},
+                                                                       "kinds": ["enter",
+                                                                                 "function"],
+                                                                       "nestingLevel": 2,
+                                                                       "executionOrder": 6},
+                                                                      {"properties": {"gcc/analyzer/checker_event/emission_id": "(7)",
+                                                                                      "gcc/analyzer/checker_event/kind": "EK_WARNING"},
+                                                                       "location": {"physicalLocation": {"artifactLocation": {"uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                                                                                              "uriBaseId": "PWD"},
+                                                                                                         "region": {"startLine": 13,
+                                                                                                                    "startColumn": 3,
+                                                                                                                    "endColumn": 34},
+                                                                                                         "contextRegion": {"startLine": 13,
+                                                                                                                           "snippet": {"text": "  fprintf(stderr, \"LOG: %s\", msg);\n"}}},
+                                                                                    "logicalLocations": [{"name": "custom_logger",
+                                                                                                          "fullyQualifiedName": "custom_logger",
+                                                                                                          "decoratedName": "custom_logger",
+                                                                                                          "kind": "function"}],
+                                                                                    "message": {"text": "call to ‘fprintf’ from within signal handler"}},
+                                                                       "kinds": ["danger"],
+                                                                       "nestingLevel": 2,
+                                                                       "executionOrder": 7}]}]}]}]}]}
+
+// TODO: replay the source code
+// TODO: show the CWE
+/* { dg-begin-multiline-output "" }
+../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c:13:3: warning: call to ‘fprintf’ from within signal handler [-Wanalyzer-unsafe-call-within-signal-handler]
+  'main': event 1
+    |
+    |
+  'main': event 2
+    |
+    |
+  event 3
+    |
+    |GNU C17:
+    | (3): later on, when the signal is delivered to the process
+    |
+    +--> 'handler': event 4
+           |
+           |
+         'handler': event 5
+           |
+           |
+           +--> 'custom_logger': event 6
+                  |
+                  |
+                'custom_logger': event 7
+                  |
+                  |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif
new file mode 100644
index 000000000000..97f409f4aa45
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-1.sarif
@@ -0,0 +1,15 @@ 
+// Taken from SARIF v2.1.0, Appendix K.1: "Minimal valid SARIF log file"
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "CodeScanner"
+        }
+      },
+      "results": [
+      ]
+    }
+  ]
+}
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif
new file mode 100644
index 000000000000..dff53cd8c849
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-2.sarif
@@ -0,0 +1,73 @@ 
+/* Taken from SARIF v2.1.0, Appendix K.2: "Minimal recommended SARIF log
+   file with source information".  */
+
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "CodeScanner",
+          "rules": [
+            {
+              "id": "C2001",
+              "fullDescription": {
+                "text": "A variable was used without being initialized. This can result in runtime errors such as null reference exceptions."
+              },
+              "messageStrings": {
+                "default": {
+                  "text": "Variable \"{0}\" was used without being initialized."
+                }
+              }
+            }
+          ]
+        }
+      },
+      "artifacts": [
+        {
+          "location": {
+            "uri": "src/collections/list.cpp",
+            "uriBaseId": "SRCROOT"
+          },
+          "sourceLanguage": "c"
+        }
+      ],
+      "results": [
+        {
+          "ruleId": "C2001",
+          "ruleIndex": 0,
+          "message": {
+            "id": "default",
+            "arguments": [
+              "count"
+            ]
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "src/collections/list.cpp",
+                  "uriBaseId": "SRCROOT",
+                  "index": 0
+                },
+                "region": {
+                  "startLine": 15
+                }
+              },
+              "logicalLocations": [
+                {
+                  "fullyQualifiedName": "collections::list::add"
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+In function 'collections::list::add':
+src/collections/list.cpp:15: warning: Variable "count" was used without being initialized. [C2001]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif
new file mode 100644
index 000000000000..5e306528e403
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-3.sarif
@@ -0,0 +1,65 @@ 
+/* Taken from SARIF v2.1.0, Appendix K.3: "Minimal recommended SARIF log
+   file without source information".  */
+
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "BinaryScanner"
+        }
+      },
+      "artifact": [
+        {
+          "location": {
+            "uri": "bin/example",
+            "uriBaseId": "BINROOT"
+          }
+        }
+      ],
+      "logicalLocations": [
+        {
+          "name": "Example",
+          "kind": "namespace"
+        },
+        {
+          "name": "Worker",
+          "fullyQualifiedName": "Example.Worker",
+          "kind": "type",
+          "parentIndex": 0
+        },
+        {
+          "name": "DoWork",
+          "fullyQualifiedName": "Example.Worker.DoWork",
+          "kind": "function",
+          "parentIndex": 1
+        }
+      ],
+      "results": [
+        {
+          "ruleId": "B6412",
+          "message": {
+            "text": "The insecure method \"Crypto.Sha1.Encrypt\" should not be used."
+          },
+          "level": "warning",
+          "locations": [
+            {
+              "logicalLocations": [
+                {
+                  "fullyQualifiedName": "Example.Worker.DoWork",
+                  "index": 2
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+In function 'Example.Worker.DoWork':
+BinaryScanner: warning: The insecure method "Crypto.Sha1.Encrypt" should not be used. [B6412]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif
new file mode 100644
index 000000000000..84d3f887cefd
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/spec-example-4.sarif
@@ -0,0 +1,766 @@ 
+/* Taken from SARIF v2.1.0, Appendix K.4: "Comprehensive SARIF file".  */
+
+{
+  "version": "2.1.0",
+  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+  "runs": [
+    {
+      "automationId": {
+        "guid": "BC650830-A9FE-44CB-8818-AD6C387279A0",
+        "id": "Nightly code scan/2018-10-08"
+      },
+      "baselineGuid": "0A106451-C9B1-4309-A7EE-06988B95F723",
+      "runAggregates": [
+        {
+          "id": "Build/14.0.1.2/Release/20160716-13:22:18",
+          "correlationGuid": "26F138B6-6014-4D3D-B174-6E1ACE9439F3"
+        }
+      ],
+      "tool": {
+        "driver": {
+          "name": "CodeScanner",
+          "fullName": "CodeScanner 1.1 for Microsoft Windows (R) (en-US)",
+          "version": "2.1",
+          "semanticVersion": "2.1.0",
+          "dottedQuadFileVersion": "2.1.0.0",
+          "releaseDateUtc": "2019-03-17",
+          "organization": "Example Corporation",
+          "product": "Code Scanner",
+          "productSuite": "Code Quality Tools",
+          "shortDescription": {
+            "text": "A scanner for code."
+          },
+          "fullDescription": {
+            "text": "A really great scanner for all your code."
+          },
+          "properties": {
+            "copyright": "Copyright (c) 2017 by Example Corporation."
+          },
+          "globalMessageStrings": {
+            "variableDeclared": {
+              "text": "Variable \"{0}\" was declared here.",
+              "markdown": " Variable `{0}` was declared here."
+            }
+          },
+          "rules": [
+            {
+              "id": "C2001",
+              "deprecatedIds": [
+                "CA2000"
+              ],
+              "defaultConfiguration": {
+                "level": "error",
+                "rank": 95
+              },
+              "shortDescription": {
+                "text": "A variable was used without being initialized."
+              },
+              "fullDescription": {
+                "text": "A variable was used without being initialized. This can result in runtime errors such as null reference exceptions."
+              },
+              "messageStrings": {
+                "default": {
+                  "text": "Variable \"{0}\" was used without being initialized. It was declared [here]({1}).",
+                  "markdown": "Variable `{0}` was used without being initialized. It was declared [here]({1})."
+                }
+              }
+            }
+          ],
+          "notifications": [
+            {
+              "id": "start",
+              "shortDescription": {
+                "text": "The run started."
+              },
+              "messageStrings": {
+                "default": {
+                  "text": "Run started."
+                }
+              }
+            },
+            {
+              "id": "end",
+              "shortDescription": {
+                "text": "The run ended."
+              },
+              "messageStrings": {
+                "default": {
+                  "text": "Run ended."
+                }
+              }
+            }
+          ],
+          "language": "en-US"
+        },
+        "extensions": [
+          {
+            "name": "CodeScanner Security Rules",
+            "version": "3.1",
+            "rules": [
+              {
+                "id": "S0001",
+                "defaultConfiguration": {
+                  "level": "error"
+                },
+                "shortDescription": {
+                  "text": "Do not use weak cryptographic algorithms."
+                },
+                "messageStrings": {
+                  "default": {
+                    "text": "The cryptographic algorithm '{0}' should not be used."
+                  }
+                }
+              }
+            ]
+          }
+        ]
+      },
+      "language": "en-US",
+      "versionControlProvenance": [
+        {
+          "repositoryUri": "https://github.com/example-corp/browser",
+          "revisionId": "5da53fbb2a0aaa12d648b73984acc9aac2e11c2a",
+          "mappedTo": {
+            "uriBaseId": "PROJECTROOT"
+          }
+        }
+      ],
+      "originalUriBaseIds": {
+        "PROJECTROOT": {
+          "uri": "file://build.example.com/work/"
+        },
+        "SRCROOT": {
+          "uri": " src/",
+          "uriBaseId": "PROJECTROOT"
+        },
+        "BINROOT": {
+          "uri": " bin/",
+          "uriBaseId": "PROJECTROOT"
+        }
+      },
+      "invocations": [
+        {
+          "commandLine": "CodeScanner @build/collections.rsp",
+          "responseFiles": [
+            {
+              "uri": "build/collections.rsp",
+              "uriBaseId": "SRCROOT",
+              "index": 0
+            }
+          ],
+          "startTimeUtc": "2016-07-16T14:18:25Z",
+          "endTimeUtc": "2016-07-16T14:19:01Z",
+          "machine": "BLD01",
+          "account": "buildAgent",
+          "processId": 1218,
+          "fileName": "/bin/tools/CodeScanner",
+          "workingDirectory": {
+            "uri": "file:///home/buildAgent/src"
+          },
+          "environmentVariables": {
+            "PATH": "/usr/local/bin:/bin:/bin/tools:/home/buildAgent/bin",
+            "HOME": "/home/buildAgent",
+            "TZ": "EST"
+          },
+          "toolConfigurationNotifications": [
+            {
+              "descriptor": {
+                "id": "UnknownRule"
+              },
+              "associatedRule": {
+                "ruleId": "ABC0001"
+              },
+              "level": "warning",
+              "message": {
+                "text": "Could not disable rule \"ABC0001\" because there is no rule with that id."
+              }
+            }
+          ],
+          "toolExecutionNotifications": [
+            {
+              "descriptor": {
+                "id": "CTN0001"
+              },
+              "level": "note",
+              "message": {
+                "text": "Run started."
+              }
+            },
+            {
+              "descriptor": {
+                "id": "CTN9999"
+              },
+              "associatedRule": {
+                "id": "C2001",
+                "index": 0
+              },
+              "level": "error",
+              "message": {
+                "text": "Exception evaluating rule \"C2001\". Rule disabled; run continues."
+              },
+              "locations": [
+                {
+                  "physicalLocation": {
+                    "artifactLocation": {
+                      "uri": "crypto/hash.cpp",
+                      "uriBaseId": "SRCROOT",
+                      "index": 4
+                    }
+                  }
+                }
+              ],
+              "threadId": 52,
+              "timeUtc": "2016-07-16T14:18:43.119Z",
+              "exception": {
+                "kind": "ExecutionEngine.RuleFailureException",
+                "message": "Unhandled exception during rule evaluation.",
+                "stack": {
+                  "frames": [
+                    {
+                      "location": {
+                        "message": {
+                          "text": "Exception thrown"
+                        },
+                        "logicalLocations": [
+                          {
+                            "fullyQualifiedName":
+                              "Rules.SecureHashAlgorithmRule.Evaluate"
+                          }
+                        ],
+                        "physicalLocation": {
+                          "address": {
+                            "offset": 4244988
+                          }
+                        }
+                      },
+                      "module": "RuleLibrary",
+                      "threadId": 52
+                    },
+                    {
+                      "location": {
+                        "logicalLocations": [
+                          {
+                            "fullyQualifiedName":
+                              "ExecutionEngine.Engine.EvaluateRule"
+                          }
+                        ],
+                        "physicalLocation": {
+                          "address": {
+                            "offset": 4245514
+                          }
+                        }
+                      },
+                      "module": "ExecutionEngine",
+                      "threadId": 52
+                    }
+                  ]
+                },
+                "innerExceptions": [
+                  {
+                    "kind": "System.ArgumentException",
+                    "message": "length is < 0"
+                  }
+                ]
+              }
+            },
+            {
+              "descriptor": {
+                "id": "CTN0002"
+              },
+              "level": "note",
+              "message": {
+                "text": "Run ended."
+              }
+            }
+          ],
+          "exitCode": 0,
+          "executionSuccessful": true
+        }
+      ],
+      "artifacts": [
+        {
+          "location": {
+            "uri": "build/collections.rsp",
+            "uriBaseId": "SRCROOT"
+          },
+          "mimeType": "text/plain",
+          "length": 81,
+          "contents": {
+            "text": "-input src/collections/*.cpp -log out/collections.sarif -rules all -disable C9999"
+          }
+        },
+        {
+          "location": {
+            "uri": "application/main.cpp",
+            "uriBaseId": "SRCROOT"
+          },
+          "sourceLanguage": "cplusplus",
+          "length": 1742,
+          "hashes": {
+            "sha-256": "cc8e6a99f3eff00adc649fee132ba80fe333ea5a"
+          }
+        },
+        {
+          "location": {
+            "uri": "collections/list.cpp",
+            "uriBaseId": "SRCROOT"
+          },
+          "sourceLanguage": "cplusplus",
+          "length": 980,
+          "hashes": {
+            "sha-256": "b13ce2678a8807ba0765ab94a0ecd394f869bc81"
+          }
+        },
+        {
+          "location": {
+            "uri": "collections/list.h",
+            "uriBaseId": "SRCROOT"
+          },
+          "sourceLanguage": "cplusplus",
+          "length": 24656,
+          "hashes": {
+            "sha-256": "849be119aaba4e9f88921a99e3036fb6c2a8144a"
+          }
+        },
+        {
+          "location": {
+            "uri": "crypto/hash.cpp",
+            "uriBaseId": "SRCROOT"
+          },
+          "sourceLanguage": "cplusplus",
+          "length": 1424,
+          "hashes": {
+            "sha-256": "3ffe2b77dz255cdf95f97d986d7a6ad8f287eaed"
+          }
+        },
+        {
+          "location": {
+            "uri": "app.zip",
+            "uriBaseId": "BINROOT"
+          },
+          "mimeType": "application/zip",
+          "length": 310450,
+          "hashes": {
+            "sha-256": "df18a5e74b6b46ddaa23ad7271ee2b7c5731cbe1"
+          }
+        },
+        {
+          "location": {
+            "uri": "/docs/intro.docx"
+          },
+          "mimeType":
+             "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+          "parentIndex": 5,
+          "offset": 17522,
+          "length": 4050
+        }
+      ],
+      "logicalLocations": [
+        {
+          "name": "add",
+          "fullyQualifiedName": "collections::list::add",
+          "decoratedName": "?add@list@collections@@QAEXH@Z",
+          "kind": "function",
+          "parentIndex": 1
+        },
+        {
+          "name": "list",
+          "fullyQualifiedName": "collections::list",
+          "kind": "type",
+          "parentIndex": 2
+        },
+        {
+          "name": "collections",
+          "kind": "namespace"
+        },
+        {
+          "name": "add_core",
+          "fullyQualfiedName": "collections::list::add_core",
+          "decoratedName": "?add_core@list@collections@@QAEXH@Z",
+          "kind": "function",
+          "parentIndex": 1
+        },
+        {
+          "fullyQualifiedName": "main",
+          "kind": "function"
+        }
+      ],
+      "results": [
+        {
+          "ruleId": "C2001",
+          "ruleIndex": 0,
+          "kind": "fail",
+          "level": "error",
+          "message": {
+            "id": "default",
+            "arguments": [
+              "ptr",
+              "0"
+            ]
+          },
+          "suppressions": [
+            {
+              "kind": "external",
+              "status": "accepted"
+            }
+          ],
+          "baselineState": "unchanged",
+          "rank": 95,
+          "analysisTarget": {
+            "uri": "collections/list.cpp",
+            "uriBaseId": "SRCROOT",
+            "index": 2
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "collections/list.h",
+                  "uriBaseId": "SRCROOT",
+                  "index": 3
+                },
+                "region": {
+                  "startLine": 15,
+                  "startColumn": 9,
+                  "endLine": 15,
+                  "endColumn": 10,
+                  "charLength": 1,
+                  "charOffset": 254,
+                  "snippet": {
+                    "text": "add_core(ptr, offset, val);\n    return;"
+                  }
+                }
+              },
+              "logicalLocations": [
+                {
+                  "fullyQualifiedName": "collections::list::add",
+                  "index": 0
+                }
+              ]
+            }
+          ],
+          "relatedLocations": [
+            {
+              "id": 0,
+              "message": {
+                "id": "variableDeclared",
+                "arguments": [
+                  "ptr"
+                ]
+              },
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "collections/list.h",
+                  "uriBaseId": "SRCROOT",
+                  "index": 3
+                },
+                "region": {
+                  "startLine": 8,
+                  "startColumn": 5
+                }
+              },
+              "logicalLocations": [
+                {
+                  "fullyQualifiedName": "collections::list::add",
+                  "index": 0
+                }
+              ]
+            }
+          ],
+          "codeFlows": [
+            {
+              "message": {
+                "text": "Path from declaration to usage"
+              },
+ 
+              "threadFlows": [
+                {
+                  "id": "thread-52",
+                  "locations": [
+                    {
+                      "importance": "essential",
+                      "location": {
+                        "message": {
+                          "text": "Variable \"ptr\" declared.",
+                          "markdown": "Variable `ptr` declared."
+                        },
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri":"collections/list.h",
+                            "uriBaseId": "SRCROOT",
+                            "index": 3
+                          },
+                          "region": {
+                            "startLine": 15,
+                            "snippet": {
+                              "text": "int *ptr;"
+                            }
+                          }
+                        },
+                        "logicalLocations": [
+                          {
+                            "fullyQualifiedName": "collections::list::add",
+                            "index": 0
+                          }
+                        ]
+                      },
+                      "module": "platform"
+                    },
+                    {
+                      "state": {
+                        "y": {
+                          "text": "2"
+                        },
+                        "z": {
+                          "text": "4"
+                        },
+                        "y + z": {
+                          "text": "6"
+                        },
+                        "q": {
+                          "text": "7"
+                        }
+                      },
+                      "importance": "unimportant",
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri":"collections/list.h",
+                            "uriBaseId": "SRCROOT",
+                            "index": 3
+                          },
+                          "region": {
+                            "startLine": 15,
+                            "snippet": {
+                             "text": "offset = (y + z) * q + 1;"
+                            }
+                          }
+                        },
+                        "logicalLocations": [
+                          {
+                            "fullyQualifiedName": "collections::list::add",
+                            "index": 0
+                          }
+                        ],
+                        "annotations": [
+                          {
+                            "startLine": 15,
+                            "startColumn": 13,
+                            "endColumn": 19,
+                            "message": {
+                              "text": "(y + z) = 42",
+                              "markdown": "`(y + z) = 42`"
+                            }
+                          }
+                        ]
+                      },
+                      "module": "platform"
+                    },
+                    {
+                      "importance": "essential",
+                      "location": {
+                        "message": {
+                          "text": "Uninitialized variable \"ptr\" passed to method \"add_core\".",
+                          "markdown": "Uninitialized variable `ptr` passed to method `add_core`."
+                        },
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri":"collections/list.h",
+                            "uriBaseId": "SRCROOT",
+                            "index": 3
+                          },
+                          "region": {
+                            "startLine": 25,
+                            "snippet": {
+                              "text": "add_core(ptr, offset, val)"
+                            }
+                          }
+                        },
+                        "logicalLocations": [
+                          {
+                            "fullyQualifiedName": "collections::list::add",
+                            "index": 0
+                          }
+                        ]
+                      },
+                      "module": "platform"
+                    }
+                  ]
+                }
+              ]
+            }
+          ],
+          "stacks": [
+            {
+              "message": {
+                "text": "Call stack resulting from usage of uninitialized variable."
+              },
+              "frames": [
+                {
+                  "location": {
+                    "message": {
+                      "text": "Exception thrown."
+                    },
+                    "physicalLocation": {
+                      "artifactLocation": {
+                        "uri": "collections/list.h",
+                        "uriBaseId": "SRCROOT",
+                        "index": 3
+                      },
+                      "region": {
+                        "startLine": 110,
+                        "startColumn": 15
+                      },
+                      "address": {
+                        "offset": 4229178
+                      }
+                    },
+                    "logicalLocations": [
+                      {
+                        "fullyQualifiedName": "collections::list::add_core",
+                        "index": 0
+                      }
+                    ]
+                  },
+                  "module": "platform",
+                  "threadId": 52,
+                  "parameters": [ "null", "0", "14" ]
+                },
+                {
+                  "location": {
+                    "physicalLocation": {
+                      "artifactLocation": {
+                        "uri": "collections/list.h",
+                        "uriBaseId": "SRCROOT",
+                        "index": 3
+                      },
+                      "region": {
+                        "startLine": 43,
+                        "startColumn": 15
+                      },
+                      "address": {
+                        "offset": 4229268
+                      }
+                    },
+                    "logicalLocations": [
+                      {
+                        "fullyQualifiedName": "collections::list::add",
+                        "index": 0
+                      }
+                    ]
+                  },
+                  "module": "platform",
+                  "threadId": 52,
+                  "parameters": [ "14" ]
+                },
+                {
+                  "location": {
+                    "physicalLocation": {
+                      "artifactLocation": {
+                        "uri": "application/main.cpp",
+                        "uriBaseId": "SRCROOT",
+                        "index": 1
+                      },
+                      "region": {
+                        "startLine": 28,
+                        "startColumn": 9
+                      },
+                      "address": {
+                        "offset": 4229836
+                      }
+                    },
+                    "logicalLocations": [
+                      {
+                        "fullyQualifiedName": "main",
+                        "index": 4
+                      }
+                    ]
+                  },
+                  "module": "application",
+                  "threadId": 52
+                }
+              ]
+            }
+          ],
+          "addresses": [
+            {
+              "baseAddress": 4194304,
+              "fullyQualifiedName": "collections.dll",
+              "kind": "module",
+              "section": ".text"
+            },
+            {
+              "offset": 100,
+              "fullyQualifiedName": "collections.dll!collections::list::add",
+              "kind": "function",
+              "parentIndex": 0
+            },
+            {
+              "offset": 22,
+              "fullyQualifiedName": "collections.dll!collections::list::add+0x16",
+              "parentIndex": 1
+            }
+          ],
+          "fixes": [
+            {
+              "description": {
+                "text": "Initialize the variable to null"
+              },
+              "artifactChanges": [
+                {
+                  "artifactLocation": {
+                    "uri": "collections/list.h",
+                    "uriBaseId": "SRCROOT",
+                    "index": 3
+                  },
+                  "replacements": [
+                    {
+                      "deletedRegion": {
+                        "startLine": 42
+                      },
+                      "insertedContent": {
+                        "text": "A different line\n"
+                      }
+                    }
+                  ]
+                }
+              ]
+            }
+          ],
+          "hostedViewerUri":
+            "https://www.example.com/viewer/3918d370-c636-40d8-bf23-8c176043a2df",
+          "workItemUris": [
+            "https://github.com/example/project/issues/42",
+            "https://github.com/example/project/issues/54"
+          ],
+          "provenance": {
+            "firstDetectionTimeUtc": "2016-07-15T14:20:42Z",
+            "firstDetectionRunGuid": "8F62D8A0-C14F-4516-9959-1A663BA6FB99",
+            "lastDetectionTimeUtc": "2016-07-16T14:20:42Z",
+            "lastDetectionRunGuid": "BC650830-A9FE-44CB-8818-AD6C387279A0",
+            "invocationIndex": 0
+          }
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+In function 'collections::list::add':
+collections/list.h:15:9: error: Variable "ptr" was used without being initialized. It was declared [here](0). [C2001]
+  event 1
+    |
+    |
+  event 2
+    |
+    |
+  event 3
+    |
+    |
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+collections/list.h:8:5: note: Variable "ptr" was declared here.
+   { dg-end-multiline-output "" } */
+
+// TODO: what's up with the events?
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif
new file mode 100644
index 000000000000..6a1252072d16
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/tutorial-example.sarif
@@ -0,0 +1,117 @@ 
+/* Adapted from https://github.com/microsoft/sarif-tutorials
+   samples/bad-eval-with-code-flow.sarif.
+   which is licensed under the Creative Commons Attribution 4.0 International Public License
+   and/or the MIT License.  */
+
+{
+  "version": "2.1.0",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "name": "PythonScanner"
+        }
+      },
+      "results": [
+        {
+          "ruleId": "PY2335",
+          "message": {
+            "text": "Use of tainted variable 'raw_input' in the insecure function 'eval'."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "bad-eval-with-code-flow.py"
+                },
+                "region": {
+                  "startLine": 10
+                }
+              }
+            }
+          ],
+          "codeFlows": [
+            {
+              "message": {
+                "text": "Tracing the path from user input to insecure usage."
+              },
+              "threadFlows": [
+                {
+                  "locations": [
+                    {
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri": "bad-eval-with-code-flow.py"
+                          },
+                          "region": {
+                            "startLine": 5
+                          }
+                        }
+                      },
+                      "state": {
+                        "expr": {
+                          "text": "undef"
+                        }
+                      },
+                      "nestingLevel": 0
+                    },
+                    {
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri": "bad-eval-with-code-flow.py"
+                          },
+                          "region": {
+                            "startLine": 6
+                          }
+                        }
+                      },
+                      "state": {
+                        "expr": {
+                          "text": "42"
+                        }
+                      },
+                      "nestingLevel": 0
+                    },
+                    {
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri": "bad-eval-with-code-flow.py"
+                          },
+                          "region": {
+                            "startLine": 10
+                          }
+                        }
+                      },
+                      "state": {
+                        "raw_input": {
+                          "text": "42"
+                        }
+                      },
+                      "nestingLevel": 1
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+bad-eval-with-code-flow.py:10: warning: Use of tainted variable 'raw_input' in the insecure function 'eval'. [PY2335]
+  events 1-2
+    |
+    |
+    +--> event 3
+           |
+           |
+   { dg-end-multiline-output "" } */
+
+// TODO: show path even when we can't find the source
+// TODO: show path when we can find the source
\ No newline at end of file
diff --git a/gcc/testsuite/sarif-replay.dg/dg.exp b/gcc/testsuite/sarif-replay.dg/dg.exp
new file mode 100644
index 000000000000..cf9c82d9ae2b
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/dg.exp
@@ -0,0 +1,46 @@ 
+#  Copyright (C) 2023-2024 Free Software Foundation, Inc.
+
+#  This file is part of GCC.
+#
+#  GCC is free software; you can redistribute it and/or modify it under
+#  the terms of the GNU General Public License as published by the Free
+#  Software Foundation; either version 3, or (at your option) any later
+#  version.
+#
+#  GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+#  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+#  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+#  for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with GCC; see the file COPYING3.  If not see
+#  <http://www.gnu.org/licenses/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib sarif-replay-dg.exp
+
+global DEFAULT_SARIF_REPLAY_FLAGS
+if [info exists DEFAULT_SARIF_REPLAY_FLAGS] then {
+  set save_default_sarif_replay_flags $DEFAULT_SARIF_REPLAY_FLAGS
+}
+
+# If a testcase doesn't have special options, use these.
+set DEFAULT_SARIF_REPLAY_FLAGS "-fjson-comments -fdiagnostics-color=never"
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+sarif-replay-dg-runtest [lsort \
+       [glob -nocomplain $srcdir/$subdir/*/*.sarif ] ] "" $DEFAULT_SARIF_REPLAY_FLAGS
+
+# All done.
+dg-finish
+
+if [info exists save_default_sarif_replay_flags] {
+  set DEFAULT_SARIF_REPLAY_FLAGS $save_default_sarif_replay_flags
+} else {
+  unset DEFAULT_SARIF_REPLAY_FLAGS
+}
diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif
new file mode 100644
index 000000000000..0d64aeb73de1
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/malformed-json/array-missing-comma.sarif
@@ -0,0 +1,6 @@ 
+["foo", "bar" "baz"] /* { dg-error "expected ',' or '\]'; got string" } */
+
+/* { dg-begin-multiline-output "" }
+    1 | ["foo", "bar" "baz"]
+      |               ^~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif
new file mode 100644
index 000000000000..05b74a81efcc
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/malformed-json/array-with-trailing-comma.sarif
@@ -0,0 +1,6 @@ 
+[ 0, 1, 2, ] /* { dg-error "expected a JSON value but got '\\\]'" } */
+
+{ dg-begin-multiline-output "" }
+    1 | [ 0, 1, 2, ]
+      |            ^
+{ dg-end-multiline-output "" }
diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif
new file mode 100644
index 000000000000..7756eef1add3
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/malformed-json/bad-token.sarif
@@ -0,0 +1,6 @@ 
+  not a valid JSON file // { dg-error "invalid JSON token: unexpected character: 'n'" }
+
+{ dg-begin-multiline-output "" }
+    1 |   not a valid JSON file
+      |   ^
+{ dg-end-multiline-output "" }
diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif
new file mode 100644
index 000000000000..9d2bf9476b16
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/malformed-json/object-missing-comma.sarif
@@ -0,0 +1,7 @@ 
+{ "foo": "bar"
+  "baz": 42 } // { dg-error "expected ',' or '\}'; got string" }
+
+{ dg-begin-multiline-output "" }
+    2 |   "baz": 42 }
+      |   ^~~~~
+{ dg-end-multiline-output "" }
diff --git a/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif b/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif
new file mode 100644
index 000000000000..e1aae9b350c9
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/malformed-json/object-with-trailing-comma.sarif
@@ -0,0 +1,6 @@ 
+{ "foo": "bar", } /* { dg-error "expected string for object key after ','; got '\\\}'" } */
+
+{ dg-begin-multiline-output "" }
+    1 | { "foo": "bar", }
+      |                 ^
+{ dg-end-multiline-output "" }