public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: David Malcolm <dmalcolm@redhat.com>
To: gcc-patches@gcc.gnu.org
Subject: [PATCH 10/12] Add sarif frontend
Date: Wed, 22 Jun 2022 18:34:45 -0400	[thread overview]
Message-ID: <20220622223447.2462880-11-dmalcolm@redhat.com> (raw)
In-Reply-To: <20220622223447.2462880-1-dmalcolm@redhat.com>

This patch is a work-in-progress (lots of TODOs and FIXMEs) that adds a new
SARIF frontend to gcc: sarif-replayer, which is invoked when passing .sarif
files to the gcc driver program: it will attempt to replay the .sarif file
using the provided diagnostic formatting options.

gcc/ChangeLog:
	* sarif/Make-lang.in: New file.
	* sarif/lang.opt: New file.
	* sarif/sarif-frontend.cc: New file.
	* sarif/sarif-replay.cc: New file.
	* sarif/sarif-replay.h: New file.

gcc/testsuite/ChangeLog:
	* lib/sarif-dg.exp: New test.
	* lib/sarif.exp: New test.
	* sarif/bad-eval-with-code-flow.py: New test.
	* sarif/escaped-braces.sarif: New test.
	* sarif/invalid-json-array-missing-comma.sarif: New test.
	* sarif/invalid-json-array-with-trailing-comma.sarif: New test.
	* sarif/invalid-json-bad-token.sarif: New test.
	* sarif/invalid-json-object-missing-comma.sarif: New test.
	* sarif/invalid-json-object-with-trailing-comma.sarif: New test.
	* sarif/invalid-sarif-bad-runs.sarif: New test.
	* sarif/invalid-sarif-missing-arguments-for-placeholders.sarif:
	New test.
	* sarif/invalid-sarif-no-runs.sarif: New test.
	* sarif/invalid-sarif-no-version.sarif: New test.
	* sarif/invalid-sarif-non-object-in-runs.sarif: New test.
	* sarif/invalid-sarif-not-an-object.sarif: New test.
	* sarif/invalid-sarif-not-enough-arguments-for-placeholders.sarif:
	New test.
	* sarif/invalid-sarif-version-not-a-string.sarif: New test.
	* sarif/malformed-placeholder.sarif: New test.
	* sarif/null-runs.sarif: New test.
	* sarif/roundtrip-signal-1.c.sarif: New test.
	* sarif/sarif.exp: New test.
	* sarif/signal-1.c.sarif: New test.
	* sarif/spec-example-1.sarif: New test.
	* sarif/spec-example-2.sarif: New test.
	* sarif/spec-example-3.sarif: New test.
	* sarif/spec-example-4.sarif: New test.
	* sarif/tutorial-example-foo.sarif: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/sarif/Make-lang.in                        |  132 ++
 gcc/sarif/config-lang.in                      |   34 +
 gcc/sarif/lang-specs.h                        |   26 +
 gcc/sarif/lang.opt                            |   31 +
 gcc/sarif/sarif-frontend.cc                   |  191 +++
 gcc/sarif/sarif-replay.cc                     | 1489 +++++++++++++++++
 gcc/sarif/sarif-replay.h                      |   26 +
 gcc/testsuite/lib/sarif-dg.exp                |  233 +++
 gcc/testsuite/lib/sarif.exp                   |   36 +
 .../sarif/bad-eval-with-code-flow.py          |   10 +
 gcc/testsuite/sarif/escaped-braces.sarif      |   19 +
 .../invalid-json-array-missing-comma.sarif    |    6 +
 ...valid-json-array-with-trailing-comma.sarif |    6 +
 .../sarif/invalid-json-bad-token.sarif        |    6 +
 .../invalid-json-object-missing-comma.sarif   |    7 +
 ...alid-json-object-with-trailing-comma.sarif |    6 +
 .../sarif/invalid-sarif-bad-runs.sarif        |    7 +
 ...f-missing-arguments-for-placeholders.sarif |   14 +
 .../sarif/invalid-sarif-no-runs.sarif         |    6 +
 .../sarif/invalid-sarif-no-version.sarif      |    6 +
 .../invalid-sarif-non-object-in-runs.sarif    |    7 +
 .../sarif/invalid-sarif-not-an-object.sarif   |    6 +
 ...ot-enough-arguments-for-placeholders.sarif |   14 +
 .../invalid-sarif-version-not-a-string.sarif  |    6 +
 .../sarif/malformed-placeholder.sarif         |   15 +
 gcc/testsuite/sarif/null-runs.sarif           |    2 +
 .../sarif/roundtrip-signal-1.c.sarif          |  398 +++++
 gcc/testsuite/sarif/sarif.exp                 |   50 +
 gcc/testsuite/sarif/signal-1.c.sarif          |  362 ++++
 gcc/testsuite/sarif/spec-example-1.sarif      |   15 +
 gcc/testsuite/sarif/spec-example-2.sarif      |   74 +
 gcc/testsuite/sarif/spec-example-3.sarif      |   67 +
 gcc/testsuite/sarif/spec-example-4.sarif      |  758 +++++++++
 .../sarif/tutorial-example-foo.sarif          |  117 ++
 34 files changed, 4182 insertions(+)
 create mode 100644 gcc/sarif/Make-lang.in
 create mode 100644 gcc/sarif/config-lang.in
 create mode 100644 gcc/sarif/lang-specs.h
 create mode 100644 gcc/sarif/lang.opt
 create mode 100644 gcc/sarif/sarif-frontend.cc
 create mode 100644 gcc/sarif/sarif-replay.cc
 create mode 100644 gcc/sarif/sarif-replay.h
 create mode 100644 gcc/testsuite/lib/sarif-dg.exp
 create mode 100644 gcc/testsuite/lib/sarif.exp
 create mode 100644 gcc/testsuite/sarif/bad-eval-with-code-flow.py
 create mode 100644 gcc/testsuite/sarif/escaped-braces.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-json-array-missing-comma.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-json-array-with-trailing-comma.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-json-bad-token.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-json-object-missing-comma.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-json-object-with-trailing-comma.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-bad-runs.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-missing-arguments-for-placeholders.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-no-runs.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-no-version.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-non-object-in-runs.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-not-an-object.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-not-enough-arguments-for-placeholders.sarif
 create mode 100644 gcc/testsuite/sarif/invalid-sarif-version-not-a-string.sarif
 create mode 100644 gcc/testsuite/sarif/malformed-placeholder.sarif
 create mode 100644 gcc/testsuite/sarif/null-runs.sarif
 create mode 100644 gcc/testsuite/sarif/roundtrip-signal-1.c.sarif
 create mode 100644 gcc/testsuite/sarif/sarif.exp
 create mode 100644 gcc/testsuite/sarif/signal-1.c.sarif
 create mode 100644 gcc/testsuite/sarif/spec-example-1.sarif
 create mode 100644 gcc/testsuite/sarif/spec-example-2.sarif
 create mode 100644 gcc/testsuite/sarif/spec-example-3.sarif
 create mode 100644 gcc/testsuite/sarif/spec-example-4.sarif
 create mode 100644 gcc/testsuite/sarif/tutorial-example-foo.sarif

