diff --git a/gcc/json/Make-lang.in b/gcc/json/Make-lang.in
new file mode 100644
index 00000000000..a62d028f41a
--- /dev/null
+++ b/gcc/json/Make-lang.in
@@ -0,0 +1,131 @@
+# Make-lang.in -- Top level -*- makefile -*- fragment for gcc JSON "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 json in LANGUAGES.
+json: json-replay$(exeext)
+
+.PHONY: json
+
+JSON_OBJS = json/json-frontend.o json/json-replay.o \
+	attribs.o deferred-locations.o json-reader.o
+json_OBJS = $(JSON_OBJS)
+
+json-replay$(exeext): $(JSON_OBJS) $(BACKEND) $(LIBDEPS)
+	@$(call LINK_PROGRESS,$(INDEX.json),start)
+	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ \
+	      $(JSON_OBJS) $(BACKEND) $(LIBS) $(BACKENDLIBS)
+	@$(call LINK_PROGRESS,$(INDEX.json),end)
+
+# Build hooks.
+
+json.all.cross:
+json.start.encap:
+json.rest.encap:
+
+json.info:
+json.man:
+
+lang_checks += check-json
+
+# No json-specific selftests
+selftest-json:
+
+# Install hooks.
+
+json.install-common: installdirs
+	-rm -f $(DESTDIR)$(bindir)/$(GCCJSON_INSTALL_NAME)$(exeext)
+	$(INSTALL_PROGRAM) gccjson$(exeext) $(DESTDIR)$(bindir)/$(GCCJSON_INSTALL_NAME)$(exeext)
+	-if test -f json-replay$(exeext); then \
+	  if test -f gccjson-cross$(exeext); then \
+	    :; \
+	  else \
+	    rm -f $(DESTDIR)$(bindir)/$(GCCJSON_TARGET_INSTALL_NAME)$(exeext); \
+	    ( cd $(DESTDIR)$(bindir) && \
+	      $(LN) $(GCCJSON_INSTALL_NAME)$(exeext) $(GCCJSON_TARGET_INSTALL_NAME)$(exeext) ); \
+	  fi; \
+	fi
+
+json.install-plugin:
+
+json.install-info: $(DESTDIR)$(infodir)/gccjson.info
+
+json.install-pdf: doc/gccjson.pdf
+	@$(NORMAL_INSTALL)
+	test -z "$(pdfdir)" || $(mkinstalldirs) "$(DESTDIR)$(pdfdir)/gcc"
+	@for p in doc/gccjson.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
+
+json.install-html: $(build_htmldir)/json
+	@$(NORMAL_INSTALL)
+	test -z "$(htmldir)" || $(mkinstalldirs) "$(DESTDIR)$(htmldir)"
+	@for p in $(build_htmldir)/json; 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
+
+json.install-man: $(DESTDIR)$(man1dir)/$(GCCJSON_INSTALL_NAME)$(man1ext)
+
+
+json.uninstall:
+	rm -rf $(DESTDIR)$(bindir)/$(GCCJSON_INSTALL_NAME)$(exeext)
+	rm -rf $(DESTDIR)$(man1dir)/$(GCCJSON_INSTALL_NAME)$(man1ext)
+	rm -rf $(DESTDIR)$(bindir)/$(GCCJSON_TARGET_INSTALL_NAME)$(exeext)
+	rm -rf $(DESTDIR)$(infodir)/gccjson.info*
+
+# Clean hooks.
+
+json.mostlyclean:
+	-rm -f json/*$(objext)
+	-rm -f json/*$(coverageexts)
+	-rm -f gccjson$(exeext) gccjson-cross$(exeext) json-replay$(exeext)
+json.clean:
+json.distclean:
+json.maintainer-clean:
+	-rm -f $(docobjdir)/gccjson.1
+
+# Stage hooks.
+
+json.stage1: stage1-start
+	-mv json/*$(objext) stage1/json
+json.stage2: stage2-start
+	-mv json/*$(objext) stage2/json
+json.stage3: stage3-start
+	-mv json/*$(objext) stage3/json
+json.stage4: stage4-start
+	-mv json/*$(objext) stage4/json
+json.stageprofile: stageprofile-start
+	-mv json/*$(objext) stageprofile/json
+json.stagefeedback: stagefeedback-start
+	-mv json/*$(objext) stagefeedback/json
diff --git a/gcc/json/config-lang.in b/gcc/json/config-lang.in
new file mode 100644
index 00000000000..c1b3593570c
--- /dev/null
+++ b/gcc/json/config-lang.in
@@ -0,0 +1,34 @@
+# config-lang.in -- Top level configure fragment for gcc JSON "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="json"
+
+compilers="json-replay\$(exeext)"
+
+gtfiles="\$(srcdir)/json/json-frontend.cc"
+
+# Build by default.
+build_by_default="yes"
diff --git a/gcc/json/json-frontend.cc b/gcc/json/json-frontend.cc
new file mode 100644
index 00000000000..edf72282d96
--- /dev/null
+++ b/gcc/json/json-frontend.cc
@@ -0,0 +1,176 @@
+/* The dummy "frontend" for re-emitting diagnostics saved in JSON 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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "debug.h"
+#include "langhooks.h"
+#include "langhooks-def.h"
+#include "json/json-replay.h"
+#include "opts.h"
+#include "diagnostic.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
+json_langhook_init (void)
+{
+  replay_json (main_input_filename);
+  return false;
+}
+
+static unsigned int
+json_langhook_option_lang_mask (void)
+{
+  return CL_JSON;
+}
+
+static bool
+json_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 & json_langhook_option_lang_mask ())
+	break;
+      result = false;
+    }
+
+  JSON_handle_option_auto (&global_options, &global_options_set,
+			    scode, arg, value,
+			    json_langhook_option_lang_mask (), kind,
+			    loc, handlers, global_dc);
+
+  return result;
+}
+
+static tree
+json_langhook_type_for_mode (machine_mode, int)
+{
+  gcc_unreachable ();
+  return NULL_TREE;
+}
+
+static bool
+json_langhook_global_bindings_p (void)
+{
+  return true;
+}
+
+static tree
+json_langhook_pushdecl (tree decl ATTRIBUTE_UNUSED)
+{
+  gcc_unreachable ();
+}
+
+static tree
+json_langhook_getdecls (void)
+{
+  return NULL;
+}
+#undef LANG_HOOKS_NAME
+#define LANG_HOOKS_NAME		"json"
+
+#undef LANG_HOOKS_INIT
+#define LANG_HOOKS_INIT		json_langhook_init
+
+#undef LANG_HOOKS_OPTION_LANG_MASK
+#define LANG_HOOKS_OPTION_LANG_MASK	json_langhook_option_lang_mask
+
+#undef LANG_HOOKS_HANDLE_OPTION
+#define LANG_HOOKS_HANDLE_OPTION        json_langhook_handle_option
+
+#undef LANG_HOOKS_TYPE_FOR_MODE
+#define LANG_HOOKS_TYPE_FOR_MODE	json_langhook_type_for_mode
+
+#undef LANG_HOOKS_GLOBAL_BINDINGS_P
+#define LANG_HOOKS_GLOBAL_BINDINGS_P	json_langhook_global_bindings_p
+
+#undef LANG_HOOKS_PUSHDECL
+#define LANG_HOOKS_PUSHDECL		json_langhook_pushdecl
+
+#undef LANG_HOOKS_GETDECLS
+#define LANG_HOOKS_GETDECLS		json_langhook_getdecls
+
+#undef  LANG_HOOKS_DEEP_UNSHARING
+#define LANG_HOOKS_DEEP_UNSHARING	true
+
+struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
+
+#include "gt-json-json-frontend.h"
+#include "gtype-json.h"
diff --git a/gcc/json/json-replay.cc b/gcc/json/json-replay.cc
new file mode 100644
index 00000000000..d24d2b63e15
--- /dev/null
+++ b/gcc/json/json-replay.cc
@@ -0,0 +1,614 @@
+/* Re-emitting diagnostics saved in JSON 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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
+#include "line-map.h"
+#include "stringpool.h"
+#include "gcc-rich-location.h"
+#include "json-parsing.h"
+#include "json-reader.h"
+#include "deferred-locations.h"
+#include "diagnostic-client-data-hooks.h"
+#include "json/json-replay.h"
+
+class json_replayer;
+
+/* Concrete subclass of diagnostic_client_data_hooks, for
+   use when replaying a json file.
+
+   Takes ownership of the json_replayer and toplevel json::value objects.  */
+
+class json_replayer_diagnostic_client_data_hooks
+  : public diagnostic_client_data_hooks
+{
+ public:
+  json_replayer_diagnostic_client_data_hooks (json_replayer *replayer)
+  : m_replayer (replayer),
+    m_toplevel_jv (NULL)
+  {}
+
+  ~json_replayer_diagnostic_client_data_hooks ();
+
+  const client_version_info *get_any_version_info () const final override
+  {
+    /* The JSON dump doesn't contain any version info.  */
+    return NULL;
+  }
+  const logical_location *get_current_logical_location () const final override
+  {
+    return NULL; // TODO
+  }
+  const char *
+  maybe_get_sarif_source_language (const char *) const final override
+  {
+    return NULL;
+  }
+
+  void stash (json::value *toplevel_jv)
+  {
+    m_toplevel_jv = toplevel_jv;
+  }
+
+  json_replayer *m_replayer;
+  json::value *m_toplevel_jv;
+};
+
+/* A bundle of state for replaying a GCC diagnostic json file.  */
+
+class json_replayer : public json_reader
+{
+public:
+  json_replayer (const char *filename) : json_reader (filename) {}
+  ~json_replayer ();
+
+  void emit_json_as_diagnostics (json::value *jv);
+
+  /* Get the value of property ATTR_NAME within OBJ,
+     exiting with an error if it is not present.  */
+  json::value *
+  get_required_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = obj->get (attr_name);
+    if (!attr_val)
+      fatal_error (obj,
+		   "expected a %qs within object", attr_name);
+    return attr_val;
+  }
+
+  /* Get the value of optional property ATTR_NAME within OBJ,
+     exiting with an error if it is not a string.  */
+  const char *
+  get_optional_string_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = obj->get (attr_name);
+    if (!attr_val)
+      return NULL;
+    json::string *attr_str = dyn_cast <json::string *> (attr_val);
+    if (!attr_str)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be a string", attr_name);
+    return attr_str->get_string ();
+  }
+
+  /* Get the value of property ATTR_NAME within OBJ,
+     exiting with an error if it is not present or is not a string.  */
+  const char *
+  get_required_string_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = get_required_attr (obj, attr_name);
+    json::string *attr_str = dyn_cast <json::string *> (attr_val);
+    if (!attr_str)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be a string", attr_name);
+    return attr_str->get_string ();
+  }
+
+  /* Get the value of optional property ATTR_NAME within OBJ,
+     exiting with an error if it is not an object.  */
+  json::object *
+  get_optional_object_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = obj->get (attr_name);
+    if (!attr_val)
+      return NULL;
+    json::object *attr_obj = dyn_cast <json::object *> (attr_val);
+    if (!attr_obj)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be an object", attr_name);
+    return attr_obj;
+  }
+
+  /* Get the value of property ATTR_NAME within OBJ,
+     exiting with an error if it is not present or is not an object.  */
+  json::object *
+  get_required_object_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = get_required_attr (obj, attr_name);
+    json::object *attr_obj = dyn_cast <json::object *> (attr_val);
+    if (!attr_obj)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be an object", attr_name);
+    return attr_obj;
+  }
+
+  /* Get the value of optional property ATTR_NAME within OBJ,
+     exiting with an error if it is not an array.  */
+  json::array *
+  get_optional_array_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = obj->get (attr_name);
+    if (!attr_val)
+      return NULL;
+    json::array *attr_arr = dyn_cast <json::array *> (attr_val);
+    if (!attr_arr)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be an array", attr_name);
+    return attr_arr;
+  }
+
+  /* Get the value of property ATTR_NAME within OBJ,
+     exiting with an error if it is not present or is not an integer.  */
+  long
+  get_required_long_attr (json::object *obj, const char *attr_name)
+  {
+    json::value *attr_val = get_required_attr (obj, attr_name);
+    json::integer_number *attr_int
+      = dyn_cast <json::integer_number *> (attr_val);
+    if (!attr_int)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be an integer number",
+		   attr_name);
+    return attr_int->get ();
+  }
+
+  /* If OBJ has optional property ATTR_NAME within OBJ and it is an integer,
+     return true and write its value to *OUT.
+     If it is not present, return false.
+     If it is not an integer, exit with an error.  */
+  bool
+  get_optional_long_attr (json::object *obj, const char *attr_name, long *out)
+  {
+    json::value *attr_val = obj->get (attr_name);
+    if (!attr_val)
+      return false;
+    json::integer_number *attr_int
+      = dyn_cast <json::integer_number *> (attr_val);
+    if (!attr_int)
+      fatal_error (attr_val,
+		   "expected the value of %qs to be an integer number",
+		   attr_name);
+    *out = attr_int->get ();
+    return true;
+  }
+
+  json::object *
+  require_object (json::value *jv)
+  {
+    if (json::object *obj = dyn_cast <json::object *> (jv))
+      return obj;
+    fatal_error (jv, "expected an object");
+    return NULL;
+  }
+
+  location_t
+  get_required_expanded_location_attr (json::object *obj, const char *attr_name,
+				       deferred_locations *deferred_locs,
+				       int pass);
+
+  location_t
+  get_optional_expanded_location_attr (json::object *obj, const char *attr_name,
+				       deferred_locations *deferred_locs,
+				       int pass);
+private:
+  void emit_diag_obj (json::object *diag_obj,
+		      deferred_locations *deferred_locs,
+		      int pass);
+  location_t
+  get_location_from_expanded_location_obj (json::object *loc_obj,
+					   deferred_locations *deferred_locs,
+					   int pass);
+
+  diagnostic_t get_diagnostic_kind (json::object *diag_obj);
+
+  hash_map<json::object *, location_t *> m_map_loc_obj_to_loc_t;
+};
+
+/* class json_replayer_diagnostic_client_data_hooks
+     : public diagnostic_client_data_hooks.  */
+
+json_replayer_diagnostic_client_data_hooks::
+~json_replayer_diagnostic_client_data_hooks ()
+{
+  delete m_replayer;
+  delete m_toplevel_jv;
+}
+
+/* class json_replayer : public json_reader.  */
+
+json_replayer::~json_replayer ()
+{
+  for (auto iter : m_map_loc_obj_to_loc_t)
+    delete iter.second;
+}
+
+/* In pass 0, defer the creation of a location_t for OBJ (originally created
+   by json_from_expanded_location).
+   In pass 1, look up the location_t that was created.  */
+
+location_t
+json_replayer::
+get_location_from_expanded_location_obj (json::object *loc_obj,
+					 deferred_locations *deferred_locs,
+					 int pass)
+{
+  expanded_location exp_loc = {NULL, 0, 0, NULL, false};
+  exp_loc.file = get_optional_string_attr (loc_obj, "file");
+  long line;
+  if (get_optional_long_attr (loc_obj, "line", &line))
+    exp_loc.line = line;
+  long column;
+  if (get_optional_long_attr (loc_obj, "column", &column))
+    exp_loc.column = column;
+  if (pass)
+    return **m_map_loc_obj_to_loc_t.get (loc_obj);
+  else
+    {
+      location_t *loc = new location_t;
+      deferred_locs->add_location (exp_loc, loc);
+      m_map_loc_obj_to_loc_t.put (loc_obj, loc);
+      return UNKNOWN_LOCATION;
+    }
+}
+
+/* Get the value of property ATTR_NAME within OBJ,
+   exiting with an error if it is not present or not a location.
+   In pass 0, defer the creation of a location_t for the value (originally
+   created by json_from_expanded_location).
+   In pass 1, look up the location_t that was created.  */
+
+location_t
+json_replayer::
+get_required_expanded_location_attr (json::object *obj, const char *attr_name,
+				     deferred_locations *deferred_locs,
+				     int pass)
+{
+  json::object *loc_obj = get_required_object_attr (obj, attr_name);
+  return get_location_from_expanded_location_obj (loc_obj, deferred_locs, pass);
+}
+
+/* As get_required_expanded_location_attr, but return UNKNOWN_LOCATION
+   if ATTR_NAME is not present within OBJ.  */
+
+location_t
+json_replayer::
+get_optional_expanded_location_attr (json::object *obj, const char *attr_name,
+				     deferred_locations *deferred_locs,
+				     int pass)
+{
+  json::object *loc_obj = get_optional_object_attr (obj, attr_name);
+  if (!loc_obj)
+    return UNKNOWN_LOCATION;
+  return get_location_from_expanded_location_obj (loc_obj, deferred_locs, pass);
+}
+
+/* Get the diagnostic_t kind for DIAG_OBJ.  */
+
+diagnostic_t
+json_replayer::get_diagnostic_kind (json::object *diag_obj)
+{
+  const char *kind_str = get_required_string_attr (diag_obj, "kind");
+
+  static const char *const diagnostic_kind_text[] = {
+#define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
+#include "diagnostic.def"
+#undef DEFINE_DIAGNOSTIC_KIND
+      "must-not-happen"
+  };
+  for (unsigned i = 0; i < ARRAY_SIZE (diagnostic_kind_text); i++)
+    {
+      /* Compare, without the trailing ": ".  */
+      const char *kind_text = diagnostic_kind_text[i];
+      size_t len = strlen (kind_text);
+      if (len <= 2)
+	continue;
+      gcc_assert (kind_text[len - 2] == ':');
+      gcc_assert (kind_text[len - 1] == ' ');
+      if (0 == strncmp (kind_text, kind_str, len - 2)
+	  && kind_str[len - 2] == '\0')
+	return static_cast<diagnostic_t> (i);
+    }
+  fatal_error (diag_obj,
+	       "unrecognized value for %qs: %qs", "kind", kind_str);
+}
+
+static hash_map<tree, tree> map_id_to_fndecl;
+
+/* Create a placeholder void -> void function declaration for a function
+   named FUNC_STR.  */
+
+static tree
+make_placeholder_fndecl (const char *func_str)
+{
+  tree id = get_identifier (func_str);
+  if (tree *slot = map_id_to_fndecl.get (id))
+    return *slot;
+  tree fndecl = build_fn_decl (func_str, NULL_TREE/*fn_type*/);
+  map_id_to_fndecl.put (id, fndecl);
+  return fndecl;
+}
+
+/* Custom subclass of rich_location, relating to replaying a specific
+   diagnostic serialized to JSON.  */
+
+class json_rich_location : public rich_location
+{
+public:
+  json_rich_location (json_replayer *replayer,
+		      json::object *diag_obj, deferred_locations *deferred_locs,
+		      int pass)
+  : rich_location (line_table, UNKNOWN_LOCATION)
+  {
+    json::value *locations_val
+      = replayer->get_required_attr (diag_obj, "locations");
+    json::array *locations_arr = dyn_cast <json::array *> (locations_val);
+    if (!locations_arr)
+      replayer->fatal_error (locations_val,
+			   "expected an array for the value of %qs",
+			   "locations");
+    m_ranges.truncate (0);
+    for (auto loc_iter : *locations_arr)
+      {
+	json::object *loc_obj = dyn_cast<json::object *> (loc_iter);
+	if (!loc_obj)
+	  replayer->fatal_error (loc_iter,
+				 "expected an object within the %qs array",
+				 "locations");
+	/* Compare with diagnostic-format-json.cc:json_from_location_range.  */
+	location_t caret_loc
+	  = replayer->get_required_expanded_location_attr (loc_obj, "caret",
+							   deferred_locs, pass);
+	location_t start_loc
+	  = replayer->get_optional_expanded_location_attr (loc_obj, "start",
+							   deferred_locs, pass);
+	location_t finish_loc
+	  = replayer->get_optional_expanded_location_attr (loc_obj, "finish",
+							   deferred_locs, pass);
+	if (start_loc == UNKNOWN_LOCATION)
+	  start_loc = caret_loc;
+	if (finish_loc == UNKNOWN_LOCATION)
+	  finish_loc = caret_loc;
+	const char *label_str
+	  = replayer->get_optional_string_attr (loc_obj, "label");
+	range_label *label
+	  = label_str ? new text_range_label (label_str) : NULL;
+	location_t range_loc = make_location (caret_loc, start_loc, finish_loc);
+	add_range (range_loc, SHOW_RANGE_WITH_CARET, label);
+      }
+
+    json::array *fixits_arr
+      = replayer->get_optional_array_attr (diag_obj, "fixits");
+    if (fixits_arr)
+      for (auto fixit_iter : *fixits_arr)
+	{
+	  json::object *fixit_obj = dyn_cast<json::object *> (fixit_iter);
+	  if (!fixit_obj)
+	    replayer->fatal_error (fixit_iter,
+				   "expected an object within the %qs array",
+				   "fixits");
+	  on_fixit_obj (replayer, fixit_obj, deferred_locs, pass);
+	}
+
+    json::array *path_arr
+      = replayer->get_optional_array_attr (diag_obj, "path");
+    if (path_arr)
+      {
+	pretty_printer pp;
+	simple_diagnostic_path *path = new simple_diagnostic_path (&pp);
+	set_path (path);
+	for (auto event_iter : *path_arr)
+	  {
+	    json::object *event_obj = dyn_cast<json::object *> (event_iter);
+	    if (!event_obj)
+	      replayer->fatal_error (event_iter,
+				     "expected an object within the %qs array",
+				     "path");
+	    location_t loc
+	      = replayer->get_optional_expanded_location_attr (event_obj,
+							       "location",
+							       deferred_locs,
+							       pass);
+	    const char *desc_str
+	      = replayer->get_required_string_attr (event_obj, "description");
+	    tree fndecl = NULL_TREE;
+	    if (const char *func_str
+		  = replayer->get_optional_string_attr (event_obj, "function"))
+	      fndecl = make_placeholder_fndecl (func_str);
+	    long depth = replayer->get_required_long_attr (event_obj, "depth");
+	    path->add_event (loc, fndecl, depth, "%s", desc_str);
+	  }
+      }
+  }
+
+private:
+  /* Compare with diagnostic-format-json.cc:json_from_fixit_hint.  */
+  void on_fixit_obj (json_replayer *replayer,
+		     json::object *fixit_obj,
+		     deferred_locations *deferred_locs, int pass)
+  {
+    location_t start_loc
+      = replayer->get_required_expanded_location_attr (fixit_obj, "start",
+						       deferred_locs, pass);
+    location_t next_loc
+      = replayer->get_required_expanded_location_attr (fixit_obj, "next",
+						       deferred_locs, pass);
+    const char *string
+      = replayer->get_required_string_attr (fixit_obj, "string");
+    if (pass)
+      maybe_add_fixit (start_loc, next_loc, string);
+  }
+};
+
+class json_replayer_rule : public diagnostic_metadata::rule
+{
+public:
+  json_replayer_rule (json_replayer *replayer, json::object *diag_obj)
+  : m_replayer (replayer), m_diag_obj (diag_obj)
+  {}
+
+  char *make_description () const final override
+  {
+    if (const char *option
+	  = m_replayer->get_optional_string_attr (m_diag_obj, "option"))
+      return xstrdup (option);
+    return NULL;
+  }
+
+  char *make_url () const final override
+  {
+    if (const char *option_url
+	  = m_replayer->get_optional_string_attr (m_diag_obj, "option_url"))
+      return xstrdup (option_url);
+    return NULL;
+  }
+
+private:
+  json_replayer *m_replayer;
+  json::object *m_diag_obj;
+};
+
+
+/* Replay the diagnotic DIAG_OBJ to global_dc.
+   In pass 0, do everything except actually replay the diagnostic,
+   deferring the creation of location_t values.
+   In pass 1, actually emit the diagostic, using real location_t values.
+   Exit with an error if DIAG_OBJ does not match the expected output format.  */
+
+void
+json_replayer::emit_diag_obj (json::object *diag_obj,
+			      deferred_locations *deferred_locs,
+			      int pass)
+{
+  auto_diagnostic_group d;
+
+  diagnostic_t kind = get_diagnostic_kind (diag_obj);
+  const char *message_str = get_required_string_attr (diag_obj, "message");
+
+  json_rich_location rich_loc (this, diag_obj, deferred_locs, pass);
+
+  diagnostic_metadata meta;
+
+  if (json::object *metadata_obj = get_optional_object_attr (diag_obj,
+							     "metadata"))
+    {
+      long cwe_long;
+      if (get_optional_long_attr (metadata_obj, "cwe", &cwe_long))
+	meta.add_cwe (cwe_long);
+    }
+
+  bool emitted = true;
+  if (pass == 1)
+    {
+      const int option = 0;
+      json_replayer_rule rule (this, diag_obj);
+      meta.add_rule (rule);
+      emitted = emit_diagnostic (kind, &rich_loc, &meta, option,
+				 "%s", message_str);
+    }
+
+  if (emitted)
+    if (json::value *children_val = diag_obj->get ("children"))
+      {
+	json::array *children_arr = dyn_cast <json::array *> (children_val);
+	if (!children_arr)
+	  fatal_error (children_val,
+		       "expected an array for the value of %qs",
+		       "children");
+	for (auto child_val : *children_arr)
+	  {
+	    /* We expect an array of objects.  */
+	    json::object *child_obj = dyn_cast <json::object *> (child_val);
+	    if (!child_obj)
+	      fatal_error (child_val,
+			   "expected an object within the %qs array",
+			   "children");
+	    emit_diag_obj (child_obj, deferred_locs, pass);
+	  }
+      }
+}
+
+/* Replay the diagnotics in JV to global_dc.
+   Exit with an error if JV does not match the expected output format.  */
+
+void
+json_replayer::emit_json_as_diagnostics (json::value *jv)
+{
+  /* We expect an array as the top-level value.  */
+  json::array *toplev_arr = dyn_cast <json::array *> (jv);
+  if (!toplev_arr)
+    fatal_error (jv, "expected an array as the top-level value");
+
+  deferred_locations deferred_locs;
+
+  for (int pass = 0; pass < 2; pass++)
+    {
+      for (auto diag_val : *toplev_arr)
+	{
+	  /* We expect an object.  */
+	  json::object *diag_obj = require_object (diag_val);
+	  emit_diag_obj (diag_obj, &deferred_locs, pass);
+	}
+      if (pass == 0)
+	deferred_locs.generate_location_t_values ();
+    }
+}
+
+/* Attempt to load a json file from FILENAME and replay it.
+   Exit on any errors.  */
+
+void
+replay_json (const char *filename)
+{
+  json_replayer *p = new json_replayer (filename);
+  json_replayer_diagnostic_client_data_hooks *hooks
+    = new json_replayer_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)
+    {
+      hooks->stash (jv);
+      p->emit_json_as_diagnostics (jv);
+    }
+}
diff --git a/gcc/json/json-replay.h b/gcc/json/json-replay.h
new file mode 100644
index 00000000000..f2776aa4171
--- /dev/null
+++ b/gcc/json/json-replay.h
@@ -0,0 +1,26 @@
+/* Re-emitting diagnostics saved in JSON 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_JSON_JSON_REPLAY_H
+#define GCC_JSON_JSON_REPLAY_H
+
+extern void replay_json (const char *filename);
+
+#endif /* GCC_JSON_JSON_H */
diff --git a/gcc/json/lang-specs.h b/gcc/json/lang-specs.h
new file mode 100644
index 00000000000..da9a4f25757
--- /dev/null
+++ b/gcc/json/lang-specs.h
@@ -0,0 +1,26 @@
+/* lang-specs.h -- gcc driver specs for the JSON "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 json "frontend".  */
+
+{".json",  "@json", 0, 1, 0},
+/* FIXME: */
+{"@json",  "json-replay %i %(cc1_options)",
+    0, 1, 0},
diff --git a/gcc/json/lang.opt b/gcc/json/lang.opt
new file mode 100644
index 00000000000..4c75f5d35dd
--- /dev/null
+++ b/gcc/json/lang.opt
@@ -0,0 +1,31 @@
+; Options for the JSON 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
+JSON
+
+fallow-comments
+JSON Var(flag_allow_comments)
+Extend JSON to support comments
+
+; This comment is to ensure we retain the blank line above.
diff --git a/gcc/testsuite/json/invalid-json-array-missing-comma.json b/gcc/testsuite/json/invalid-json-array-missing-comma.json
new file mode 100644
index 00000000000..0f32d38420e
--- /dev/null
+++ b/gcc/testsuite/json/invalid-json-array-missing-comma.json
@@ -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/json/invalid-json-array-with-trailing-comma.json b/gcc/testsuite/json/invalid-json-array-with-trailing-comma.json
new file mode 100644
index 00000000000..05b74a81efc
--- /dev/null
+++ b/gcc/testsuite/json/invalid-json-array-with-trailing-comma.json
@@ -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/json/invalid-json-bad-token.json b/gcc/testsuite/json/invalid-json-bad-token.json
new file mode 100644
index 00000000000..7756eef1add
--- /dev/null
+++ b/gcc/testsuite/json/invalid-json-bad-token.json
@@ -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/json/invalid-json-object-missing-comma.json b/gcc/testsuite/json/invalid-json-object-missing-comma.json
new file mode 100644
index 00000000000..9d2bf9476b1
--- /dev/null
+++ b/gcc/testsuite/json/invalid-json-object-missing-comma.json
@@ -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/json/invalid-json-object-with-trailing-comma.json b/gcc/testsuite/json/invalid-json-object-with-trailing-comma.json
new file mode 100644
index 00000000000..e1aae9b350c
--- /dev/null
+++ b/gcc/testsuite/json/invalid-json-object-with-trailing-comma.json
@@ -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/json/invalid-jsondump-diag-not-an-object.json b/gcc/testsuite/json/invalid-jsondump-diag-not-an-object.json
new file mode 100644
index 00000000000..6f5d37f08e1
--- /dev/null
+++ b/gcc/testsuite/json/invalid-jsondump-diag-not-an-object.json
@@ -0,0 +1,6 @@
+[ 42 ] /* { dg-error "expected an object" } */
+
+/* { dg-begin-multiline-output "" }
+    1 | [ 42 ]
+      |   ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/json/invalid-jsondump-kind-not-a-string.json b/gcc/testsuite/json/invalid-jsondump-kind-not-a-string.json
new file mode 100644
index 00000000000..ab55f1e5573
--- /dev/null
+++ b/gcc/testsuite/json/invalid-jsondump-kind-not-a-string.json
@@ -0,0 +1,20 @@
+[
+    {
+        "kind": 42, /* { dg-error "expected the value of 'kind' to be a string" } */
+        "locations": [],
+        "column-origin": 1,
+        "option": "-Wanalyzer-unsafe-call-within-signal-handler",
+        "escape-source": false,
+        "children": [],
+        "option_url": "https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html#index-Wanalyzer-unsafe-call-within-signal-handler",
+        "message": "call to \u2018fprintf\u2019 from within signal handler",
+        "metadata": {
+            "cwe": 479
+        }
+    }
+]
+
+/* { dg-begin-multiline-output "" }
+    3 |         "kind": 42,
+      |                 ^~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/json/invalid-jsondump-not-an-array.json b/gcc/testsuite/json/invalid-jsondump-not-an-array.json
new file mode 100644
index 00000000000..9b14ea35565
--- /dev/null
+++ b/gcc/testsuite/json/invalid-jsondump-not-an-array.json
@@ -0,0 +1,6 @@
+{ "foo": "bar" } /* { dg-error "expected an array as the top-level value" } */
+
+/* { dg-begin-multiline-output "" }
+    1 | { "foo": "bar" }
+      | ^~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/json/json.exp b/gcc/testsuite/json/json.exp
new file mode 100644
index 00000000000..0a33ba53fb6
--- /dev/null
+++ b/gcc/testsuite/json/json.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 json-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_JSON_FLAGS
+if ![info exists DEFAULT_JSON_FLAGS] then {
+    set DEFAULT_JSON_FLAGS "-fallow-comments"
+}
+# Initialize `dg'.
+dg-init
+
+dg-runtest [lsort \
+       [glob -nocomplain $srcdir/$subdir/*.json ] ] "" $DEFAULT_JSON_FLAGS
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/json/signal-1.c.json b/gcc/testsuite/json/signal-1.c.json
new file mode 100644
index 00000000000..5f4962209a9
--- /dev/null
+++ b/gcc/testsuite/json/signal-1.c.json
@@ -0,0 +1,131 @@
+[
+    {
+        "kind": "warning",
+        "locations": [
+            {
+                "finish": {
+                    "byte-column": 33,
+                    "display-column": 33,
+                    "line": 13,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 33
+                },
+                "caret": {
+                    "byte-column": 3,
+                    "display-column": 3,
+                    "line": 13,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 3
+                }
+            }
+        ],
+        "path": [
+            {
+                "location": {
+                    "byte-column": 5,
+                    "display-column": 5,
+                    "line": 21,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 5
+                },
+                "description": "entry to \u2018main\u2019",
+                "depth": 1,
+                "function": "main"
+            },
+            {
+                "location": {
+                    "byte-column": 3,
+                    "display-column": 3,
+                    "line": 25,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 3
+                },
+                "description": "registering \u2018handler\u2019 as signal handler",
+                "depth": 1,
+                "function": "main"
+            },
+            {
+                "description": "later on, when the signal is delivered to the process",
+                "depth": 0
+            },
+            {
+                "location": {
+                    "byte-column": 13,
+                    "display-column": 13,
+                    "line": 16,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 13
+                },
+                "description": "entry to \u2018handler\u2019",
+                "depth": 1,
+                "function": "handler"
+            },
+            {
+                "location": {
+                    "byte-column": 3,
+                    "display-column": 3,
+                    "line": 18,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 3
+                },
+                "description": "calling \u2018custom_logger\u2019 from \u2018handler\u2019",
+                "depth": 1,
+                "function": "handler"
+            },
+            {
+                "location": {
+                    "byte-column": 6,
+                    "display-column": 6,
+                    "line": 11,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 6
+                },
+                "description": "entry to \u2018custom_logger\u2019",
+                "depth": 2,
+                "function": "custom_logger"
+            },
+            {
+                "location": {
+                    "byte-column": 3,
+                    "display-column": 3,
+                    "line": 13,
+                    "file": "../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c",
+                    "column": 3
+                },
+                "description": "call to \u2018fprintf\u2019 from within signal handler",
+                "depth": 2,
+                "function": "custom_logger"
+            }
+        ],
+        "column-origin": 1,
+        "option": "-Wanalyzer-unsafe-call-within-signal-handler",
+        "escape-source": false,
+        "children": [],
+        "option_url": "https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html#index-Wanalyzer-unsafe-call-within-signal-handler",
+        "message": "call to \u2018fprintf\u2019 from within signal handler",
+        "metadata": {
+            "cwe": 479
+        }
+    }
+]
+
+/* { dg-begin-multiline-output "" }
+../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c:13:3: warning: call to ‘fprintf’ from within signal handler [CWE-479] [-Wanalyzer-unsafe-call-within-signal-handler]
+  'main': events 1-2
+    |
+    |......
+    |
+  event 3
+    |
+    |json-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: various things wrong here
diff --git a/gcc/testsuite/lib/json-dg.exp b/gcc/testsuite/lib/json-dg.exp
new file mode 100644
index 00000000000..18701f84cac
--- /dev/null
+++ b/gcc/testsuite/lib/json-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 json callbacks for dg.exp.
+
+proc json-dg-test { prog do_what extra_tool_flags } {
+    set result \
+	[gcc-dg-test-1 json_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.
+    #
+    # json 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.
+    # json 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 json-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 json-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 json-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/json.exp b/gcc/testsuite/lib/json.exp
new file mode 100644
index 00000000000..52ba75a1a14
--- /dev/null
+++ b/gcc/testsuite/lib/json.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.
+
+#
+# json 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 json_target_compile { source dest type options } {
+    set return_val [target_compile $source $dest $type $options]
+    return $return_val;
+}