diff --git a/gcc/sarif/Make-lang.in b/gcc/sarif/Make-lang.in
new file mode 100644
index 00000000000..53b6239da07
--- /dev/null
+++ b/gcc/sarif/Make-lang.in
@@ -0,0 +1,132 @@
+# Make-lang.in -- Top level -*- makefile -*- fragment for gcc SARIF "frontend".
+
+# Copyright (C) 2022 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/>.
+
+# This file provides the language dependent support in the main Makefile.
+
+# The name for selecting sarif in LANGUAGES.
+sarif: sarif-replay$(exeext)
+
+.PHONY: sarif
+
+SARIF_OBJS = sarif/sarif-frontend.o sarif/sarif-replay.o \
+	attribs.o deferred-locations.o json-reader.o
+sarif_OBJS = $(SARIF_OBJS)
+
+sarif-replay$(exeext): $(SARIF_OBJS) $(BACKEND) $(LIBDEPS)
+	@$(call LINK_PROGRESS,$(INDEX.sarif),start)
+	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ \
+	      $(SARIF_OBJS) $(BACKEND) $(LIBS) $(BACKENDLIBS)
+	@$(call LINK_PROGRESS,$(INDEX.sarif),end)
+
+# Build hooks.
+
+sarif.all.cross:
+sarif.start.encap:
+sarif.rest.encap:
+
+sarif.info:
+sarif.man:
+
+lang_checks += check-sarif
+#lang_checks_parallelized += check-sarif
+#check_sarif_parallelize = 10
+
+# No sarif-specific selftests
+selftest-sarif:
+
+# Install hooks.
+
+sarif.install-common: installdirs
+	-rm -f $(DESTDIR)$(bindir)/$(GCCSARIF_INSTALL_NAME)$(exeext)
+	$(INSTALL_PROGRAM) gccsarif$(exeext) $(DESTDIR)$(bindir)/$(GCCSARIF_INSTALL_NAME)$(exeext)
+	-if test -f sarif-replay$(exeext); then \
+	  if test -f gccsarif-cross$(exeext); then \
+	    :; \
+	  else \
+	    rm -f $(DESTDIR)$(bindir)/$(GCCSARIF_TARGET_INSTALL_NAME)$(exeext); \
+	    ( cd $(DESTDIR)$(bindir) && \
+	      $(LN) $(GCCSARIF_INSTALL_NAME)$(exeext) $(GCCSARIF_TARGET_INSTALL_NAME)$(exeext) ); \
+	  fi; \
+	fi
+
+sarif.install-plugin:
+
+sarif.install-info: $(DESTDIR)$(infodir)/gccsarif.info
+
+sarif.install-pdf: doc/gccsarif.pdf
+	@$(NORMAL_INSTALL)
+	test -z "$(pdfdir)" || $(mkinstalldirs) "$(DESTDIR)$(pdfdir)/gcc"
+	@for p in doc/gccsarif.pdf; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  f=$(pdf__strip_dir) \
+	  echo " $(INSTALL_DATA) '$$d$$p' '$(DESTDIR)$(pdfdir)/gcc/$$f'"; \
+	  $(INSTALL_DATA) "$$d$$p" "$(DESTDIR)$(pdfdir)/gcc/$$f"; \
+	done
+
+sarif.install-html: $(build_htmldir)/sarif
+	@$(NORMAL_INSTALL)
+	test -z "$(htmldir)" || $(mkinstalldirs) "$(DESTDIR)$(htmldir)"
+	@for p in $(build_htmldir)/sarif; do \
+	  if test -f "$$p" || test -d "$$p"; then d=""; else d="$(srcdir)/"; fi; \
+	  f=$(html__strip_dir) \
+	  if test -d "$$d$$p"; then \
+	    echo " $(mkinstalldirs) '$(DESTDIR)$(htmldir)/$$f'"; \
+	    $(mkinstalldirs) "$(DESTDIR)$(htmldir)/$$f" || exit 1; \
+	    echo " $(INSTALL_DATA) '$$d$$p'/* '$(DESTDIR)$(htmldir)/$$f'"; \
+	    $(INSTALL_DATA) "$$d$$p"/* "$(DESTDIR)$(htmldir)/$$f"; \
+	  else \
+	    echo " $(INSTALL_DATA) '$$d$$p' '$(DESTDIR)$(htmldir)/$$f'"; \
+	    $(INSTALL_DATA) "$$d$$p" "$(DESTDIR)$(htmldir)/$$f"; \
+	  fi; \
+	done
+
+sarif.install-man: $(DESTDIR)$(man1dir)/$(GCCSARIF_INSTALL_NAME)$(man1ext)
+
+sarif.uninstall:
+	rm -rf $(DESTDIR)$(bindir)/$(GCCSARIF_INSTALL_NAME)$(exeext)
+	rm -rf $(DESTDIR)$(man1dir)/$(GCCSARIF_INSTALL_NAME)$(man1ext)
+	rm -rf $(DESTDIR)$(bindir)/$(GCCSARIF_TARGET_INSTALL_NAME)$(exeext)
+	rm -rf $(DESTDIR)$(infodir)/gccsarif.info*
+
+# Clean hooks.
+
+sarif.mostlyclean:
+	-rm -f sarif/*$(objext)
+	-rm -f sarif/*$(coverageexts)
+	-rm -f gccsarif$(exeext) gccsarif-cross$(exeext) sarif-replay$(exeext)
+sarif.clean:
+sarif.distclean:
+sarif.maintainer-clean:
+	-rm -f $(docobjdir)/gccsarif.1
+
+# Stage hooks.
+
+sarif.stage1: stage1-start
+	-mv sarif/*$(objext) stage1/sarif
+sarif.stage2: stage2-start
+	-mv sarif/*$(objext) stage2/sarif
+sarif.stage3: stage3-start
+	-mv sarif/*$(objext) stage3/sarif
+sarif.stage4: stage4-start
+	-mv sarif/*$(objext) stage4/sarif
+sarif.stageprofile: stageprofile-start
+	-mv sarif/*$(objext) stageprofile/sarif
+sarif.stagefeedback: stagefeedback-start
+	-mv sarif/*$(objext) stagefeedback/sarif
diff --git a/gcc/sarif/config-lang.in b/gcc/sarif/config-lang.in
new file mode 100644
index 00000000000..6ed01f4116d
--- /dev/null
+++ b/gcc/sarif/config-lang.in
@@ -0,0 +1,34 @@
+# config-lang.in -- Top level configure fragment for gcc SARIF "frontend".
+
+# Copyright (C) 2022 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/>.
+
+# Configure looks for the existence of this file to auto-config each language.
+# We define several parameters used by configure:
+#
+# language	- name of language as it would appear in $(LANGUAGES)
+# compilers	- value to add to $(COMPILERS)
+
+language="sarif"
+
+compilers="sarif-replay\$(exeext)"
+
+gtfiles="\$(srcdir)/sarif/sarif-frontend.cc"
+
+# Build by default.
+build_by_default="yes"
diff --git a/gcc/sarif/lang-specs.h b/gcc/sarif/lang-specs.h
new file mode 100644
index 00000000000..750689c8374
--- /dev/null
+++ b/gcc/sarif/lang-specs.h
@@ -0,0 +1,26 @@
+/* lang-specs.h -- gcc driver specs for the SARIF "frontend".
+   Copyright (C) 2022 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/>.  */
+
+/* This is the contribution to the `default_compilers' array in gcc.cc
+   for the sarif "frontend".  */
+
+{".sarif",  "@sarif", 0, 1, 0},
+/* FIXME: */
+{"@sarif",  "sarif-replay %i %(cc1_options)",
+    0, 1, 0},
diff --git a/gcc/sarif/lang.opt b/gcc/sarif/lang.opt
new file mode 100644
index 00000000000..33d17879333
--- /dev/null
+++ b/gcc/sarif/lang.opt
@@ -0,0 +1,31 @@
+; Options for the SARIF front end.
+; Copyright (C) 2022 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/>.
+
+; See the GCC internals manual for a description of this file's format.
+
+; Please try to keep this file in ASCII collating order.
+
+Language
+SARIF
+
+fallow-comments
+SARIF Var(flag_allow_comments)
+Extend JSON to support comments
+
+; This comment is to ensure we retain the blank line above.
diff --git a/gcc/sarif/sarif-frontend.cc b/gcc/sarif/sarif-frontend.cc
new file mode 100644
index 00000000000..7623c85fee4
--- /dev/null
+++ b/gcc/sarif/sarif-frontend.cc
@@ -0,0 +1,191 @@
+/* The dummy "frontend" for re-emitting diagnostics saved in SARIF form.
+   Copyright (C) 2022 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/>.  */
+
+// TODO: prune this
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "debug.h"
+#include "langhooks.h"
+#include "langhooks-def.h"
+#include "diagnostic.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
+#include "opts.h"
+#include "options.h"
+#include "line-map.h"
+#include "stringpool.h"
+#include "gcc-rich-location.h"
+#include "json.h"
+#include "json-reader.h"
+#include "deferred-locations.h"
+#include "logical-location.h"
+#include "diagnostic-client-data-hooks.h"
+#include "sarif/sarif-replay.h"
+
+/* Placeholder implementation; needed by a frontend.  */
+
+tree
+convert (tree, tree)
+{
+  gcc_unreachable ();
+  return NULL_TREE;
+}
+
+/* Language-dependent contents of a type.  */
+
+struct GTY(()) lang_type
+{
+  char dummy;
+};
+
+/* Language-dependent contents of a decl.  */
+
+struct GTY((variable_size)) lang_decl
+{
+  char dummy;
+};
+
+/* Language-dependent contents of an identifier.  This must include a
+   tree_identifier.  */
+
+struct GTY(()) lang_identifier
+{
+  struct tree_identifier common;
+};
+
+/* The resulting tree type.  */
+
+union GTY((desc ("TREE_CODE (&%h.generic) == IDENTIFIER_NODE"),
+	   chain_next ("CODE_CONTAINS_STRUCT (TREE_CODE (&%h.generic), TS_COMMON) ? ((union lang_tree_node *) TREE_CHAIN (&%h.generic)) : NULL")))
+lang_tree_node
+{
+  union tree_node GTY((tag ("0"),
+		       desc ("tree_node_structure (&%h)"))) generic;
+  struct lang_identifier GTY((tag ("1"))) identifier;
+};
+
+/* We don't use language_function.  */
+
+struct GTY(()) language_function
+{
+  int dummy;
+};
+
+/* Language hooks.  */
+
+static bool
+sarif_langhook_init (void)
+{
+  build_common_tree_nodes (false);
+
+  replay_sarif (main_input_filename);
+
+  return false;
+}
+
+static unsigned int
+sarif_langhook_option_lang_mask (void)
+{
+  return CL_SARIF;
+}
+
+static bool
+sarif_langhook_handle_option (size_t scode,
+			      const char *arg,
+			      HOST_WIDE_INT value,
+			      int kind,
+			      location_t loc,
+			      const struct cl_option_handlers *handlers)
+{
+  bool result = true;
+
+  switch (scode)
+    {
+    default:
+      if (cl_options[scode].flags & sarif_langhook_option_lang_mask ())
+	break;
+      result = false;
+    }
+
+  SARIF_handle_option_auto (&global_options, &global_options_set,
+			    scode, arg, value,
+			    sarif_langhook_option_lang_mask (), kind,
+			    loc, handlers, global_dc);
+
+  return result;
+}
+
+static tree
+sarif_langhook_type_for_mode (machine_mode, int)
+{
+  gcc_unreachable ();
+  return NULL_TREE;
+}
+
+static bool
+sarif_langhook_global_bindings_p (void)
+{
+  return true;
+}
+
+static tree
+sarif_langhook_pushdecl (tree decl ATTRIBUTE_UNUSED)
+{
+  gcc_unreachable ();
+}
+
+static tree
+sarif_langhook_getdecls (void)
+{
+  return NULL;
+}
+#undef LANG_HOOKS_NAME
+#define LANG_HOOKS_NAME		"sarif"
+
+#undef LANG_HOOKS_INIT
+#define LANG_HOOKS_INIT		sarif_langhook_init
+
+#undef LANG_HOOKS_OPTION_LANG_MASK
+#define LANG_HOOKS_OPTION_LANG_MASK	sarif_langhook_option_lang_mask
+
+#undef LANG_HOOKS_HANDLE_OPTION
+#define LANG_HOOKS_HANDLE_OPTION        sarif_langhook_handle_option
+
+#undef LANG_HOOKS_TYPE_FOR_MODE
+#define LANG_HOOKS_TYPE_FOR_MODE	sarif_langhook_type_for_mode
+
+#undef LANG_HOOKS_GLOBAL_BINDINGS_P
+#define LANG_HOOKS_GLOBAL_BINDINGS_P	sarif_langhook_global_bindings_p
+
+#undef LANG_HOOKS_PUSHDECL
+#define LANG_HOOKS_PUSHDECL		sarif_langhook_pushdecl
+
+#undef LANG_HOOKS_GETDECLS
+#define LANG_HOOKS_GETDECLS		sarif_langhook_getdecls
+
+#undef  LANG_HOOKS_DEEP_UNSHARING
+#define LANG_HOOKS_DEEP_UNSHARING	true
+
+struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
+
+#include "gt-sarif-sarif-frontend.h"
+#include "gtype-sarif.h"
diff --git a/gcc/sarif/sarif-replay.cc b/gcc/sarif/sarif-replay.cc
new file mode 100644
index 00000000000..2d5c58ead1e
--- /dev/null
+++ b/gcc/sarif/sarif-replay.cc
@@ -0,0 +1,1489 @@
+/* Re-emitting diagnostics saved in SARIF form.
+   Copyright (C) 2022 David Malcolm <dmalcolm@redhat.com>.
+   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/>.  */
+
+// TODO: prune this
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "debug.h"
+#include "langhooks.h"
+#include "langhooks-def.h"
+#include "diagnostic.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
+#include "opts.h"
+#include "options.h"
+#include "line-map.h"
+#include "stringpool.h"
+#include "gcc-rich-location.h"
+#include "json-reader.h"
+#include "deferred-locations.h"
+#include "logical-location.h"
+#include "diagnostic-client-data-hooks.h"
+#include "sarif/sarif-replay.h"
+
+/* Forward decls.  */
+
+class sarif_replayer;
+class sarif_diagnostic_client_data_hooks;
+
+/* FIXME.  */
+
+struct pending_location_range
+{
+  location_t m_start;
+  location_t m_end;
+};
+
+/* Concrete subclass of client_version_info, for use when
+   m_replayer->m_driver_obj is non-NULL.  */
+
+class current_driver_version_info : public client_version_info
+{
+public:
+  current_driver_version_info (sarif_replayer *replayer)
+  : m_replayer (replayer)
+  {}
+
+  const char *get_tool_name () const final override;
+  char *maybe_make_full_name () const final override;
+  const char *get_version_string () const final override;
+  char *maybe_make_version_url () const final override;
+  void for_each_plugin (plugin_visitor &visitor) const final override;
+
+private:
+  sarif_replayer *m_replayer;
+};
+
+/* Concrete subclass of logical_location, for use when
+   m_replayer->m_current_logical_loc_obj is non-NULL.  */
+
+class current_sarif_logical_location : public logical_location
+{
+public:
+  current_sarif_logical_location (sarif_replayer *replayer)
+  : m_replayer (replayer)
+  {}
+
+  const char *get_short_name () const final override;
+  const char *get_name_with_scope () const final override;
+  const char *get_internal_name () const final override;
+  enum logical_location_kind get_kind () const final override;
+
+private:
+  sarif_replayer *m_replayer;
+};
+
+/* Concrete subclass of diagnostic_client_data_hooks, for
+   use when replaying a SARIF file.
+
+   Takes ownership of the sarif_replayer and toplevel json::value objects.  */
+
+class sarif_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
+{
+ public:
+  sarif_diagnostic_client_data_hooks (sarif_replayer *replayer)
+  : m_replayer (replayer),
+    m_toplevel_jv (NULL),
+    m_driver_version_info (replayer),
+    m_current_logical_location (replayer)
+  {}
+
+  ~sarif_diagnostic_client_data_hooks ();
+
+  const client_version_info *get_any_version_info () const final override;
+  const logical_location *get_current_logical_location () const final override;
+  const char *
+  maybe_get_sarif_source_language (const char *filename) const final override;
+
+  void stash (json::value *toplevel_jv)
+  {
+    m_toplevel_jv = toplevel_jv;
+  }
+
+  sarif_replayer *m_replayer;
+  json::value *m_toplevel_jv;
+
+  current_driver_version_info m_driver_version_info;
+  current_sarif_logical_location m_current_logical_location;
+};
+
+/* Subclass of diagnostic_metadata::rule for a reference to the SARIF
+   specification.  */
+
+class spec_ref : public diagnostic_metadata::rule
+{
+public:
+  spec_ref (const char *section)
+    : m_section (section)
+  {}
+
+  char *make_description () const final override
+  {
+    /* '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 final override
+  {
+    /* 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 ).  */
+    return NULL;
+  }
+
+private:
+  /* 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;
+};
+
+/* A bundle of state for replaying a SARIF JSON file.  */
+
+class sarif_replayer : public json_reader
+{
+public:
+  friend class current_driver_version_info;
+  friend class current_sarif_logical_location;
+  friend class sarif_diagnostic_client_data_hooks;
+
+  sarif_replayer (const char *filename);
+  ~sarif_replayer ();
+
+  json::value *
+  get_optional_property (json::object *obj, const property_spec_ref &ref)
+  {
+    return obj->get (ref.get_property_name ());
+  }
+
+  json::value *
+  get_required_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      fatal_error_with_ref (obj, ref,
+			    "expected %s object to have a %qs property",
+			    ref.get_obj_name (), ref.get_property_name ());
+    return property_val;
+  }
+
+  int
+  require_int (json::value *jv, const property_spec_ref &ref)
+  {
+    if (json::integer_number *num = dyn_cast <json::integer_number *> (jv))
+      return num->get ();
+    fatal_error_with_ref (jv, ref, "expected %s.%s to be an integer",
+			  ref.get_obj_name (), ref.get_property_name ());
+    return 0;
+  }
+
+  bool
+  get_optional_int_property (int *out,
+			     json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      return false;
+    *out = require_int (property_val, ref);
+    return true;
+  }
+
+  const char *
+  require_string (json::value *jv, const property_spec_ref &ref)
+  {
+    json::string *str = dyn_cast <json::string *> (jv);
+    if (!str)
+      fatal_error_with_ref (jv, ref, "expected %s.%s to be a string",
+			    ref.get_obj_name (), ref.get_property_name ());
+    return str->get_string ();
+  }
+
+  const char *
+  get_optional_string_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      return NULL;
+    return require_string (property_val, ref);
+  }
+
+  const char *
+  get_required_string_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_required_property (obj, ref);
+    return require_string (property_val, ref);
+  }
+
+  json::object *
+  require_object (json::value *jv, const property_spec_ref &ref)
+  {
+    if (json::object *obj = dyn_cast <json::object *> (jv))
+      return obj;
+    fatal_error_with_ref (jv, ref, "expected %s.%s to be an object",
+			  ref.get_obj_name (), ref.get_property_name ());
+    return NULL;
+  }
+
+  json::object *
+  require_object_for_element (json::value *jv, const property_spec_ref &ref)
+  {
+    if (json::object *obj = dyn_cast <json::object *> (jv))
+      return obj;
+    fatal_error_with_ref (jv, ref,
+			  "expected element of %s.%s array to be an object",
+			  ref.get_obj_name (), ref.get_property_name ());
+    return NULL;
+  }
+
+  json::object *
+  get_optional_object_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      return NULL;
+    return require_object (property_val, ref);
+  }
+
+  json::object *
+  get_required_object_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_required_property (obj, ref);
+    if (!property_val)
+      return NULL;
+    return require_object (property_val, ref);
+  }
+
+  json::array *
+  require_array_property (json::value *jv, const property_spec_ref &ref)
+  {
+    if (json::array *obj = dyn_cast <json::array *> (jv))
+      return obj;
+    fatal_error_with_ref (jv, ref, "expected %s.%s to be an array",
+			  ref.get_obj_name (), ref.get_property_name ());
+    return NULL;
+  }
+
+  json::array *
+  get_optional_array_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_optional_property (obj, ref);
+    if (!property_val)
+      return NULL;
+    return require_array_property (property_val, ref);
+  }
+
+  json::array *
+  get_required_array_property (json::object *obj, const property_spec_ref &ref)
+  {
+    json::value *property_val = get_required_property (obj, ref);
+    return require_array_property (property_val, ref);
+  }
+
+  void emit_sarif_as_diagnostics (json::value *jv, int pass);
+  void handle_run_obj (json::object *run_obj);
+  json::object *
+  lookup_rule_by_id_in_tool (const char *rule_id,
+			     json::object *tool_obj);
+  json::object *
+  lookup_rule_by_id_in_component (const char *rule_id,
+				  json::object *tool_component_obj);
+  diagnostic_path *
+  make_sarif_diagnostic_path (json::object *thread_flow_obj);
+  void handle_result_obj (json::object *result_obj,
+			  json::object *tool_obj);
+
+  char *
+  make_plain_text_within_result_message (json::object *tool_component_obj,
+					 json::object *message_obj,
+					 json::object *rule_obj);
+  const char *
+  lookup_plain_text_within_result_message (json::object *tool_component_obj,
+					   json::object *message_obj,
+					   json::object *rule_obj);
+
+  location_t handle_location_object (json::object *location_obj,
+				     json::object **out_logical_location_obj);
+  location_t handle_physical_location_object (json::object *phys_loc_obj);
+  const char *handle_artifact_location_object (json::object *artifact_loc_obj);
+  void handle_region_object (json::object *region_obj,
+			     expanded_location *start_exp_loc,
+			     expanded_location *end_exp_loc);
+
+  tree get_or_create_fndecl (const char *func_str);
+  tree get_or_create_fndecl (json::object *logical_location_obj);
+
+  void fatal_error_with_ref (json::value *jv, const spec_ref &ref,
+			     const char *gmsgid, ...) ATTRIBUTE_GCC_DIAG(4,5)
+  {
+    location_t loc = m_json_loc_map.get_range_for_value (jv);
+
+    auto_diagnostic_group d;
+    va_list ap;
+    va_start (ap, gmsgid);
+    rich_location richloc (line_table, loc);
+    diagnostic_metadata metadata;
+    metadata.add_rule (ref);
+    emit_diagnostic_valist (DK_ERROR, &richloc, &metadata, 0, gmsgid, &ap);
+    va_end (ap);
+    exit (1);
+    // FIXME: ::fatal_error, but make it usable from DejaGnu
+  }
+
+private:
+  deferred_locations m_deferred_locs;
+
+  /* Map from physicalLocation object to pending_location_range ptr.
+     Populated in pass 0; used in pass 1.  */
+  hash_map<json::object *, pending_location_range *> m_phys_loc_map;
+  // TODO: delete this in dtor
+
+  int m_pass;
+
+  hash_map<tree, tree> m_map_id_to_fndecl;
+
+  json::object *m_driver_obj;
+  json::array *m_artifacts_arr;
+  json::object *m_current_logical_loc_obj;
+};
+
+/* class current_driver_version_info : public client_version_info.  */
+
+const char *
+current_driver_version_info::get_tool_name () const
+{
+  gcc_assert (m_replayer->m_driver_obj);
+
+  property_spec_ref name_prop ("toolComponent", "name", "3.19.8");
+  return m_replayer->get_optional_string_property
+    (m_replayer->m_driver_obj, name_prop);
+}
+
+char *
+current_driver_version_info::maybe_make_full_name () const
+{
+  gcc_assert (m_replayer->m_driver_obj);
+
+  property_spec_ref full_name_prop ("toolComponent", "fullName", "3.19.9");
+  if (const char *full_name = m_replayer->get_optional_string_property
+      (m_replayer->m_driver_obj, full_name_prop))
+    return xstrdup (full_name);
+  return NULL;
+}
+
+const char *
+current_driver_version_info::get_version_string () const
+{
+  gcc_assert (m_replayer->m_driver_obj);
+
+  property_spec_ref version_prop ("toolComponent", "version", "3.19.13");
+  return m_replayer->get_optional_string_property (m_replayer->m_driver_obj,
+						 version_prop);
+}
+
+char *
+current_driver_version_info::maybe_make_version_url () const
+{
+  gcc_assert (m_replayer->m_driver_obj);
+
+  property_spec_ref info_uri_prop ("toolComponent", "informationUri",
+				   "3.19.17");
+  if (const char *version_url = m_replayer->get_optional_string_property
+      (m_replayer->m_driver_obj, info_uri_prop))
+    return xstrdup (version_url);
+  return NULL;
+}
+
+void
+current_driver_version_info::for_each_plugin (plugin_visitor &visitor) const
+{
+  // TODO
+}
+
+/* class current_sarif_logical_location : public logical_location.  */
+
+const char *
+current_sarif_logical_location::get_short_name () const
+{
+  gcc_assert (m_replayer->m_current_logical_loc_obj);
+  property_spec_ref name_prop ("logicalLocation", "name", "3.33.4");
+  return m_replayer->get_optional_string_property
+    (m_replayer->m_current_logical_loc_obj, name_prop);
+}
+
+const char *
+current_sarif_logical_location::get_name_with_scope () const
+{
+  gcc_assert (m_replayer->m_current_logical_loc_obj);
+  property_spec_ref fqname_prop ("logicalLocation", "fullyQualifiedName",
+				 "3.33.5");
+  return m_replayer->get_optional_string_property
+    (m_replayer->m_current_logical_loc_obj, fqname_prop);
+}
+
+const char *
+current_sarif_logical_location::get_internal_name () const
+{
+  gcc_assert (m_replayer->m_current_logical_loc_obj);
+  property_spec_ref decorated_name_prop ("logicalLocation", "decoratedName",
+					 "3.33.6");
+  return m_replayer->get_optional_string_property
+    (m_replayer->m_current_logical_loc_obj, decorated_name_prop);
+}
+
+enum logical_location_kind
+current_sarif_logical_location::get_kind () const
+{
+  gcc_assert (m_replayer->m_current_logical_loc_obj);
+  property_spec_ref kind_prop ("logicalLocation", "kind", "3.33.7");
+  if (const char *kind = m_replayer->get_optional_string_property
+      (m_replayer->m_current_logical_loc_obj, kind_prop))
+    {
+      if (!strcmp (kind, "function"))
+	return LOGICAL_LOCATION_KIND_FUNCTION;
+      if (!strcmp (kind, "member"))
+	return LOGICAL_LOCATION_KIND_MEMBER;
+      if (!strcmp (kind, "module"))
+	return LOGICAL_LOCATION_KIND_MODULE;
+      if (!strcmp (kind, "namespace"))
+	return LOGICAL_LOCATION_KIND_NAMESPACE;
+      if (!strcmp (kind, "type"))
+	return LOGICAL_LOCATION_KIND_TYPE;
+      if (!strcmp (kind, "returnType"))
+	return LOGICAL_LOCATION_KIND_RETURN_TYPE;
+      if (!strcmp (kind, "parameter"))
+	return LOGICAL_LOCATION_KIND_PARAMETER;
+      if (!strcmp (kind, "variable"))
+	return LOGICAL_LOCATION_KIND_VARIABLE;
+      /* TODO: maybe consolidate this with maybe_get_sarif_kind
+	 in the producer code.  */
+    }
+
+  return LOGICAL_LOCATION_KIND_UNKNOWN;
+}
+
+/* class sarif_diagnostic_client_data_hooks.  */
+
+sarif_diagnostic_client_data_hooks::~sarif_diagnostic_client_data_hooks ()
+{
+  delete m_replayer;
+  delete m_toplevel_jv;
+}
+
+/* We only have a client_version_info * if m_replayer has found
+   a driver object.  */
+
+const client_version_info *
+sarif_diagnostic_client_data_hooks::get_any_version_info () const
+{
+  if (!m_replayer->m_driver_obj)
+    return NULL;
+  return &m_driver_version_info;
+}
+
+/* We only have a current logical_location if m_replayer has one.  */
+
+const logical_location *
+sarif_diagnostic_client_data_hooks::get_current_logical_location () const
+{
+  if (!m_replayer->m_current_logical_loc_obj)
+    return NULL;
+  return &m_current_logical_location;
+}
+
+const char *
+sarif_diagnostic_client_data_hooks::
+maybe_get_sarif_source_language (const char *filename) const
+{
+  if (!m_replayer->m_artifacts_arr)
+    return NULL;
+
+  for (auto iter : *m_replayer->m_artifacts_arr)
+    {
+      property_spec_ref artifacts_prop ("run", "artifacts", "3.14.15");
+      json::object *artifact_obj
+	= m_replayer->require_object_for_element (iter, artifacts_prop);
+
+      property_spec_ref location_prop ("artifact", "location", "3.24.2");
+      if (json::object *location_obj
+	    = m_replayer->get_optional_object_property (artifact_obj,
+						      location_prop))
+	{
+	  property_spec_ref uri_prop ("artifactLocation", "uri", "3.4.3");
+	  if (const char *uri
+	      = m_replayer->get_optional_string_property (location_obj,
+							uri_prop))
+	    if (!strcmp (uri, filename))
+	      {
+		property_spec_ref source_lang_prop
+		  ("artifact", "sourceLanguage", "3.24.10");
+		if (const char *source_language
+		    = m_replayer->get_optional_string_property (artifact_obj,
+							      source_lang_prop))
+		  return source_language;
+	      }
+	}
+    }
+
+  return NULL;
+}
+
+/* sarif_replayer's ctor.  */
+
+sarif_replayer::sarif_replayer (const char *filename)
+: json_reader (filename),
+  m_pass (0),
+  m_driver_obj (NULL),
+  m_artifacts_arr (NULL),
+  m_current_logical_loc_obj (NULL)
+{
+}
+
+/* sarif_replayer's dtor.  */
+
+sarif_replayer::~sarif_replayer ()
+{
+  for (auto iter : m_phys_loc_map)
+    delete iter.second;
+}
+
+/* Perform one pass of replay of the output file.
+   Pass 0 captures the source locations of interest, so that we can generate
+   line_maps.
+   Pass 1 uses the line_maps.  */
+
+void
+sarif_replayer::emit_sarif_as_diagnostics (json::value *jv, int pass)
+{
+  m_pass = pass;
+
+  /* We expect a sarifLog object as the top-level value
+     (SARIF v2.1.0 section 3.13).  */
+  json::object *toplev_obj = dyn_cast <json::object *> (jv);
+  if (!toplev_obj)
+    fatal_error_with_ref (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.  */
+  get_required_string_property (toplev_obj,
+				property_spec_ref ("sarifLog", "version",
+						   "3.13.2"));
+
+  /* sarifLog.runs must be null or be an array.  */
+  property_spec_ref prop_runs ("sarifLog", "runs", "3.13.4");
+  json::value *runs = get_required_property (toplev_obj, prop_runs);
+  switch (runs->get_kind ())
+    {
+    default:
+      fatal_error_with_ref (runs, prop_runs,
+			    "expected sarifLog.runs to be"
+			    " %<null%> or an array");
+      break;
+    case json::JSON_NULL:
+      /* Nothing to do.  */
+      return;
+    case json::JSON_ARRAY:
+      {
+	json::array *runs_arr = as_a <json::array *> (runs);
+	for (auto element : *runs_arr)
+	  {
+	    json::object *run_obj
+	      = require_object_for_element (element, prop_runs);
+	    handle_run_obj (run_obj);
+	  }
+      }
+      break;
+    }
+
+  if (m_pass == 0)
+    m_deferred_locs.generate_location_t_values ();
+}
+
+/* Process a run object (SARIF v2.1.0 section 3.14).  */
+
+void
+sarif_replayer::handle_run_obj (json::object *run_obj)
+{
+  json::object *tool_obj
+    = get_required_object_property (run_obj,
+				    property_spec_ref ("run", "tool",
+						       "3.14.6"));
+
+  m_driver_obj
+    = get_required_object_property (tool_obj,
+				    property_spec_ref ("tool", "driver",
+						       "3.18.2"));
+
+  m_artifacts_arr
+    = get_optional_array_property (run_obj,
+				   property_spec_ref ("run", "artifacts",
+						      "3.14.15"));
+
+  /* If present, run.results must be null or be an array.  */
+  property_spec_ref prop_results ("run", "results", "3.14.23");
+  json::value *results = run_obj->get ("results");
+  if (!results)
+    return;
+  switch (results->get_kind ())
+    {
+    default:
+      fatal_error_with_ref (results, prop_results,
+			    "expected run.results to be"
+			    " %<null%> or an array");
+      break;
+    case json::JSON_NULL:
+      /* Nothing to do.  */
+      return;
+    case json::JSON_ARRAY:
+      {
+	json::array *results_arr = as_a <json::array *> (results);
+	for (auto element : *results_arr)
+	  {
+	    json::object *result_obj
+	      = require_object_for_element (element, prop_results);
+	    handle_result_obj (result_obj, tool_obj);
+	  }
+      }
+      break;
+    }
+}
+
+/* Convert a SARIF result.level (3.27.10) to a GCC diagnostic kind.  */
+
+static diagnostic_t
+get_diagnostic_kind_from_result_level (const char *level)
+{
+  if (!strcmp (level, "warning"))
+    return DK_WARNING;
+  if (!strcmp (level, "error"))
+    return DK_ERROR;
+  if (!strcmp (level, "note"))
+    return DK_NOTE;
+  return DK_UNSPECIFIED; // FIXME: what if this happens (e.g. with "none")
+}
+
+/* Concrete subclass of diagnostic_metadata::rule for a specific SARIF
+   rule object.  */
+
+class sarif_rule : public diagnostic_metadata::rule
+{
+public:
+  sarif_rule (sarif_replayer *replayer, const char *rule_id,
+	      json::object *rule_obj)
+  : m_replayer (replayer),
+    m_rule_id (rule_id),
+    m_rule_obj (rule_obj)
+  {}
+
+  char *make_description () const final override
+  {
+    if (m_rule_id)
+      return xstrdup (m_rule_id);
+    else
+      return NULL;
+  }
+
+  char *make_url () const final override
+  {
+    if (m_rule_obj)
+      {
+	property_spec_ref prop_help_uri
+	  ("reportingDescriptor", "helpUri", "3.49.12");
+	if (const char *help_uri
+	    = m_replayer->get_optional_string_property (m_rule_obj,
+						      prop_help_uri))
+	  return xstrdup (help_uri);
+      }
+    return NULL;
+  }
+
+private:
+  sarif_replayer *m_replayer;
+  const char *m_rule_id;
+  json::object *m_rule_obj;
+};
+
+/*  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':
+	// FIXME: what about multiple leading zeroes?
+	iter_digit++;
+	continue;
+    }
+  return false; // TODO
+}
+
+/* Lookup the plain text string within a result.message (§3.27.11),
+   and substitute for any placeholders (§3.11.5).
+   TODO: embedded links?
+
+   MESSAGE_OBJ is "theMessage"
+   RULE_OBJ is "theRule".  */
+
+char *
+sarif_replayer::
+make_plain_text_within_result_message (json::object *tool_component_obj,
+				       json::object *message_obj,
+				       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 NULL;
+
+  /* Look up any arguments for substituting into placeholders.  */
+  property_spec_ref arguments_prop ("message", "arguments", "3.11.11");
+  json::array *arguments
+    = get_optional_array_property (message_obj, arguments_prop);
+
+  /* Duplicate original_text, substituting any placeholders.  */
+  pretty_printer pp;
+
+  const char *iter_src = original_text;
+  while (char ch = *iter_src)
+    {
+      unsigned arg_idx;
+      if (maybe_consume_placeholder (iter_src, &arg_idx))
+	{
+	  if (!arguments)
+	    fatal_error_with_ref (message_obj, arguments_prop,
+				  "message string contains placeholder %<{%i}%>"
+				  " but message object has no %qs property",
+				  (int)arg_idx,
+				  arguments_prop.get_property_name ());
+	  if (arg_idx >= arguments->length ())
+	    fatal_error_with_ref (message_obj, arguments_prop,
+				  "not enough strings in %qs array for"
+				  " placeholder %<{%i}%>",
+				  arguments_prop.get_property_name (),
+				  (int)arg_idx);
+	  const char *replacement_str
+	    = require_string (arguments->get (arg_idx), arguments_prop);
+	  pp_string (&pp, replacement_str);
+	}
+      else if (ch == '{' || ch == '}')
+	{
+	  /* '{' and '}' are escaped by repeating them.  */
+	  if (iter_src[1] == ch)
+	    {
+	      pp_character (&pp, ch);
+	      iter_src += 2;
+	    }
+	  else
+	    fatal_error_with_ref (message_obj, arguments_prop,
+				  "unescaped '%c' within message string", ch);
+	}
+      else
+	pp_character (&pp, *(iter_src++));
+    }
+
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+  /* IF theMessage.text is present and the desired language is theRun.language THEN
+       Use the text or markdown property of theMessage as appropriate.
+     IF the string has not yet been found THEN
+         IF theMessage occurs as the value of result.message (§3.27.11) THEN
+             LET theRule be the reportingDescriptor object (§3.49), an element of theComponent.rules (§3.19.23), which defines the rule that was violated by this result.
+             IF theRule exists AND theRule.messageStrings (§3.49.11) is present AND contains a property whose name equals theMessage.id THEN
+                 LET theMFMS be the multiformatMessageString object (§3.12) that is the value of that property.
+                 Use the text or markdown property of theMFMS as appropriate.
+         ELSE IF theMessage occurs as the value of notification.message (§3.58.5) THEN
+             LET theDescriptor be the reportingDescriptor object (§3.49), an element of theComponent.notifications (§3.19.23), which describes this notification.
+             IF theDescriptor exists AND theDescriptor.messageStrings is present AND contains a property whose name equals theMessage.id THEN
+                 LET theMFMS be the multiformatMessageString object that is the value of that property.
+                 Use the text or markdown property of theMFMS as appropriate.
+     IF the string has not yet been found THEN
+         IF theComponent.globalMessageStrings (§3.19.22) is present AND contains a property whose name equals theMessage.id THEN
+                 LET theMFMS be the multiformatMessageString object that is the value of that property.
+                 Use the text or markdown property of theMFMS as appropriate.
+     IF the string has not yet been found THEN
+         The lookup procedure fails (which means the SARIF log file is invalid).
+  */
+
+/* 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 (json::object *tool_component_obj,
+					 json::object *message_obj,
+					 json::object *rule_obj)
+{
+  gcc_assert (message_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 char *text
+	= get_optional_string_property (message_obj,
+					property_spec_ref ("message", "text",
+							   "3.11.8")))
+    // TODO: check language
+    return text;
+
+  const char *message_id
+    = get_optional_string_property (message_obj,
+				    property_spec_ref ("message", "id",
+						       "3.11.10"));
+
+  /*   LET theRule be the reportingDescriptor object (§3.49), an element of theComponent.rules (§3.19.23), which defines the rule that was violated by this result.
+       IF theRule exists AND theRule.messageStrings (§3.49.11) is present AND contains a property whose name equals theMessage.id THEN
+           LET theMFMS be the multiformatMessageString object (§3.12) that is the value of that property.
+           Use the text or markdown property of theMFMS as appropriate.
+  */
+       if (rule_obj && message_id)
+    {
+      property_spec_ref message_strings ("reportingDescriptor",
+					 "messageStrings",
+					 "3.49.11");
+      if (json::object *message_strings_obj
+	    = get_optional_object_property (rule_obj, message_strings))
+	{
+	  if (json::value *mfms = message_strings_obj->get (message_id))
+	    {
+	      json::object *mfms_obj = require_object (mfms, message_strings);
+
+	      // 3.12 multiformatMessageString object
+	      const char *text = get_required_string_property
+		(mfms_obj,
+		 property_spec_ref ("multiformatMessageString", "text",
+				    "3.12.3"));
+	      return text;
+	    }
+	}
+    }
+
+  // TODO:
+  /* IF the string has not yet been found THEN
+         IF theComponent.globalMessageStrings (§3.19.22) is present AND contains a property whose name equals theMessage.id THEN
+                 LET theMFMS be the multiformatMessageString object that is the value of that property.
+                 Use the text or markdown property of theMFMS as appropriate.
+  */
+
+  /* Failure.  */
+  fatal_error_with_ref (message_obj, spec_ref ("3.11.7"),
+			"could not find string for message object");
+  return NULL;
+}
+
+/* FIXME.  */
+// 3.52.3 reportingDescriptor lookup
+// "For an example of the interaction between ruleId and rule.id, see §3.52.4."
+
+json::object *
+sarif_replayer::lookup_rule_by_id_in_tool (const char *rule_id,
+					 json::object *tool_obj)
+{
+  if (!rule_id)
+    return NULL;
+  if (!tool_obj)
+    return NULL;
+
+  json::object *driver_obj
+    = get_required_object_property (tool_obj,
+				    property_spec_ref ("tool", "driver",
+						       "3.18.2"));
+
+  if (json::object *rule_obj
+	= lookup_rule_by_id_in_component (rule_id, driver_obj))
+    return rule_obj;
+
+  // TODO: also handle extensions
+
+  return NULL;
+}
+
+/* FIXME.  */
+
+json::object *
+sarif_replayer::lookup_rule_by_id_in_component (const char *rule_id,
+					      json::object *tool_component_obj)
+{
+  property_spec_ref rules ("toolComponent", "rules", "3.18.2");
+
+  json::array *rules_arr
+    = get_optional_array_property (tool_component_obj, rules);
+  if (!rules_arr)
+    return NULL;
+
+  for (auto element : *rules_arr)
+    {
+      json::object *reporting_desc_obj
+	= require_object_for_element (element, rules);
+
+      /* reportingDescriptor objects (§3.49).  */
+      property_spec_ref id ("reportingDescriptor", "id", "3.49.3");
+      const char *desc_id
+	= get_required_string_property (reporting_desc_obj, id);
+      if (!strcmp (rule_id, desc_id))
+	return reporting_desc_obj;
+    }
+
+  return NULL;
+}
+
+/* Ensure that we have a placeholder fndecl named FUNC_STR.
+   All such placeholder functions merely have signature
+     void FUNC_STR (void);  */
+
+tree
+sarif_replayer::get_or_create_fndecl (const char *func_str)
+{
+  tree id = get_identifier (func_str);
+  if (tree *slot = m_map_id_to_fndecl.get (id))
+    return *slot;
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fn_type = fntype_void_void;
+  tree fndecl = build_fn_decl (func_str, fn_type);
+  m_map_id_to_fndecl.put (id, fndecl);
+  return fndecl;
+}
+
+/* Attempt to get a placeholder fndecl for the given SARIF logicalLocation
+   object (3.33).
+
+   All such placeholder functions merely have signature
+     void FUNC_STR (void);  */
+
+tree
+sarif_replayer::get_or_create_fndecl (json::object *logical_location_obj)
+{
+  /* First try "name".  */
+  property_spec_ref prop_name ("logicalLocation", "name", "3.33.4");
+  if (const char *name
+	= get_optional_string_property (logical_location_obj, prop_name))
+    return get_or_create_fndecl (name);
+
+  /* Failing that, try "fullyQualifiedName".  */
+  property_spec_ref
+    prop_fqname ("logicalLocation", "fullyQualifiedName", "3.33.5");
+  if (const char *fqname
+	= get_optional_string_property (logical_location_obj, prop_fqname))
+    return get_or_create_fndecl (fqname);
+
+  /* Failure.  */
+  return NULL;
+}
+
+/* Concrete subclass of diagnostic_event for use when replaying diagnostics
+   from a SARIF file.
+
+   Corresponds to a threadFlowLocation object (§3.38).  */
+
+class sarif_replay_diagnostic_event : public diagnostic_event
+{
+public:
+  sarif_replay_diagnostic_event (sarif_replayer *replayer,
+				 json::object *tflow_loc_obj)
+  : m_replayer (replayer),
+    m_tflow_loc_obj (tflow_loc_obj),
+    m_logical_location_obj (NULL),
+    m_loc (UNKNOWN_LOCATION),
+    m_fndecl (NULL),
+    m_stack_depth (0),
+    m_message (NULL),
+    m_meaning ()
+  {
+    property_spec_ref location_prop ("threadFlowLocation", "location",
+				     "3.38.3");
+    if (json::object *location_obj
+	= replayer->get_optional_object_property (tflow_loc_obj, location_prop))
+      {
+	/* location object (§3.28).  */
+	m_loc = replayer->handle_location_object (location_obj,
+						  &m_logical_location_obj);
+	if (m_logical_location_obj)
+	  m_fndecl = replayer->get_or_create_fndecl (m_logical_location_obj);
+
+	/* Get any message from here.  */
+	property_spec_ref location_message ("location", "message", "3.28.5");
+	if (json::object *message_obj
+	    = replayer->get_optional_object_property (location_obj,
+						      location_message))
+	  m_message = replayer->make_plain_text_within_result_message
+	    (NULL,
+	     message_obj,
+	     NULL/* FIXME.  */);
+      }
+
+    // 3.38.8 kinds property
+    // TODO: populate m_meaning
+
+    /* nestingLevel property (§3.38.10).  */
+    property_spec_ref nesting_level ("threadFlowLocation", "nestingLevel",
+				     "3.38.10");
+    replayer->get_optional_int_property
+      (&m_stack_depth, tflow_loc_obj, nesting_level);
+    if (m_stack_depth < 0)
+      replayer->fatal_error_with_ref (tflow_loc_obj, nesting_level,
+				      "expected a non-negative integer");
+  }
+  ~sarif_replay_diagnostic_event ()
+  {
+    free (m_message);
+  }
+
+  location_t get_location () const final override { return m_loc; }
+  tree get_fndecl () const final override { return m_fndecl; }
+  int get_stack_depth () const final override { return m_stack_depth; }
+  label_text get_desc (bool) const final override
+  {
+    return label_text::borrow (m_message ? m_message : "");
+  }
+  const logical_location *get_logical_location () const final override
+  {
+    return NULL; // TODO
+  }
+  meaning get_meaning () const final override
+  {
+    return m_meaning;
+  }
+
+private:
+  sarif_replayer *m_replayer;
+  json::object *m_tflow_loc_obj;
+  json::object *m_logical_location_obj;
+  location_t m_loc;
+  tree m_fndecl;
+  int m_stack_depth;
+  char *m_message; // has been i18n-ed and formatted, or is NULL
+  meaning m_meaning;
+};
+
+/* Concrete subclass of diagnostic_path for use when replaying diagnostics
+   from a SARIF file.  */
+
+class sarif_replay_diagnostic_path : public diagnostic_path
+{
+public:
+  unsigned num_events () const final override { return m_events.length (); }
+  const diagnostic_event & get_event (int idx) const final override
+  {
+    return *m_events[idx];
+  }
+
+  void add_event (sarif_replay_diagnostic_event *event)
+  {
+    m_events.safe_push (event);
+  }
+
+private:
+  auto_delete_vec<sarif_replay_diagnostic_event> m_events;
+};
+
+/* Make a new diagnostic_path instance for THREAD_FLOW_OBJ, a
+   SARIF threadFlow object (section 3.37).  */
+
+diagnostic_path *
+sarif_replayer::make_sarif_diagnostic_path (json::object *thread_flow_obj)
+{
+  property_spec_ref locations ("threadFlow", "locations", "3.37.6");
+  json::array *locations_arr
+    = get_required_array_property (thread_flow_obj, locations);
+  sarif_replay_diagnostic_path *path = new sarif_replay_diagnostic_path ();
+  for (auto location : *locations_arr)
+    {
+      /* threadFlowLocation object (§3.38).  */
+      json::object *tflow_loc_obj
+	= require_object_for_element (location, locations);
+
+      sarif_replay_diagnostic_event *event
+	= new sarif_replay_diagnostic_event (this, tflow_loc_obj);
+      path->add_event (event);
+    }
+  return path;
+}
+
+/* TODO.  */
+// location object (§3.28)
+
+location_t
+sarif_replayer::handle_location_object (json::object *location_obj,
+				      json::object **out_logical_location_obj)
+{
+  location_t loc = UNKNOWN_LOCATION;
+
+  // §3.28.3 physicalLocation property
+  {
+    property_spec_ref physical_location_prop ("location", "physicalLocation",
+					      "3.28.3");
+    if (json::object *phys_loc_obj
+	= get_optional_object_property (location_obj, physical_location_prop))
+      loc = handle_physical_location_object (phys_loc_obj);
+  }
+
+  // §3.28.4 logicalLocations property
+  {
+    property_spec_ref logical_locations_prop ("location", "logicalLocations",
+					      "3.28.4");
+    if (json::array *logical_loc_arr
+	= get_optional_array_property (location_obj, logical_locations_prop))
+      if (logical_loc_arr->length () > 0)
+	{
+	  /* Only look at the first, if there's more than one.  */
+	  *out_logical_location_obj
+	    = require_object_for_element (logical_loc_arr->get (0),
+					  logical_locations_prop);
+	}
+  }
+
+  return loc;
+}
+
+/* TODO.  */
+// physicalLocation object (§3.29)
+
+location_t
+sarif_replayer::handle_physical_location_object (json::object *phys_loc_obj)
+{
+  expanded_location start_exp_loc = {NULL, 0, 0, NULL, false};
+  expanded_location end_exp_loc = {NULL, 0, 0, NULL, false};
+
+  const char *filename = NULL;
+  //3.29.3 artifactLocation property
+  property_spec_ref artifact_location_prop ("physicalLocation", "artifactLocation",
+					    "3.29.3");
+  if (json::object *artifact_loc_obj
+	= get_optional_object_property (phys_loc_obj, artifact_location_prop))
+    if (const char *filename
+	  = handle_artifact_location_object (artifact_loc_obj))
+      {
+	start_exp_loc.file = filename;
+	end_exp_loc.file = filename;
+      }
+
+  //3.29.4 region property
+  property_spec_ref region_prop ("physicalLocation", "region", "3.29.4");
+  if (json::object *region_obj
+	= get_optional_object_property (phys_loc_obj, region_prop))
+    handle_region_object (region_obj, &start_exp_loc, &end_exp_loc);
+
+  // FIXME: what about ranges???
+
+  // TODO:
+  //3.29.5 contextRegion property
+  //3.29.6 address property
+
+  if (m_pass == 0)
+    {
+      pending_location_range *pending_range = new pending_location_range ();
+      m_deferred_locs.add_location (start_exp_loc, &pending_range->m_start);
+      m_deferred_locs.add_location (end_exp_loc, &pending_range->m_end);
+      m_phys_loc_map.put (phys_loc_obj, pending_range);
+      return UNKNOWN_LOCATION;
+    }
+  else
+    {
+      pending_location_range *pending_range
+	= *m_phys_loc_map.get (phys_loc_obj);
+      return make_location (pending_range->m_start,
+			    pending_range->m_start,
+			    pending_range->m_end);
+    }
+}
+
+// TODO
+// artifactLocation object (§3.4)
+
+const char *
+sarif_replayer::handle_artifact_location_object (json::object *artifact_loc_obj)
+{
+  // TODO
+  // 3.4.3 uri property
+  property_spec_ref uri_prop ("artifactLocation", "uri", "3.4.3");
+  const char *uri = get_optional_string_property (artifact_loc_obj, uri_prop);
+  return uri;
+
+  // TODO
+  // 3.4.4 uriBaseId property
+  // 3.4.5 index property
+}
+
+// TODO
+// region object (§3.30)
+
+void
+sarif_replayer::handle_region_object (json::object *region_obj,
+				    expanded_location *start_exp_loc,
+				    expanded_location *end_exp_loc)
+{
+#if 0
+  if (pass == 0)
+    {
+      sarif::region_object *obj = new region_object (region_obj);
+    }
+#endif
+  // TODO
+  // TODO: 3.30.5 startLine property
+  property_spec_ref start_line_prop ("region", "startLine", "3.30.5");
+  int start_line;
+  if (get_optional_int_property (&start_line, region_obj, start_line_prop))
+    {
+      /* Text region defined by line/column properties.  */
+      start_exp_loc->line = start_line;
+      end_exp_loc->column = 1;
+
+      int start_column;
+      property_spec_ref start_column_prop ("region", "startColumn", "3.30.6");
+      if (get_optional_int_property (&start_column, region_obj,
+				     start_column_prop))
+	start_exp_loc->column = start_column;
+      else
+	start_exp_loc->column = 1;
+
+      int end_line;
+      property_spec_ref end_line_prop ("region", "endLine", "3.30.7");
+      if (get_optional_int_property (&end_line, region_obj, end_line_prop))
+	end_exp_loc->line = end_line;
+      else
+	end_exp_loc->line = start_line;
+
+      int end_column;
+      property_spec_ref end_column_prop ("region", "endColumn", "3.30.8");
+      if (get_optional_int_property (&end_column, 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_exp_loc->column = end_column - 1;
+	}
+      else
+	{
+	  // missing "endColumn" means the whole of the rest of the row
+	  // TODO
+	}
+    }
+}
+
+/* Process a result object (SARIF v2.1.0 section 3.27).  */
+
+void
+sarif_replayer::handle_result_obj (json::object *result_obj,
+				 json::object *tool_obj)
+{
+  const char *rule_id
+    = get_optional_string_property (result_obj,
+				    property_spec_ref ("result", "ruleId",
+						       "3.27.5"));
+  json::object *rule_obj = lookup_rule_by_id_in_tool (rule_id, tool_obj);
+
+  // 3.27.6 ruleIndex property
+
+  // TODO: 3.27.8 taxa property
+  if (json::array *taxa_arr
+	= get_optional_array_property (result_obj,
+				       property_spec_ref ("result", "taxa",
+							  "3.27.8")))
+    {
+      // TODO:
+    }
+
+  diagnostic_t diag_kind = DK_WARNING;
+  if (const char *level
+	= get_optional_string_property (result_obj,
+				    property_spec_ref ("result", "level",
+						       "3.27.10")))
+    diag_kind = get_diagnostic_kind_from_result_level (level);
+
+  // 3.27.11 message property
+  char *text = NULL;
+  json::object *message_obj
+    = get_optional_object_property (result_obj,
+				    property_spec_ref ("result", "message",
+						       "3.27.11"));
+  if (message_obj)
+    text = make_plain_text_within_result_message (NULL, // FIXME: tool_component_obj,
+						  message_obj,
+						  rule_obj);
+
+  // 3.27.12 locations property
+  json::object *logical_location_obj = NULL;
+  tree fndecl = NULL;
+  location_t loc = UNKNOWN_LOCATION;
+  property_spec_ref locations_prop ("result", "locations", "3.27.12");
+  json::array *locations_arr
+    = get_required_array_property (result_obj, locations_prop);
+  if (locations_arr->length () > 0)
+    {
+      /* Only look at the first, if there's more than one.  */
+      // location objects (§3.28)
+      json::object *location_obj
+	= require_object_for_element (locations_arr->get (0), locations_prop);
+      loc = handle_location_object (location_obj, &logical_location_obj);
+      if (logical_location_obj)
+	fndecl = get_or_create_fndecl (logical_location_obj);
+    }
+
+  // 3.27.18 codeFlows property
+  diagnostic_path *path = NULL;
+  property_spec_ref code_flows ("result", "codeFlows", "3.27.18");
+  if (json::array *code_flows_arr
+	= get_optional_array_property (result_obj, code_flows))
+    {
+      if (code_flows_arr->length () == 1)
+	{
+	  json::object *code_flow_obj
+	    = require_object_for_element (code_flows_arr->get (0), code_flows);
+
+	  property_spec_ref thread_flows ("result", "threadFlows", "3.36.3");
+	  if (json::array *thread_flows_arr
+		= get_optional_array_property (code_flow_obj, thread_flows))
+	    {
+	      if (thread_flows_arr->length () == 1)
+		{
+		  json::object *thread_flow_obj
+		    = require_object_for_element (thread_flows_arr->get (0),
+						  thread_flows);
+		  path = make_sarif_diagnostic_path (thread_flow_obj);
+		}
+	    }
+	}
+    }
+
+  // TODO: 3.27.22 relatedLocations property
+
+  // TODO: 3.27.30 fixes property
+
+  // TODO: use logical_location_obj, if non-NULL
+  //sarif_logical_location logical_loc (logical_location_obj);
+  //if (logical_location_obj)
+  m_current_logical_loc_obj = logical_location_obj;
+  current_function_decl = fndecl;
+  rich_location rich_loc (line_table, loc);
+  rich_loc.set_path (path);
+  sarif_rule rule (this, rule_id, rule_obj);
+  diagnostic_metadata metadata;
+  metadata.add_rule (rule);
+  auto_diagnostic_group d;
+  if (m_pass == 1)
+    if (emit_diagnostic (diag_kind, &rich_loc, &metadata, 0, "%s",
+			 text ? text : "FIXME"))
+      {
+	// TODO
+      }
+  free (text);
+  delete path;
+  current_function_decl = NULL;
+  m_current_logical_loc_obj = NULL;
+}
+
+/* Attempt to load a SARIF file from FILENAME and replay it.
+   Exit on any errors.  */
+
+void
+replay_sarif (const char *filename)
+{
+  sarif_replayer *p = new sarif_replayer (filename);
+  sarif_diagnostic_client_data_hooks *hooks
+    = new sarif_diagnostic_client_data_hooks (p);
+  global_dc->m_client_data_hooks = hooks;
+
+  char *content = read_file (filename);
+  json::error *err = NULL;
+  json::value *jv = p->parse_utf8_string (content, flag_allow_comments, &err);
+  if (err)
+    {
+      p->fatal_error (err);
+      delete err;
+    }
+  free (content);
+
+  if (jv)
+    {
+      for (int pass = 0; pass < 2; pass++)
+	p->emit_sarif_as_diagnostics (jv, pass);
+      hooks->stash (jv);
+    }
+
+  // global_dc's client_data takes ownership of "p"
+  // TODO: should it take ownership of jv?
+  // TODO: or should we clone jv?
+}
diff --git a/gcc/sarif/sarif-replay.h b/gcc/sarif/sarif-replay.h
new file mode 100644
index 00000000000..fb2aa79dee2
--- /dev/null
+++ b/gcc/sarif/sarif-replay.h
@@ -0,0 +1,26 @@
+/* Re-emitting diagnostics saved in SARIF form.
+   Copyright (C) 2022 David Malcolm <dmalcolm@redhat.com>.
+   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/>.  */
+
+#ifndef GCC_SARIF_SARIF_REPLAY_H
+#define GCC_SARIF_SARIF_REPLAY_H
+
+extern void replay_sarif (const char *filename);
+
+#endif /* GCC_SARIF_SARIF_H */
diff --git a/gcc/testsuite/lib/sarif-dg.exp b/gcc/testsuite/lib/sarif-dg.exp
new file mode 100644
index 00000000000..c82d7a131a1
--- /dev/null
+++ b/gcc/testsuite/lib/sarif-dg.exp
@@ -0,0 +1,233 @@
+#   Copyright (C) 2004-2022 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
+load_lib torture-options.exp
+
+#FIXME: copied from gfortran-dg.exp
+
+# Define sarif callbacks for dg.exp.
+
+proc sarif-dg-test { prog do_what extra_tool_flags } {
+    set result \
+	[gcc-dg-test-1 sarif_target_compile $prog $do_what $extra_tool_flags]
+    
+    set comp_output [lindex $result 0]
+    set output_file [lindex $result 1]
+
+    # gcc's default is to print the caret and source code, but
+    # most test cases implicitly use the flag -fno-diagnostics-show-caret
+    # to disable caret (and source code) printing.
+    #
+    # However, a few test cases override this back to the default by
+    # explicily supplying "-fdiagnostics-show-caret", so that we can have
+    # test coverage for caret/source code printing.
+    #
+    # sarif error messages with caret-printing look like this:
+    #     [name]:[locus]:
+    #
+    #        some code
+    #              1
+    #     Error: Some error at (1)
+    # or
+    #     [name]:[locus]:
+    #
+    #       some code
+    #              1
+    #     [name]:[locus2]:
+    #
+    #       some other code
+    #         2
+    #     Error: Some error at (1) and (2)
+    # or
+    #     [name]:[locus]:
+    #
+    #       some code and some more code
+    #              1       2
+    #     Error: Some error at (1) and (2)
+    #
+    # If this is such a test case, skip the rest of this function, so
+    # that the test case can explicitly verify the output that it expects.
+    if {[string first "-fdiagnostics-show-caret" $extra_tool_flags] >= 0} {
+	return [list $comp_output $output_file]
+    }
+
+    # Otherwise, caret-printing is disabled.
+    # sarif errors with caret-printing disabled look like this:
+    #     [name]:[locus]: Error: Some error
+    # or
+    #     [name]:[locus]: Error: (1)
+    #     [name]:[locus2]: Error: Some error at (1) and (2)
+    #
+    # Where [locus] is either [line] or [line].[column] or
+    # [line].[column]-[column] .
+    #
+    # We collapse these to look like:
+    #  [name]:[line]:[column]: Error: Some error at (1) and (2)
+    # or
+    #  [name]:[line]:[column]: Error: Some error at (1) and (2)
+    #  [name]:[line2]:[column]: Error: Some error at (1) and (2)
+    #
+    # Note that these regexps only make sense in the combinations used below.
+    # Note also that is imperative that we first deal with the form with
+    # two loci.
+    set locus_regexp "(\[^\n\]+:\[0-9\]+)\[\.:\](\[0-9\]+)(-\[0-9\]+)?:\n\n\[^\n\]+\n\[^\n\]+\n"
+    set diag_regexp "(\[^\n\]+)\n"
+
+    # We proceed in steps:
+
+    # 1. We add first a column number if none exists.
+    # (Some Fortran diagnostics have the locus after Warning|Error)
+    set colnum_regexp "(^|\n)(Warning: |Error: )?(\[^:\n\]+:\[0-9\]+):(\[ \n\])"
+    regsub -all $colnum_regexp $comp_output "\\1\\3:0:\\4\\2" comp_output
+    verbose "comput_output0:\n$comp_output"
+
+    # 2. We deal with the form with two different locus lines,
+    set two_loci "(^|\n)$locus_regexp$locus_regexp$diag_regexp"
+    regsub -all $two_loci $comp_output "\\1\\2:\\3: \\8\n\\5\:\\6: \\8\n" comp_output
+    verbose "comput_output1:\n$comp_output"
+
+    set locus_prefix "(\[^:\n\]+:\[0-9\]+:\[0-9\]+: )(Warning: |Error: )"
+    set two_loci2 "(^|\n)$locus_prefix\\(1\\)\n$locus_prefix$diag_regexp"
+    regsub -all $two_loci2 $comp_output "\\1\\2\\3\\6\n\\4\\5\\6\n" comp_output
+    verbose "comput_output2:\n$comp_output"
+
+    # 3. then with the form with only one locus line.
+    set single_locus "(^|\n)$locus_regexp$diag_regexp"
+    regsub -all $single_locus $comp_output "\\1\\2:\\3: \\5\n" comp_output
+    verbose "comput_output3:\n$comp_output"
+
+    # 4. Add a line number if none exists
+    regsub -all "(^|\n)(Warning: |Error: )" $comp_output "\\1:0:0: \\2" comp_output
+    verbose "comput_output4:\n$comp_output"
+    return [list $comp_output $output_file]
+}
+
+proc sarif-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-dg-runtest { testcases flags default-extra-flags } {
+    global runtests
+    global torture_with_loops
+
+    # Some callers set torture options themselves; don't override those.
+    set existing_torture_options [torture-options-exist]
+    if { $existing_torture_options == 0 } {
+	global DG_TORTURE_OPTIONS
+	torture-init
+	set-torture-options $DG_TORTURE_OPTIONS
+    }
+    dump-torture-options
+
+    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
+        }
+
+	# look if this is dg-do-run test, in which case
+	# we cycle through the option list, otherwise we don't
+	if [expr [search_for $test "dg-do run"]] {
+	    set option_list $torture_with_loops
+	} else {
+	    set option_list [list { -O } ]
+	}
+
+	set nshort [file tail [file dirname $test]]/[file tail $test]
+	list-module-names $test
+
+	foreach flags_t $option_list {
+	    verbose "Testing $nshort, $flags $flags_t" 1
+	    dg-test $test "$flags $flags_t" ${default-extra-flags}
+	    cleanup-modules ""
+	}
+    }
+
+    if { $existing_torture_options == 0 } {
+	torture-finish
+    }
+}
+
+proc sarif-dg-debug-runtest { target_compile trivial opt_opts testcases } {
+    global srcdir subdir DEBUG_TORTURE_OPTIONS
+
+    if ![info exists DEBUG_TORTURE_OPTIONS] {
+       set DEBUG_TORTURE_OPTIONS ""
+       set type_list [list "-gstabs" "-gstabs+" "-gxcoff" "-gxcoff+" "-gdwarf-2" ]
+       foreach type $type_list {
+           set comp_output [$target_compile \
+                   "$srcdir/$subdir/$trivial" "trivial.S" assembly \
+                   "additional_flags=$type"]
+           if { [string match "exit status *" $comp_output] } {
+               continue
+           }
+           if { [string match \
+                       "* target system does not support the * debug format*" \
+                       $comp_output]
+           } {
+               continue
+           }
+           remove-build-file "trivial.S"
+           foreach level {1 "" 3} {
+	       if { ($type == "-gdwarf-2") && ($level != "") } {
+		   lappend DEBUG_TORTURE_OPTIONS [list "${type}" "-g${level}"]
+		   foreach opt $opt_opts {
+		       lappend DEBUG_TORTURE_OPTIONS \
+			       [list "${type}" "-g${level}" "$opt" ]
+		   }
+	       } else {
+		   lappend DEBUG_TORTURE_OPTIONS [list "${type}${level}"]
+		   foreach opt $opt_opts {
+		       lappend DEBUG_TORTURE_OPTIONS \
+			       [list "${type}${level}" "$opt" ]
+		   }
+               }
+           }
+       }
+    }
+
+    verbose -log "Using options $DEBUG_TORTURE_OPTIONS"
+
+    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
+       }
+
+       set nshort [file tail [file dirname $test]]/[file tail $test]
+	list-module-names $test
+
+       foreach flags $DEBUG_TORTURE_OPTIONS {
+           set doit 1
+           # gcc-specific checking removed here
+
+           if { $doit } {
+               verbose -log "Testing $nshort, $flags" 1
+               dg-test $test $flags ""
+		cleanup-modules ""
+           }
+       }
+    }
+}
diff --git a/gcc/testsuite/lib/sarif.exp b/gcc/testsuite/lib/sarif.exp
new file mode 100644
index 00000000000..925d2787017
--- /dev/null
+++ b/gcc/testsuite/lib/sarif.exp
@@ -0,0 +1,36 @@
+# Copyright (C) 2003-2022 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/>.
+
+# FIXME: copied from gfortran.exp
+
+# This file is just 'sed -e 's/77/fortran/g' \
+#			 -e 's/f2c/gfortran' g77.exp > gfortran.exp'
+#
+# with some minor modifications to make it work.
+
+#
+# sarif support library routines
+#
+load_lib prune.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib target-supports.exp
+
+proc sarif_target_compile { source dest type options } {
+    set return_val [target_compile $source $dest $type $options]
+    return $return_val;
+}
diff --git a/gcc/testsuite/sarif/bad-eval-with-code-flow.py b/gcc/testsuite/sarif/bad-eval-with-code-flow.py
new file mode 100644
index 00000000000..e72d8de48a5
--- /dev/null
+++ b/gcc/testsuite/sarif/bad-eval-with-code-flow.py
@@ -0,0 +1,10 @@
+# Taken from https://github.com/microsoft/sarif-tutorials
+# samples/3-Beyond-basics/bad-eval-with-code-flow.py
+# which is licensed under MIT License.
+
+print("Hello, world!")
+expr = input("Expression> ")
+use_input(expr)
+
+def use_input(raw_input):
+    print(eval(raw_input))
diff --git a/gcc/testsuite/sarif/escaped-braces.sarif b/gcc/testsuite/sarif/escaped-braces.sarif
new file mode 100644
index 00000000000..8374a94b835
--- /dev/null
+++ b/gcc/testsuite/sarif/escaped-braces.sarif
@@ -0,0 +1,19 @@
+{
+  "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 "" }
+sarif-replay: warning: before open '{' after open
+sarif-replay: warning: before close '}' after close
+   { dg-end-multiline-output "" }  */
+
+// TODO: lose the "sarif-replay: " prefixes
diff --git a/gcc/testsuite/sarif/invalid-json-array-missing-comma.sarif b/gcc/testsuite/sarif/invalid-json-array-missing-comma.sarif
new file mode 100644
index 00000000000..0f32d38420e
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-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/invalid-json-array-with-trailing-comma.sarif b/gcc/testsuite/sarif/invalid-json-array-with-trailing-comma.sarif
new file mode 100644
index 00000000000..05b74a81efc
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-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/invalid-json-bad-token.sarif b/gcc/testsuite/sarif/invalid-json-bad-token.sarif
new file mode 100644
index 00000000000..7756eef1add
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-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/invalid-json-object-missing-comma.sarif b/gcc/testsuite/sarif/invalid-json-object-missing-comma.sarif
new file mode 100644
index 00000000000..9d2bf9476b1
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-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/invalid-json-object-with-trailing-comma.sarif b/gcc/testsuite/sarif/invalid-json-object-with-trailing-comma.sarif
new file mode 100644
index 00000000000..e1aae9b350c
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-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 "" }
diff --git a/gcc/testsuite/sarif/invalid-sarif-bad-runs.sarif b/gcc/testsuite/sarif/invalid-sarif-bad-runs.sarif
new file mode 100644
index 00000000000..c5a26f05516
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-missing-arguments-for-placeholders.sarif b/gcc/testsuite/sarif/invalid-sarif-missing-arguments-for-placeholders.sarif
new file mode 100644
index 00000000000..c4354d4ef23
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-no-runs.sarif b/gcc/testsuite/sarif/invalid-sarif-no-runs.sarif
new file mode 100644
index 00000000000..f142321642c
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-no-version.sarif b/gcc/testsuite/sarif/invalid-sarif-no-version.sarif
new file mode 100644
index 00000000000..771bd9c0c05
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-non-object-in-runs.sarif b/gcc/testsuite/sarif/invalid-sarif-non-object-in-runs.sarif
new file mode 100644
index 00000000000..4eeaaaa7b24
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-not-an-object.sarif b/gcc/testsuite/sarif/invalid-sarif-not-an-object.sarif
new file mode 100644
index 00000000000..4743bad3ba3
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-not-enough-arguments-for-placeholders.sarif b/gcc/testsuite/sarif/invalid-sarif-not-enough-arguments-for-placeholders.sarif
new file mode 100644
index 00000000000..e3eb5341110
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-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/invalid-sarif-version-not-a-string.sarif b/gcc/testsuite/sarif/invalid-sarif-version-not-a-string.sarif
new file mode 100644
index 00000000000..0ffeb13626e
--- /dev/null
+++ b/gcc/testsuite/sarif/invalid-sarif-version-not-a-string.sarif
@@ -0,0 +1,6 @@
+{ "version" : 42 } // { dg-error "expected sarifLog.version to be a string \\\[SARIF v2.1.0 §3.13.2\\\]" }
+
+/* { dg-begin-multiline-output "" }
+    1 | { "version" : 42 }
+      |               ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif/malformed-placeholder.sarif b/gcc/testsuite/sarif/malformed-placeholder.sarif
new file mode 100644
index 00000000000..72da185de0d
--- /dev/null
+++ b/gcc/testsuite/sarif/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/null-runs.sarif b/gcc/testsuite/sarif/null-runs.sarif
new file mode 100644
index 00000000000..5fc630eecb3
--- /dev/null
+++ b/gcc/testsuite/sarif/null-runs.sarif
@@ -0,0 +1,2 @@
+{ "version": "2.1.0",
+  "runs": null }
diff --git a/gcc/testsuite/sarif/roundtrip-signal-1.c.sarif b/gcc/testsuite/sarif/roundtrip-signal-1.c.sarif
new file mode 100644
index 00000000000..86f85383b89
--- /dev/null
+++ b/gcc/testsuite/sarif/roundtrip-signal-1.c.sarif
@@ -0,0 +1,398 @@
+/* { dg-options "-fdiagnostics-format=sarif-file -fallow-comments" } */
+
+{
+    "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+    "runs": [
+        {
+            "results": [
+                {
+                    "level": "warning",
+                    "ruleId": "-Wanalyzer-unsafe-call-within-signal-handler",
+                    "locations": [
+                        {
+                            "logicalLocations": [
+                                {
+                                    "decoratedName": "custom_logger",
+                                    "kind": "function",
+                                    "name": "custom_logger",
+                                    "fullyQualifiedName": "custom_logger"
+                                }
+                            ],
+                            "physicalLocation": {
+                                "contextRegion": {
+                                    "startLine": 13,
+                                    "snippet": {
+                                        "text": "  fprintf(stderr, \"LOG: %s\", msg);"
+                                    }
+                                },
+                                "artifactLocation": {
+                                    "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                    "uriBaseId": "PWD"
+                                },
+                                "region": {
+                                    "startLine": 13,
+                                    "endColumn": 34,
+                                    "startColumn": 3
+                                }
+                            }
+                        }
+                    ],
+                    "message": {
+                        "text": "call to \u2018fprintf\u2019 from within signal handler"
+                    },
+                    "taxa": [
+                        {
+                            "id": "479",
+                            "toolComponent": {
+                                "name": "cwe"
+                            }
+                        }
+                    ],
+                    "codeFlows": [
+                        {
+                            "threadFlows": [
+                                {
+                                    "locations": [
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "main",
+                                                        "kind": "function",
+                                                        "name": "main",
+                                                        "fullyQualifiedName": "main"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018main\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 21,
+                                                        "snippet": {
+                                                            "text": "int main(int argc, const char *argv)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 21,
+                                                        "endColumn": 9,
+                                                        "startColumn": 5
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "main",
+                                                        "kind": "function",
+                                                        "name": "main",
+                                                        "fullyQualifiedName": "main"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "registering \u2018handler\u2019 as signal handler"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 25,
+                                                        "snippet": {
+                                                            "text": "  signal(SIGINT, handler);\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 25,
+                                                        "endColumn": 26,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            }
+                                        },
+                                        {
+                                            "nestingLevel": 0,
+                                            "location": {
+                                                "message": {
+                                                    "text": "later on, when the signal is delivered to the process"
+                                                }
+                                            }
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "handler",
+                                                        "kind": "function",
+                                                        "name": "handler",
+                                                        "fullyQualifiedName": "handler"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018handler\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 16,
+                                                        "snippet": {
+                                                            "text": "static void handler(int signum)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 16,
+                                                        "endColumn": 20,
+                                                        "startColumn": 13
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "handler",
+                                                        "kind": "function",
+                                                        "name": "handler",
+                                                        "fullyQualifiedName": "handler"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "calling \u2018custom_logger\u2019 from \u2018handler\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 18,
+                                                        "snippet": {
+                                                            "text": "  custom_logger(\"got signal\");\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 18,
+                                                        "endColumn": 30,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "call",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 2,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "custom_logger",
+                                                        "kind": "function",
+                                                        "name": "custom_logger",
+                                                        "fullyQualifiedName": "custom_logger"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018custom_logger\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 11,
+                                                        "snippet": {
+                                                            "text": "void custom_logger(const char *msg)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 11,
+                                                        "endColumn": 19,
+                                                        "startColumn": 6
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 2,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "custom_logger",
+                                                        "kind": "function",
+                                                        "name": "custom_logger",
+                                                        "fullyQualifiedName": "custom_logger"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "call to \u2018fprintf\u2019 from within signal handler"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 13,
+                                                        "snippet": {
+                                                            "text": "  fprintf(stderr, \"LOG: %s\", msg);\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 13,
+                                                        "endColumn": 34,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "danger"
+                                            ]
+                                        }
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            ],
+            "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"
+                    }
+                }
+            ],
+            "tool": {
+                "driver": {
+                    "fullName": "placeholder value for driver.fullName",
+                    "name": "GNU C17",
+                    "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"
+                        }
+                    ],
+                    "informationUri": "https://gcc.gnu.org/gcc-13/",
+                    "version": "placeholder value for driver.version"
+                }
+            },
+            "originalUriBaseIds": {
+                "PWD": {
+                    "uri": "file:///home/david/coding/gcc-newgit-serialization/build/gcc/"
+                }
+            },
+            "taxonomies": [
+                {
+                    "organization": "MITRE",
+                    "name": "CWE",
+                    "version": "4.7",
+                    "shortDescription": {
+                        "text": "The MITRE Common Weakness Enumeration"
+                    },
+                    "taxa": [
+                        {
+                            "id": "479",
+                            "helpUri": "https://cwe.mitre.org/data/definitions/479.html"
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "version": "2.1.0"
+}
+
+/* Verify that some JSON was written to a file with the expected name;
+   ideally we want as much of the data as possible to survive the round trip.
+
+   The indentation here reflects the expected hierarchy, though these tests
+   don't check for that, merely the string fragments we expect.
+
+   { dg-final { scan-sarif-file "\"version\": \"2.1.0\"" } }
+   { dg-final { scan-sarif-file "\"runs\": \\\[" } }
+     { dg-final { scan-sarif-file "\"artifacts\": \\\[" } } 
+       { dg-final { scan-sarif-file "\"location\": " } }
+         { dg-final { scan-sarif-file "\"uri\": " } }
+
+       { dg-final { scan-sarif-file "\"sourceLanguage\": \"c\"" } }
+
+       { dg-final { scan-sarif-file "\"contents\": " { xfail *-*-* } } }
+         { dg-final { scan-sarif-file "\"text\": " } }
+     { dg-final { scan-sarif-file "\"tool\": " } }
+       { dg-final { scan-sarif-file "\"driver\": " } }
+         { dg-final { scan-sarif-file "\"name\": \"GNU C17\"" } }
+         { dg-final { scan-sarif-file "\"fullName\": \"placeholder value for driver.fullName\"" } }
+         { dg-final { scan-sarif-file "\"informationUri\": \"https://gcc.gnu.org/gcc-13/\"" } }
+         { dg-final { scan-sarif-file "\"version\": \"placeholder value for driver.version\"" } }
+     { dg-final { scan-sarif-file "\"results\": \\\[" } }
+       { dg-final { scan-sarif-file "\"level\": \"warning\"" } }
+       { dg-final { scan-sarif-file "\"ruleId\": \"-Wanalyzer-unsafe-call-within-signal-handler\"" } }
+       { dg-final { scan-sarif-file "\"locations\": \\\[" } }
+         { dg-final { scan-sarif-file "\"physicalLocation\": " } }
+           { dg-final { scan-sarif-file "\"contextRegion\": " } }
+           { dg-final { scan-sarif-file "\"artifactLocation\": " } }
+           { dg-final { scan-sarif-file "\"region\": " } }
+             { dg-final { scan-sarif-file "\"startLine\": 13" } }
+             { dg-final { scan-sarif-file "\"startColumn\": 3" } }
+             { dg-final { scan-sarif-file "\"endColumn\": 34" } }
+
+         { dg-final { scan-sarif-file "\"logicalLocations\": " } }
+           { dg-final { scan-sarif-file "\"decoratedName\": \"custom_logger\"" } }
+           { dg-final { scan-sarif-file "\"kind\": \"function\"" } }
+           { dg-final { scan-sarif-file "\"name\": \"custom_logger\"" } }
+           { dg-final { scan-sarif-file "\"fullyQualifiedName\": \"custom_logger\"" } }
+
+       { dg-final { scan-sarif-file "\"message\": " } }
+         { dg-final { scan-sarif-file "\"text\": \"call to \\u2018fprintf\\u2019 from within signal handler\"" } }
+
+       { dg-final { scan-sarif-file "\"codeFlows\": \\\[" } }
+         { dg-final { scan-sarif-file "\"threadFlows\": \\\[" } }
+           { dg-final { scan-sarif-file "\"nestingLevel\": 1" } }
+           { dg-final { scan-sarif-file "\"kinds\": \\\[\"enter\", \"function\"\\\]" { xfail *-*-* } } }
+	   
+           { dg-final { scan-sarif-file "\"nestingLevel\": 2" } }
+           { dg-final { scan-sarif-file "\"kinds\": \\\[\"danger\"\\\]" { xfail *-*-* } } }
+
+*/
+
+// TODO: fix the xfails
+// TODO: verify logical locations within the path
+// TODO: verify physical locations within the path
+// TODO: verify taxa
+// TODO: verify message text
+// etc
diff --git a/gcc/testsuite/sarif/sarif.exp b/gcc/testsuite/sarif/sarif.exp
new file mode 100644
index 00000000000..dcb1eb2bd58
--- /dev/null
+++ b/gcc/testsuite/sarif/sarif.exp
@@ -0,0 +1,50 @@
+#   Copyright (C) 2004-2022 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/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib sarif-dg.exp
+
+#load_lib dg.exp
+#load_lib prune.exp
+#load_lib target-supports.exp
+#load_lib gcc-defs.exp
+#load_lib timeout.exp
+#load_lib target-libpath.exp
+#load_lib gcc.exp
+#load_lib g++.exp
+#load_lib dejagnu.exp
+#load_lib prune.exp
+#load_lib gcc-defs.exp
+#load_lib timeout.exp
+#load_lib target-libpath.exp
+#load_lib target-supports.exp
+#load_lib gcc-dg.exp
+
+# If a testcase doesn't have special options, use these.
+global DEFAULT_SARIF_FLAGS
+if ![info exists DEFAULT_SARIF_FLAGS] then {
+    set DEFAULT_SARIF_FLAGS " -fallow-comments"
+}
+# Initialize `dg'.
+dg-init
+
+dg-runtest [lsort \
+       [glob -nocomplain $srcdir/$subdir/*.sarif ] ] "" $DEFAULT_SARIF_FLAGS
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/sarif/signal-1.c.sarif b/gcc/testsuite/sarif/signal-1.c.sarif
new file mode 100644
index 00000000000..a87ebb261d9
--- /dev/null
+++ b/gcc/testsuite/sarif/signal-1.c.sarif
@@ -0,0 +1,362 @@
+{
+    "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+    "runs": [
+        {
+            "results": [
+                {
+                    "level": "warning",
+                    "ruleId": "-Wanalyzer-unsafe-call-within-signal-handler",
+                    "locations": [
+                        {
+                            "logicalLocations": [
+                                {
+                                    "decoratedName": "custom_logger",
+                                    "kind": "function",
+                                    "name": "custom_logger",
+                                    "fullyQualifiedName": "custom_logger"
+                                }
+                            ],
+                            "physicalLocation": {
+                                "contextRegion": {
+                                    "startLine": 13,
+                                    "snippet": {
+                                        "text": "  fprintf(stderr, \"LOG: %s\", msg);"
+                                    }
+                                },
+                                "artifactLocation": {
+                                    "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                    "uriBaseId": "PWD"
+                                },
+                                "region": {
+                                    "startLine": 13,
+                                    "endColumn": 34,
+                                    "startColumn": 3
+                                }
+                            }
+                        }
+                    ],
+                    "message": {
+                        "text": "call to \u2018fprintf\u2019 from within signal handler"
+                    },
+                    "taxa": [
+                        {
+                            "id": "479",
+                            "toolComponent": {
+                                "name": "cwe"
+                            }
+                        }
+                    ],
+                    "codeFlows": [
+                        {
+                            "threadFlows": [
+                                {
+                                    "locations": [
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "main",
+                                                        "kind": "function",
+                                                        "name": "main",
+                                                        "fullyQualifiedName": "main"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018main\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 21,
+                                                        "snippet": {
+                                                            "text": "int main(int argc, const char *argv)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 21,
+                                                        "endColumn": 9,
+                                                        "startColumn": 5
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "main",
+                                                        "kind": "function",
+                                                        "name": "main",
+                                                        "fullyQualifiedName": "main"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "registering \u2018handler\u2019 as signal handler"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 25,
+                                                        "snippet": {
+                                                            "text": "  signal(SIGINT, handler);\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 25,
+                                                        "endColumn": 26,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            }
+                                        },
+                                        {
+                                            "nestingLevel": 0,
+                                            "location": {
+                                                "message": {
+                                                    "text": "later on, when the signal is delivered to the process"
+                                                }
+                                            }
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "handler",
+                                                        "kind": "function",
+                                                        "name": "handler",
+                                                        "fullyQualifiedName": "handler"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018handler\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 16,
+                                                        "snippet": {
+                                                            "text": "static void handler(int signum)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 16,
+                                                        "endColumn": 20,
+                                                        "startColumn": 13
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 1,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "handler",
+                                                        "kind": "function",
+                                                        "name": "handler",
+                                                        "fullyQualifiedName": "handler"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "calling \u2018custom_logger\u2019 from \u2018handler\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 18,
+                                                        "snippet": {
+                                                            "text": "  custom_logger(\"got signal\");\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 18,
+                                                        "endColumn": 30,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "call",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 2,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "custom_logger",
+                                                        "kind": "function",
+                                                        "name": "custom_logger",
+                                                        "fullyQualifiedName": "custom_logger"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "entry to \u2018custom_logger\u2019"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 11,
+                                                        "snippet": {
+                                                            "text": "void custom_logger(const char *msg)\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 11,
+                                                        "endColumn": 19,
+                                                        "startColumn": 6
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "enter",
+                                                "function"
+                                            ]
+                                        },
+                                        {
+                                            "nestingLevel": 2,
+                                            "location": {
+                                                "logicalLocations": [
+                                                    {
+                                                        "decoratedName": "custom_logger",
+                                                        "kind": "function",
+                                                        "name": "custom_logger",
+                                                        "fullyQualifiedName": "custom_logger"
+                                                    }
+                                                ],
+                                                "message": {
+                                                    "text": "call to \u2018fprintf\u2019 from within signal handler"
+                                                },
+                                                "physicalLocation": {
+                                                    "contextRegion": {
+                                                        "startLine": 13,
+                                                        "snippet": {
+                                                            "text": "  fprintf(stderr, \"LOG: %s\", msg);\n"
+                                                        }
+                                                    },
+                                                    "artifactLocation": {
+                                                        "uri": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                                                        "uriBaseId": "PWD"
+                                                    },
+                                                    "region": {
+                                                        "startLine": 13,
+                                                        "endColumn": 34,
+                                                        "startColumn": 3
+                                                    }
+                                                }
+                                            },
+                                            "kinds": [
+                                                "danger"
+                                            ]
+                                        }
+                                    ]
+                                }
+                            ]
+                        }
+                    ]
+                }
+            ],
+            "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"
+                    }
+                }
+            ],
+            "tool": {
+                "driver": {
+                    "fullName": "some full name goes here",
+                    "name": "GNU C17",
+                    "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"
+                        }
+                    ],
+                    "informationUri": "https://gcc.gnu.org/gcc-13/",
+                    "version": "13.0.0 20220601"
+                }
+            },
+            "originalUriBaseIds": {
+                "PWD": {
+                    "uri": "file:///home/david/coding/gcc-newgit-serialization/build/gcc/"
+                }
+            },
+            "taxonomies": [
+                {
+                    "organization": "MITRE",
+                    "name": "CWE",
+                    "version": "4.7",
+                    "shortDescription": {
+                        "text": "The MITRE Common Weakness Enumeration"
+                    },
+                    "taxa": [
+                        {
+                            "id": "479",
+                            "helpUri": "https://cwe.mitre.org/data/definitions/479.html"
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "version": "2.1.0"
+}
+
+/* { dg-begin-multiline-output "" }
+../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c: In function 'custom_logger':
+../../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': events 1-2
+    |
+    |......
+    |
+  event 3
+    |
+    |sarif-replay:
+    | (3): later on, when the signal is delivered to the process
+    |
+    +--> 'handler': events 4-5
+           |
+           |
+           +--> 'custom_logger': events 6-7
+                  |
+                  |
+   { dg-end-multiline-output "" } */
+
+// TODO: fixup the src location
+// TODO: quote the source code
+// TODO: CWE
+// TODO: event messages
+// TODO: etc
diff --git a/gcc/testsuite/sarif/spec-example-1.sarif b/gcc/testsuite/sarif/spec-example-1.sarif
new file mode 100644
index 00000000000..97f409f4aa4
--- /dev/null
+++ b/gcc/testsuite/sarif/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/spec-example-2.sarif b/gcc/testsuite/sarif/spec-example-2.sarif
new file mode 100644
index 00000000000..352622dd5a5
--- /dev/null
+++ b/gcc/testsuite/sarif/spec-example-2.sarif
@@ -0,0 +1,74 @@
+/* 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 "" }
+src/collections/list.cpp:15:1: warning: Variable "count" was used without being initialized. [C2001]
+   { dg-end-multiline-output "" } */
+
+// TODO: logical location
diff --git a/gcc/testsuite/sarif/spec-example-3.sarif b/gcc/testsuite/sarif/spec-example-3.sarif
new file mode 100644
index 00000000000..0a0018928e1
--- /dev/null
+++ b/gcc/testsuite/sarif/spec-example-3.sarif
@@ -0,0 +1,67 @@
+/* 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':
+sarif-replay: warning: The insecure method "Crypto.Sha1.Encrypt" should not be used. [B6412]
+   { dg-end-multiline-output "" } */
+
+// TODO: the "sarif-replay: " prefix is unhelpful
diff --git a/gcc/testsuite/sarif/spec-example-4.sarif b/gcc/testsuite/sarif/spec-example-4.sarif
new file mode 100644
index 00000000000..6680d270905
--- /dev/null
+++ b/gcc/testsuite/sarif/spec-example-4.sarif
@@ -0,0 +1,758 @@
+/* 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 "" }
+collections/list.h: In function 'collections::list::add':
+collections/list.h:15:9: error: Variable "ptr" was used without being initialized. It was declared [here](0). [C2001]
+  'collections::list::add': events 1-3
+    |
+    |......
+    |
+   { dg-end-multiline-output "" } */
+
+// TODO: what's up with the events?
diff --git a/gcc/testsuite/sarif/tutorial-example-foo.sarif b/gcc/testsuite/sarif/tutorial-example-foo.sarif
new file mode 100644
index 00000000000..8fa37ad4b42
--- /dev/null
+++ b/gcc/testsuite/sarif/tutorial-example-foo.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": 8
+                }
+              }
+            }
+          ],
+          "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": 3
+                          }
+                        }
+                      },
+                      "state": {
+                        "expr": {
+                          "text": "undef"
+                        }
+                      },
+                      "nestingLevel": 0
+                    },
+                    {
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri": "bad-eval-with-code-flow.py"
+                          },
+                          "region": {
+                            "startLine": 4
+                          }
+                        }
+                      },
+                      "state": {
+                        "expr": {
+                          "text": "42"
+                        }
+                      },
+                      "nestingLevel": 0
+                    },
+                    {
+                      "location": {
+                        "physicalLocation": {
+                          "artifactLocation": {
+                            "uri": "bad-eval-with-code-flow.py"
+                          },
+                          "region": {
+                            "startLine": 38
+                          }
+                        }
+                      },
+                      "state": {
+                        "raw_input": {
+                          "text": "42"
+                        }
+                      },
+                      "nestingLevel": 1
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+/* { dg-begin-multiline-output "" }
+bad-eval-with-code-flow.py:8:1: warning: Use of tainted variable 'raw_input' in the insecure function 'eval'. [PY2335]
+  events 1-2
+    |
+    |
+    +--> event 3
+           |
+           |
+   { dg-end-multiline-output "" } */
+
+// TODO: logical locations?
+// TODO: fix showing the source code
-- 
2.26.3


  parent reply	other threads:[~2022-06-22 22:34 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-22 22:34 [PATCH 00/12] RFC: Replay of serialized diagnostics David Malcolm
2022-06-22 22:34 ` [PATCH 01/12] diagnostics: add ability to associate diagnostics with rules from coding standards David Malcolm
2022-06-23 19:04   ` David Malcolm
2022-06-22 22:34 ` [PATCH 02/12] diagnostics: associate rules with plugins in SARIF output David Malcolm
2022-06-22 22:34 ` [PATCH 03/12] Add more emit_diagnostic overloads David Malcolm
2022-06-23 16:39   ` Joseph Myers
2022-06-22 22:34 ` [PATCH 04/12] json: add json parsing support David Malcolm
2022-06-22 22:34 ` [PATCH 05/12] Placeholder libcpp fixups David Malcolm
2022-06-22 22:34 ` [PATCH 06/12] prune.exp: move multiline-handling to before other pruning David Malcolm
2022-06-22 22:34 ` [PATCH 07/12] Add deferred-locations.h/cc David Malcolm
2022-06-22 22:34 ` [PATCH 08/12] Add json-reader.h/cc David Malcolm
2022-06-22 22:34 ` [PATCH 09/12] Add json frontend David Malcolm
2022-06-22 22:34 ` David Malcolm [this message]
2022-06-22 22:34 ` [PATCH 11/12] Fixups to diagnostic-format-sarif.cc David Malcolm
2022-06-22 22:34 ` [PATCH 12/12] Work-in-progress of path remapping David Malcolm
2022-07-08 18:40 ` [PATCH 00/12] RFC: Replay of serialized diagnostics David Malcolm

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220622223447.2462880-11-dmalcolm@redhat.com \
    --to=dmalcolm@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).