diff mbox series

[pushed] v2: diagnostics: prettify JSON output formats

Message ID 20231206175011.2462694-1-dmalcolm@redhat.com
State New
Headers show
Series [pushed] v2: diagnostics: prettify JSON output formats | expand

Commit Message

David Malcolm Dec. 6, 2023, 5:50 p.m. UTC
I messed up the testing of the previous version of this patch, and
it turned out to have regressions.

Whilst fixing them, it turned out I needed a way to disable the
formatting for some test cases, so this version of the patch restricts
the formatting to just the diagnostics format, and adds a
-fno-diagnostics-json-formatting for turning it off.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r14-6228-g3bd8241a1f1982.

For reference, here's what I've pushed:

Previously our JSON output emitted the JSON all on one line, with
no indentation to show the structure of the values.

Although it's easy to reformat such output (e.g. with
"python -m json.tool"), I've found it's a pain to need to do so
e.g. my text editor sometimes hangs when opening a multimegabyte
json file all on one line.  Similarly diff-ing is easier if the
json is already formatted.

This patch add whitespace to json output to show the structure.
It turned out to be fairly easy to implement using pretty_printer's
existing indentation machinery.

The patch uses this formatting for the various JSON-based diagnostic
output formats.

For example, with this patch, the output from
fdiagnostics-format=json-stderr looks like:

[{"kind": "warning",
  "message": "stack-based buffer overflow",
  "option": "-Wanalyzer-out-of-bounds",
  "option_url": "https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html#index-Wanalyzer-out-of-bounds",
  "children": [{"kind": "note",
                "message": "write of 350 bytes to beyond the end of ‘buf’",
                "locations": [{"caret": {"file": "../../src/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-19.c",
                                         "line": 20,
                                         "display-column": 3,
                                         "byte-column": 3,
                                         "column": 3},
                               "finish": {"file": "../../src/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-19.c",
                                          "line": 20,
                                          "display-column": 27,
                                          "byte-column": 27,
                                          "column": 27}}],
                "escape-source": false},
               {"kind": "note",
                "message": "valid subscripts for ‘buf’ are ‘[0]’ to ‘[99]’",
                "locations": [{"caret": {"file": "../../src/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-19.c",
                                         "line": 20,
                                         "display-column": 3,
                                         "byte-column": 3,
                                         "column": 3},
                               "finish": {"file": "../../src/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-19.c",
                                          "line": 20,
                                          "display-column": 27,
                                          "byte-column": 27,
                                          "column": 27}}],
                "escape-source": false}],
  "column-origin": 1,
...snip...]

I was able to update almost all of our DejaGnu test cases for JSON to
handle this format tweak, and IMHO it improved the readability of these
test cases, but a couple were more awkward.  Hence I added
-fno-diagnostics-json-formatting as an option to disable this
formatting.

The formatting does not affect the output of -fsave-optimization-record
or the JSON output from gcov (but this could be enabled if desirable).

gcc/analyzer/ChangeLog:
	* engine.cc (dump_analyzer_json): Use
	flag_diagnostics_json_formatting.

gcc/ChangeLog:
	* common.opt (fdiagnostics-json-formatting): New.
	* diagnostic-format-json.cc: Add "formatted" boolean
	to json_output_format and subclasses, and to the
	diagnostic_output_format_init_json_* functions.  Use it when
	printing JSON.
	* diagnostic-format-sarif.cc: Likewise for sarif_builder,
	sarif_output_format, and the various
	diagnostic_output_format_init_sarif_* functions.
	* diagnostic.cc (diagnostic_output_format_init): Add
	"json_formatting" boolean and pass on to the various cases.
	* diagnostic.h (diagnostic_output_format_init): Add
	"json_formatted" param.
	(diagnostic_output_format_init_json_stderr): Add "formatted" param
	(diagnostic_output_format_init_json_file): Likewise.
	(diagnostic_output_format_init_sarif_stderr): Likewise.
	(diagnostic_output_format_init_sarif_file): Likewise.
	(diagnostic_output_format_init_sarif_stream): Likewise.
	* doc/invoke.texi (-fdiagnostics-format=json): Remove discussion
	about JSON output needing formatting.
	(-fno-diagnostics-json-formatting): Add.
	* gcc.cc (driver_handle_option): Use
	opts->x_flag_diagnostics_json_formatting.
	* gcov.cc (generate_results): Pass "false" for new formatting
	option when printing json.
	* json.cc (value::dump): Add new "formatted" param.
	(object::print): Likewise, using it to add whitespace to format
	the JSON output.
	(array::print): Likewise.
	(float_number::print): Add new "formatted" param.
	(integer_number::print): Likewise.
	(string::print): Likewise.
	(literal::print): Likewise.
	(selftest::assert_print_eq): Add "formatted" param.
	(ASSERT_PRINT_EQ): Add "FORMATTED" param.
	(selftest::test_writing_objects): Test both formatted and
	unformatted printing.
	(selftest::test_writing_arrays): Likewise.
	(selftest::test_writing_float_numbers): Update for new param of
	ASSERT_PRINT_EQ.
	(selftest::test_writing_integer_numbers): Likewise.
	(selftest::test_writing_strings): Likewise.
	(selftest::test_writing_literals): Likewise.
	(selftest::test_formatting): New.
	(selftest::json_cc_tests): Call it.
	* json.h (value::print): Add "formatted" param.
	(value::dump): Likewise.
	(object::print): Likewise.
	(array::print): Likewise.
	(float_number::print): Likewise.
	(integer_number::print): Likewise.
	(string::print): Likewise.
	(literal::print): Likewise.
	* optinfo-emit-json.cc (optrecord_json_writer::write): Pass
	"false" for new formatting option when printing json.
	(selftest::test_building_json_from_dump_calls): Likewise.
	* opts.cc (common_handle_option): Use
	opts->x_flag_diagnostics_json_formatting.

gcc/testsuite/ChangeLog:
	* c-c++-common/diagnostic-format-json-1.c: Update expected JSON
	output to reflect whitespace.
	* c-c++-common/diagnostic-format-json-2.c: Likewise.
	* c-c++-common/diagnostic-format-json-3.c: Likewise.
	* c-c++-common/diagnostic-format-json-4.c: Likewise.
	* c-c++-common/diagnostic-format-json-5.c: Likewise.
	* c-c++-common/diagnostic-format-json-stderr-1.c: Likewise.
	* g++.dg/pr90462.C: Add -fno-diagnostics-json-formatting.
	* gcc.dg/analyzer/malloc-sarif-1.c: Likewise.
	* gcc.dg/plugin/diagnostic-test-paths-3.c: Update expected JSON
	output to reflect whitespace.
	* gfortran.dg/diagnostic-format-json-1.F90: Likewise.
	* gfortran.dg/diagnostic-format-json-2.F90: Likewise.
	* gfortran.dg/diagnostic-format-json-3.F90: Likewise.
---
 gcc/analyzer/engine.cc                        |   2 +-
 gcc/common.opt                                |   4 +
 gcc/diagnostic-format-json.cc                 |  26 ++-
 gcc/diagnostic-format-sarif.cc                |  37 ++--
 gcc/diagnostic.cc                             |  17 +-
 gcc/diagnostic.h                              |  12 +-
 gcc/doc/invoke.texi                           |  22 ++-
 gcc/gcc.cc                                    |   3 +-
 gcc/gcov.cc                                   |   4 +-
 gcc/json.cc                                   | 163 +++++++++++++-----
 gcc/json.h                                    |  16 +-
 gcc/optinfo-emit-json.cc                      |   4 +-
 gcc/opts.cc                                   |   3 +-
 .../c-c++-common/diagnostic-format-json-1.c   |  42 ++---
 .../c-c++-common/diagnostic-format-json-2.c   |  48 +++---
 .../c-c++-common/diagnostic-format-json-3.c   |  48 +++---
 .../c-c++-common/diagnostic-format-json-4.c   |  93 ++++------
 .../c-c++-common/diagnostic-format-json-5.c   |  86 +++------
 .../diagnostic-format-json-stderr-1.c         |  42 ++---
 gcc/testsuite/g++.dg/pr90462.C                |   2 +-
 .../gcc.dg/analyzer/malloc-sarif-1.c          |   2 +-
 .../gcc.dg/plugin/diagnostic-test-paths-3.c   |  45 ++++-
 .../gfortran.dg/diagnostic-format-json-1.F90  |  45 ++---
 .../gfortran.dg/diagnostic-format-json-2.F90  |  49 +++---
 .../gfortran.dg/diagnostic-format-json-3.F90  |  49 +++---
 25 files changed, 470 insertions(+), 394 deletions(-)
diff mbox series

Patch

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 232636cde21..d2524e34f58 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6073,7 +6073,7 @@  dump_analyzer_json (const supergraph &sg,
   toplev_obj->set ("egraph", eg.to_json ());
 
   pretty_printer pp;
-  toplev_obj->print (&pp);
+  toplev_obj->print (&pp, flag_diagnostics_json_formatting);
   pp_formatted_text (&pp);
 
   delete toplev_obj;
diff --git a/gcc/common.opt b/gcc/common.opt
index 161a035d736..cb82272e31d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1391,6 +1391,10 @@  Enum(diagnostic_color_rule) String(always) Value(DIAGNOSTICS_COLOR_YES)
 EnumValue
 Enum(diagnostic_color_rule) String(auto) Value(DIAGNOSTICS_COLOR_AUTO)
 
+fdiagnostics-json-formatting
+Common Var(flag_diagnostics_json_formatting) Init(1)
+Enable formatting of JSON output.
+
 fdiagnostics-urls=
 Driver Common Joined RejectNegative Var(flag_diagnostics_show_urls) Enum(diagnostic_url_rule) Init(DIAGNOSTICS_URL_AUTO)
 -fdiagnostics-urls=[never|always|auto]	Embed URLs in diagnostics.
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index 418db74522d..c013192de06 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -56,11 +56,13 @@  public:
   }
 
 protected:
-  json_output_format (diagnostic_context &context)
+  json_output_format (diagnostic_context &context,
+		      bool formatted)
   : diagnostic_output_format (context),
     m_toplevel_array (new json::array ()),
     m_cur_group (nullptr),
-    m_cur_children_array (nullptr)
+    m_cur_children_array (nullptr),
+    m_formatted (formatted)
   {
   }
 
@@ -68,7 +70,7 @@  protected:
   void
   flush_to_file (FILE *outf)
   {
-    m_toplevel_array->dump (outf);
+    m_toplevel_array->dump (outf, m_formatted);
     fprintf (outf, "\n");
     delete m_toplevel_array;
     m_toplevel_array = nullptr;
@@ -84,6 +86,8 @@  private:
   /* The JSON array for the "children" array within the current diagnostic
      group.  */
   json::array *m_cur_children_array;
+
+  bool m_formatted;
 };
 
 /* Generate a JSON object for LOC.  */
@@ -301,8 +305,9 @@  json_output_format::on_end_diagnostic (const diagnostic_info &diagnostic,
 class json_stderr_output_format : public json_output_format
 {
 public:
-  json_stderr_output_format (diagnostic_context &context)
-  : json_output_format (context)
+  json_stderr_output_format (diagnostic_context &context,
+			     bool formatted)
+    : json_output_format (context, formatted)
   {
   }
   ~json_stderr_output_format ()
@@ -315,8 +320,9 @@  class json_file_output_format : public json_output_format
 {
 public:
   json_file_output_format (diagnostic_context &context,
+			   bool formatted,
 			   const char *base_file_name)
-  : json_output_format (context),
+  : json_output_format (context, formatted),
     m_base_file_name (xstrdup (base_file_name))
   {
   }
@@ -367,10 +373,12 @@  diagnostic_output_format_init_json (diagnostic_context *context)
 /* Populate CONTEXT in preparation for JSON output to stderr.  */
 
 void
-diagnostic_output_format_init_json_stderr (diagnostic_context *context)
+diagnostic_output_format_init_json_stderr (diagnostic_context *context,
+					   bool formatted)
 {
   diagnostic_output_format_init_json (context);
-  context->set_output_format (new json_stderr_output_format (*context));
+  context->set_output_format (new json_stderr_output_format (*context,
+							     formatted));
 }
 
 /* Populate CONTEXT in preparation for JSON output to a file named
@@ -378,10 +386,12 @@  diagnostic_output_format_init_json_stderr (diagnostic_context *context)
 
 void
 diagnostic_output_format_init_json_file (diagnostic_context *context,
+					 bool formatted,
 					 const char *base_file_name)
 {
   diagnostic_output_format_init_json (context);
   context->set_output_format (new json_file_output_format (*context,
+							   formatted,
 							   base_file_name));
 }
 
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 941fd0f5f74..05b2c6df2e2 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -157,7 +157,8 @@  private:
 class sarif_builder
 {
 public:
-  sarif_builder (diagnostic_context *context);
+  sarif_builder (diagnostic_context *context,
+		 bool formatted);
 
   void end_diagnostic (diagnostic_context *context,
 		       const diagnostic_info &diagnostic,
@@ -250,6 +251,8 @@  private:
   hash_set <int_hash <int, 0, 1> > m_cwe_id_set;
 
   int m_tabstop;
+
+  bool m_formatted;
 };
 
 /* class sarif_object : public json::object.  */
@@ -401,7 +404,8 @@  sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread)
 
 /* sarif_builder's ctor.  */
 
-sarif_builder::sarif_builder (diagnostic_context *context)
+sarif_builder::sarif_builder (diagnostic_context *context,
+			      bool formatted)
 : m_context (context),
   m_invocation_obj (new sarif_invocation ()),
   m_results_array (new json::array ()),
@@ -409,7 +413,8 @@  sarif_builder::sarif_builder (diagnostic_context *context)
   m_seen_any_relative_paths (false),
   m_rule_id_set (),
   m_rules_arr (new json::array ()),
-  m_tabstop (context->m_tabstop)
+  m_tabstop (context->m_tabstop),
+  m_formatted (formatted)
 {
 }
 
@@ -472,7 +477,7 @@  sarif_builder::flush_to_file (FILE *outf)
 {
   m_invocation_obj->prepare_to_flush (m_context);
   json::object *top = make_top_level_object (m_invocation_obj, m_results_array);
-  top->dump (outf);
+  top->dump (outf, m_formatted);
   m_invocation_obj = NULL;
   m_results_array = NULL;
   fprintf (outf, "\n");
@@ -1721,9 +1726,10 @@  public:
   }
 
 protected:
-  sarif_output_format (diagnostic_context &context)
+  sarif_output_format (diagnostic_context &context,
+		       bool formatted)
   : diagnostic_output_format (context),
-    m_builder (&context)
+    m_builder (&context, formatted)
   {}
 
   sarif_builder m_builder;
@@ -1732,8 +1738,10 @@  protected:
 class sarif_stream_output_format : public sarif_output_format
 {
 public:
-  sarif_stream_output_format (diagnostic_context &context, FILE *stream)
-  : sarif_output_format (context),
+  sarif_stream_output_format (diagnostic_context &context,
+			      bool formatted,
+			      FILE *stream)
+  : sarif_output_format (context, formatted),
     m_stream (stream)
   {
   }
@@ -1749,8 +1757,9 @@  class sarif_file_output_format : public sarif_output_format
 {
 public:
   sarif_file_output_format (diagnostic_context &context,
-			   const char *base_file_name)
-  : sarif_output_format (context),
+			    bool formatted,
+			    const char *base_file_name)
+  : sarif_output_format (context, formatted),
     m_base_file_name (xstrdup (base_file_name))
   {
   }
@@ -1801,10 +1810,12 @@  diagnostic_output_format_init_sarif (diagnostic_context *context)
 /* Populate CONTEXT in preparation for SARIF output to stderr.  */
 
 void
-diagnostic_output_format_init_sarif_stderr (diagnostic_context *context)
+diagnostic_output_format_init_sarif_stderr (diagnostic_context *context,
+					    bool formatted)
 {
   diagnostic_output_format_init_sarif (context);
   context->set_output_format (new sarif_stream_output_format (*context,
+							      formatted,
 							      stderr));
 }
 
@@ -1813,10 +1824,12 @@  diagnostic_output_format_init_sarif_stderr (diagnostic_context *context)
 
 void
 diagnostic_output_format_init_sarif_file (diagnostic_context *context,
+					  bool formatted,
 					  const char *base_file_name)
 {
   diagnostic_output_format_init_sarif (context);
   context->set_output_format (new sarif_file_output_format (*context,
+							    formatted,
 							    base_file_name));
 }
 
@@ -1824,9 +1837,11 @@  diagnostic_output_format_init_sarif_file (diagnostic_context *context,
 
 void
 diagnostic_output_format_init_sarif_stream (diagnostic_context *context,
+					    bool formatted,
 					    FILE *stream)
 {
   diagnostic_output_format_init_sarif (context);
   context->set_output_format (new sarif_stream_output_format (*context,
+							      formatted,
 							      stream));
 }
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 5854c89a387..b5b6a760ccf 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -2429,7 +2429,8 @@  diagnostic_text_output_format::on_diagram (const diagnostic_diagram &diagram)
 void
 diagnostic_output_format_init (diagnostic_context *context,
 			       const char *base_file_name,
-			       enum diagnostics_output_format format)
+			       enum diagnostics_output_format format,
+			       bool json_formatting)
 {
   switch (format)
     {
@@ -2440,19 +2441,25 @@  diagnostic_output_format_init (diagnostic_context *context,
       break;
 
     case DIAGNOSTICS_OUTPUT_FORMAT_JSON_STDERR:
-      diagnostic_output_format_init_json_stderr (context);
+      diagnostic_output_format_init_json_stderr (context,
+						 json_formatting);
       break;
 
     case DIAGNOSTICS_OUTPUT_FORMAT_JSON_FILE:
-      diagnostic_output_format_init_json_file (context, base_file_name);
+      diagnostic_output_format_init_json_file (context,
+					       json_formatting,
+					       base_file_name);
       break;
 
     case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_STDERR:
-      diagnostic_output_format_init_sarif_stderr (context);
+      diagnostic_output_format_init_sarif_stderr (context,
+						  json_formatting);
       break;
 
     case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE:
-      diagnostic_output_format_init_sarif_file (context, base_file_name);
+      diagnostic_output_format_init_sarif_file (context,
+						json_formatting,
+						base_file_name);
       break;
     }
 }
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 4fc31438b16..80e53ec92b0 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -1062,14 +1062,20 @@  extern char *build_message_string (const char *, ...) ATTRIBUTE_PRINTF_1;
 
 extern void diagnostic_output_format_init (diagnostic_context *,
 					   const char *base_file_name,
-					   enum diagnostics_output_format);
-extern void diagnostic_output_format_init_json_stderr (diagnostic_context *context);
+					   enum diagnostics_output_format,
+					   bool json_formatting);
+extern void diagnostic_output_format_init_json_stderr (diagnostic_context *context,
+						       bool formatted);
 extern void diagnostic_output_format_init_json_file (diagnostic_context *context,
+						     bool formatted,
 						     const char *base_file_name);
-extern void diagnostic_output_format_init_sarif_stderr (diagnostic_context *context);
+extern void diagnostic_output_format_init_sarif_stderr (diagnostic_context *context,
+							bool formatted);
 extern void diagnostic_output_format_init_sarif_file (diagnostic_context *context,
+						      bool formatted,
 						      const char *base_file_name);
 extern void diagnostic_output_format_init_sarif_stream (diagnostic_context *context,
+							bool formatted,
 							FILE *stream);
 
 /* Compute the number of digits in the decimal representation of an integer.  */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ee5002cc75d..166db887e83 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -305,6 +305,7 @@  Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-urls=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]}
+-fno-diagnostics-json-formatting
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers
 -fno-diagnostics-show-cwe
@@ -5719,8 +5720,7 @@  where the JSON is emitted to - with the former, the JSON is emitted to stderr,
 whereas with @samp{json-file} it is written to @file{@var{source}.gcc.json}.
 
 The emitted JSON consists of a top-level JSON array containing JSON objects
-representing the diagnostics.  The JSON is emitted as one line, without
-formatting; the examples below have been formatted for clarity.
+representing the diagnostics.
 
 Diagnostics can have child diagnostics.  For example, this error and note:
 
@@ -5983,6 +5983,24 @@  Diagnostics have a boolean attribute @code{escape-source}, hinting whether
 non-ASCII bytes should be escaped when printing the pertinent lines of
 source code (@code{true} for diagnostics involving source encoding issues).
 
+@opindex fno-diagnostics-json-formatting
+@opindex fdiagnostics-json-formatting
+@item -fno-diagnostics-json-formatting
+By default, when JSON is emitted for diagnostics (via
+@option{-fdiagnostics-format=sarif-stderr},
+@option{-fdiagnostics-format=sarif-file},
+@option{-fdiagnostics-format=json},
+@option{-fdiagnostics-format=json-stderr},
+@option{-fdiagnostics-format=json-file}),
+GCC will add newlines and indentation to visually emphasize the
+hierarchical structure of the JSON.
+
+Use @option{-fno-diagnostics-json-formatting} to suppress this whitespace.
+It must be passed before the option it is to affect.
+
+This is intended for compatibility with tools that do not expect the output
+to contain newlines, such as that emitted by older GCC releases.
+
 @end table
 
 @node Warning Options
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 9f21ad9453e..c970c26e3db 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -4357,7 +4357,8 @@  driver_handle_option (struct gcc_options *opts,
 	  const char *basename = (opts->x_dump_base_name ? opts->x_dump_base_name
 				  : opts->x_main_input_basename);
 	  diagnostic_output_format_init (dc, basename,
-					 (enum diagnostics_output_format)value);
+					 (enum diagnostics_output_format)value,
+					 opts->x_flag_diagnostics_json_formatting);
 	  break;
 	}
 
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 8b4748d3a1a..6d4a7bde53a 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -1600,13 +1600,13 @@  generate_results (const char *file_name)
     {
       if (flag_use_stdout)
 	{
-	  root->dump (stdout);
+	  root->dump (stdout, false);
 	  printf ("\n");
 	}
       else
 	{
 	  pretty_printer pp;
-	  root->print (&pp);
+	  root->print (&pp, false);
 	  pp_formatted_text (&pp);
 
 	  fnotice (stdout, "Creating '%s'\n",
diff --git a/gcc/json.cc b/gcc/json.cc
index d0f157f0dfe..90ddd7ab3b1 100644
--- a/gcc/json.cc
+++ b/gcc/json.cc
@@ -32,17 +32,15 @@  using namespace json;
 
 /* Dump this json::value tree to OUTF.
 
-   No formatting is done.
-
    The key/value pairs of json::objects are printed in the order
    in which the keys were originally inserted.  */
 
 void
-value::dump (FILE *outf) const
+value::dump (FILE *outf, bool formatted) const
 {
   pretty_printer pp;
   pp_buffer (&pp)->stream = outf;
-  print (&pp);
+  print (&pp, formatted);
   pp_flush (&pp);
 }
 
@@ -63,9 +61,11 @@  object::~object ()
 /* Implementation of json::value::print for json::object.  */
 
 void
-object::print (pretty_printer *pp) const
+object::print (pretty_printer *pp, bool formatted) const
 {
   pp_character (pp, '{');
+  if (formatted)
+    pp_indentation (pp) += 1;
 
   /* Iterate in the order that the keys were inserted.  */
   unsigned i;
@@ -73,15 +73,31 @@  object::print (pretty_printer *pp) const
   FOR_EACH_VEC_ELT (m_keys, i, key)
     {
       if (i > 0)
-	pp_string (pp, ", ");
+	{
+	  pp_string (pp, ",");
+	  if (formatted)
+	    {
+	      pp_newline (pp);
+	      pp_indent (pp);
+	    }
+	  else
+	    pp_space (pp);
+	}
       map_t &mut_map = const_cast<map_t &> (m_map);
       value *value = *mut_map.get (key);
       pp_doublequote (pp);
       pp_string (pp, key); // FIXME: escaping?
       pp_doublequote (pp);
       pp_string (pp, ": ");
-      value->print (pp);
+      const int indent = strlen (key) + 4;
+      if (formatted)
+	pp_indentation (pp) += indent;
+      value->print (pp, formatted);
+      if (formatted)
+	pp_indentation (pp) -= indent;
     }
+  if (formatted)
+    pp_indentation (pp) -= 1;
   pp_character (pp, '}');
 }
 
@@ -180,17 +196,30 @@  array::~array ()
 /* Implementation of json::value::print for json::array.  */
 
 void
-array::print (pretty_printer *pp) const
+array::print (pretty_printer *pp, bool formatted) const
 {
   pp_character (pp, '[');
+  if (formatted)
+    pp_indentation (pp) += 1;
   unsigned i;
   value *v;
   FOR_EACH_VEC_ELT (m_elements, i, v)
     {
       if (i)
-	pp_string (pp, ", ");
-      v->print (pp);
+	{
+	  pp_string (pp, ",");
+	  if (formatted)
+	    {
+	      pp_newline (pp);
+	      pp_indent (pp);
+	    }
+	  else
+	    pp_space (pp);
+	}
+      v->print (pp, formatted);
     }
+  if (formatted)
+    pp_indentation (pp) -= 1;
   pp_character (pp, ']');
 }
 
@@ -208,7 +237,8 @@  array::append (value *v)
 /* Implementation of json::value::print for json::float_number.  */
 
 void
-float_number::print (pretty_printer *pp) const
+float_number::print (pretty_printer *pp,
+		     bool formatted ATTRIBUTE_UNUSED) const
 {
   char tmp[1024];
   snprintf (tmp, sizeof (tmp), "%g", m_value);
@@ -220,7 +250,8 @@  float_number::print (pretty_printer *pp) const
 /* Implementation of json::value::print for json::integer_number.  */
 
 void
-integer_number::print (pretty_printer *pp) const
+integer_number::print (pretty_printer *pp,
+		       bool formatted ATTRIBUTE_UNUSED) const
 {
   char tmp[1024];
   snprintf (tmp, sizeof (tmp), "%ld", m_value);
@@ -250,7 +281,8 @@  string::string (const char *utf8, size_t len)
 /* Implementation of json::value::print for json::string.  */
 
 void
-string::print (pretty_printer *pp) const
+string::print (pretty_printer *pp,
+	       bool formatted ATTRIBUTE_UNUSED) const
 {
   pp_character (pp, '"');
   for (size_t i = 0; i != m_len; ++i)
@@ -294,7 +326,8 @@  string::print (pretty_printer *pp) const
 /* Implementation of json::value::print for json::literal.  */
 
 void
-literal::print (pretty_printer *pp) const
+literal::print (pretty_printer *pp,
+		bool formatted ATTRIBUTE_UNUSED) const
 {
   switch (m_kind)
     {
@@ -322,15 +355,18 @@  namespace selftest {
 /* Verify that JV->print () prints EXPECTED_JSON.  */
 
 static void
-assert_print_eq (const location &loc, const json::value &jv, const char *expected_json)
+assert_print_eq (const location &loc,
+		 const json::value &jv,
+		 bool formatted,
+		 const char *expected_json)
 {
   pretty_printer pp;
-  jv.print (&pp);
+  jv.print (&pp, formatted);
   ASSERT_STREQ_AT (loc, expected_json, pp_formatted_text (&pp));
 }
 
-#define ASSERT_PRINT_EQ(JV, EXPECTED_JSON) \
-  assert_print_eq (SELFTEST_LOCATION, JV, EXPECTED_JSON)
+#define ASSERT_PRINT_EQ(JV, FORMATTED, EXPECTED_JSON)	\
+  assert_print_eq (SELFTEST_LOCATION, JV, FORMATTED, EXPECTED_JSON)
 
 /* Verify that object::get works as expected.  */
 
@@ -354,7 +390,11 @@  test_writing_objects ()
   obj.set_string ("baz", "quux");
   /* This test relies on json::object writing out key/value pairs
      in key-insertion order.  */
-  ASSERT_PRINT_EQ (obj, "{\"foo\": \"bar\", \"baz\": \"quux\"}");
+  ASSERT_PRINT_EQ (obj, true,
+		   "{\"foo\": \"bar\",\n"
+		   " \"baz\": \"quux\"}");
+  ASSERT_PRINT_EQ (obj, false,
+		   "{\"foo\": \"bar\", \"baz\": \"quux\"}");
 }
 
 /* Verify that JSON arrays are written correctly.  */
@@ -363,13 +403,17 @@  static void
 test_writing_arrays ()
 {
   array arr;
-  ASSERT_PRINT_EQ (arr, "[]");
+  ASSERT_PRINT_EQ (arr, true, "[]");
 
   arr.append (new json::string ("foo"));
-  ASSERT_PRINT_EQ (arr, "[\"foo\"]");
+  ASSERT_PRINT_EQ (arr, true, "[\"foo\"]");
 
   arr.append (new json::string ("bar"));
-  ASSERT_PRINT_EQ (arr, "[\"foo\", \"bar\"]");
+  ASSERT_PRINT_EQ (arr, true,
+		   "[\"foo\",\n"
+		   " \"bar\"]");
+  ASSERT_PRINT_EQ (arr, false,
+		   "[\"foo\", \"bar\"]");
 }
 
 /* Verify that JSON numbers are written correctly.  */
@@ -377,20 +421,20 @@  test_writing_arrays ()
 static void
 test_writing_float_numbers ()
 {
-  ASSERT_PRINT_EQ (float_number (0), "0");
-  ASSERT_PRINT_EQ (float_number (42), "42");
-  ASSERT_PRINT_EQ (float_number (-100), "-100");
-  ASSERT_PRINT_EQ (float_number (123456789), "1.23457e+08");
+  ASSERT_PRINT_EQ (float_number (0), true, "0");
+  ASSERT_PRINT_EQ (float_number (42), true, "42");
+  ASSERT_PRINT_EQ (float_number (-100), true, "-100");
+  ASSERT_PRINT_EQ (float_number (123456789), true, "1.23457e+08");
 }
 
 static void
 test_writing_integer_numbers ()
 {
-  ASSERT_PRINT_EQ (integer_number (0), "0");
-  ASSERT_PRINT_EQ (integer_number (42), "42");
-  ASSERT_PRINT_EQ (integer_number (-100), "-100");
-  ASSERT_PRINT_EQ (integer_number (123456789), "123456789");
-  ASSERT_PRINT_EQ (integer_number (-123456789), "-123456789");
+  ASSERT_PRINT_EQ (integer_number (0), true, "0");
+  ASSERT_PRINT_EQ (integer_number (42), true, "42");
+  ASSERT_PRINT_EQ (integer_number (-100), true, "-100");
+  ASSERT_PRINT_EQ (integer_number (123456789), true, "123456789");
+  ASSERT_PRINT_EQ (integer_number (-123456789), true, "-123456789");
 }
 
 /* Verify that JSON strings are written correctly.  */
@@ -399,16 +443,16 @@  static void
 test_writing_strings ()
 {
   string foo ("foo");
-  ASSERT_PRINT_EQ (foo, "\"foo\"");
+  ASSERT_PRINT_EQ (foo, true, "\"foo\"");
 
   string contains_quotes ("before \"quoted\" after");
-  ASSERT_PRINT_EQ (contains_quotes, "\"before \\\"quoted\\\" after\"");
+  ASSERT_PRINT_EQ (contains_quotes, true, "\"before \\\"quoted\\\" after\"");
 
   const char data[] = {'a', 'b', 'c', 'd', '\0', 'e', 'f'};
   string not_terminated (data, 3);
-  ASSERT_PRINT_EQ (not_terminated, "\"abc\"");
+  ASSERT_PRINT_EQ (not_terminated, true, "\"abc\"");
   string embedded_null (data, sizeof data);
-  ASSERT_PRINT_EQ (embedded_null, "\"abcd\\0ef\"");
+  ASSERT_PRINT_EQ (embedded_null, true, "\"abcd\\0ef\"");
 }
 
 /* Verify that JSON literals are written correctly.  */
@@ -416,12 +460,50 @@  test_writing_strings ()
 static void
 test_writing_literals ()
 {
-  ASSERT_PRINT_EQ (literal (JSON_TRUE), "true");
-  ASSERT_PRINT_EQ (literal (JSON_FALSE), "false");
-  ASSERT_PRINT_EQ (literal (JSON_NULL), "null");
+  ASSERT_PRINT_EQ (literal (JSON_TRUE), true, "true");
+  ASSERT_PRINT_EQ (literal (JSON_FALSE), true, "false");
+  ASSERT_PRINT_EQ (literal (JSON_NULL), true, "null");
 
-  ASSERT_PRINT_EQ (literal (true), "true");
-  ASSERT_PRINT_EQ (literal (false), "false");
+  ASSERT_PRINT_EQ (literal (true), true, "true");
+  ASSERT_PRINT_EQ (literal (false), true, "false");
+}
+
+/* Verify that nested values are formatted correctly when written.  */
+
+static void
+test_formatting ()
+{
+  object obj;
+  object *child = new object;
+  object *grandchild = new object;
+
+  obj.set_string ("str", "bar");
+  obj.set ("child", child);
+  obj.set_integer ("int", 42);
+
+  child->set ("grandchild", grandchild);
+  child->set_integer ("int", 1776);
+
+  array *arr = new array;
+  for (int i = 0; i < 3; i++)
+    arr->append (new integer_number (i));
+  grandchild->set ("arr", arr);
+  grandchild->set_integer ("int", 1066);
+
+  /* This test relies on json::object writing out key/value pairs
+     in key-insertion order.  */
+  ASSERT_PRINT_EQ (obj, true,
+		   ("{\"str\": \"bar\",\n"
+		    " \"child\": {\"grandchild\": {\"arr\": [0,\n"
+		    "                                  1,\n"
+		    "                                  2],\n"
+		    "                          \"int\": 1066},\n"
+		    "           \"int\": 1776},\n"
+		    " \"int\": 42}"));
+  ASSERT_PRINT_EQ (obj, false,
+		   ("{\"str\": \"bar\", \"child\": {\"grandchild\":"
+		    " {\"arr\": [0, 1, 2], \"int\": 1066},"
+		    " \"int\": 1776}, \"int\": 42}"));
 }
 
 /* Run all of the selftests within this file.  */
@@ -436,6 +518,7 @@  json_cc_tests ()
   test_writing_integer_numbers ();
   test_writing_strings ();
   test_writing_literals ();
+  test_formatting ();
 }
 
 } // namespace selftest
diff --git a/gcc/json.h b/gcc/json.h
index 6fadd119ba5..862e5676a63 100644
--- a/gcc/json.h
+++ b/gcc/json.h
@@ -80,9 +80,9 @@  class value
  public:
   virtual ~value () {}
   virtual enum kind get_kind () const = 0;
-  virtual void print (pretty_printer *pp) const = 0;
+  virtual void print (pretty_printer *pp, bool formatted) const = 0;
 
-  void dump (FILE *) const;
+  void dump (FILE *, bool formatted) const;
 };
 
 /* Subclass of value for objects: a collection of key/value pairs
@@ -97,7 +97,7 @@  class object : public value
   ~object ();
 
   enum kind get_kind () const final override { return JSON_OBJECT; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
   void set (const char *key, value *v);
   value *get (const char *key) const;
@@ -126,7 +126,7 @@  class array : public value
   ~array ();
 
   enum kind get_kind () const final override { return JSON_ARRAY; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
   void append (value *v);
 
@@ -142,7 +142,7 @@  class float_number : public value
   float_number (double value) : m_value (value) {}
 
   enum kind get_kind () const final override { return JSON_FLOAT; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
   double get () const { return m_value; }
 
@@ -158,7 +158,7 @@  class integer_number : public value
   integer_number (long value) : m_value (value) {}
 
   enum kind get_kind () const final override { return JSON_INTEGER; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
   long get () const { return m_value; }
 
@@ -177,7 +177,7 @@  class string : public value
   ~string () { free (m_utf8); }
 
   enum kind get_kind () const final override { return JSON_STRING; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
   const char *get_string () const { return m_utf8; }
   size_t get_length () const { return m_len; }
@@ -199,7 +199,7 @@  class literal : public value
   literal (bool value): m_kind (value ? JSON_TRUE : JSON_FALSE) {}
 
   enum kind get_kind () const final override { return m_kind; }
-  void print (pretty_printer *pp) const final override;
+  void print (pretty_printer *pp, bool formatted) const final override;
 
  private:
   enum kind m_kind;
diff --git a/gcc/optinfo-emit-json.cc b/gcc/optinfo-emit-json.cc
index 11cad42a433..b181d6fb15d 100644
--- a/gcc/optinfo-emit-json.cc
+++ b/gcc/optinfo-emit-json.cc
@@ -103,7 +103,7 @@  void
 optrecord_json_writer::write () const
 {
   pretty_printer pp;
-  m_root_tuple->print (&pp);
+  m_root_tuple->print (&pp, false);
 
   bool emitted_error = false;
   char *filename = concat (dump_base_name, ".opt-record.json.gz", NULL);
@@ -466,7 +466,7 @@  test_building_json_from_dump_calls ()
 
   /* Verify that the json is sane.  */
   pretty_printer pp;
-  json_obj->print (&pp);
+  json_obj->print (&pp, false);
   const char *json_str = pp_formatted_text (&pp);
   ASSERT_STR_CONTAINS (json_str, "impl_location");
   ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\"");
diff --git a/gcc/opts.cc b/gcc/opts.cc
index 5d5efaf1b9e..7a3830caaa3 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -2949,7 +2949,8 @@  common_handle_option (struct gcc_options *opts,
 	  const char *basename = (opts->x_dump_base_name ? opts->x_dump_base_name
 				  : opts->x_main_input_basename);
 	  diagnostic_output_format_init (dc, basename,
-					 (enum diagnostics_output_format)value);
+					 (enum diagnostics_output_format)value,
+					 opts->x_flag_diagnostics_json_formatting);
 	  break;
 	}
 
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-1.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-1.c
index 6bab30e3e6c..c95218c3cfe 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-1.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-1.c
@@ -3,28 +3,20 @@ 
 
 #error message
 
-/* Use dg-regexp to consume the JSON output starting with
-   the innermost values, and working outwards.  */
-
-/* { dg-regexp "\"kind\": \"error\"" } */
-/* { dg-regexp "\"column-origin\": 1" } */
-/* { dg-regexp "\"escape-source\": false" } */
-/* { dg-regexp "\"message\": \"#error message\"" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-1.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 2" } */
-/* { dg-regexp "\"display-column\": 2" } */
-/* { dg-regexp "\"byte-column\": 2" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-1.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 6" } */
-/* { dg-regexp "\"display-column\": 6" } */
-/* { dg-regexp "\"byte-column\": 6" } */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-/* { dg-regexp "\"children\": \[\[\]\[\]\]" } */
-/* { dg-regexp "\[\[\{\}, \]*\]" } */
+/* { dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "#error message",
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 6,
+                            "byte-column": 6,
+                            "column": 6}}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-2.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-2.c
index 3c12103c9f8..a8828b7b2df 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-2.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-2.c
@@ -3,30 +3,24 @@ 
 
 #warning message
 
-/* Use dg-regexp to consume the JSON output starting with
-   the innermost values, and working outwards.  */
-
-/* { dg-regexp "\"kind\": \"warning\"" } */
-/* { dg-regexp "\"column-origin\": 1" } */
-/* { dg-regexp "\"escape-source\": false" } */
-/* { dg-regexp "\"message\": \"#warning message\"" } */
-/* { dg-regexp "\"option\": \"-Wcpp\"" } */
-/* { dg-regexp "\"option_url\": \"https:\[^\n\r\"\]*#index-Wcpp\"" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-2.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 2" } */
-/* { dg-regexp "\"display-column\": 2" } */
-/* { dg-regexp "\"byte-column\": 2" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-2.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 8" } */
-/* { dg-regexp "\"display-column\": 8" } */
-/* { dg-regexp "\"byte-column\": 8" } */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-/* { dg-regexp "\"children\": \[\[\]\[\]\]" } */
-/* { dg-regexp "\[\[\{\}, \]*\]" } */
+/* { dg-begin-multiline-output "" }
+[{"kind": "warning",
+  "message": "#warning message",
+  "option": "-Wcpp",
+   { dg-end-multiline-output "" } */
+/* { dg-regexp "  \"option_url\": \"https:\[^\n\r\"\]*#index-Wcpp\",\n" } */
+/* { dg-begin-multiline-output "" }
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 8,
+                            "byte-column": 8,
+                            "column": 8}}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-3.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-3.c
index 11d74624ff1..178bbf94b5b 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-3.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-3.c
@@ -3,30 +3,24 @@ 
 
 #warning message
 
-/* Use dg-regexp to consume the JSON output starting with
-   the innermost values, and working outwards.  */
-
-/* { dg-regexp "\"kind\": \"error\"" } */
-/* { dg-regexp "\"column-origin\": 1" } */
-/* { dg-regexp "\"escape-source\": false" } */
-/* { dg-regexp "\"message\": \"#warning message\"" } */
-/* { dg-regexp "\"option\": \"-Werror=cpp\"" } */
-/* { dg-regexp "\"option_url\": \"https:\[^\n\r\"\]*#index-Wcpp\"" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-3.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 2" } */
-/* { dg-regexp "\"display-column\": 2" } */
-/* { dg-regexp "\"byte-column\": 2" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-3.c\"" } */
-/* { dg-regexp "\"line\": 4" } */
-/* { dg-regexp "\"column\": 8" } */
-/* { dg-regexp "\"display-column\": 8" } */
-/* { dg-regexp "\"byte-column\": 8" } */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-/* { dg-regexp "\"children\": \[\[\]\[\]\]" } */
-/* { dg-regexp "\[\[\{\}, \]*\]" } */
+/* { dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "#warning message",
+  "option": "-Werror=cpp",
+   { dg-end-multiline-output "" } */
+/* { dg-regexp "  \"option_url\": \"https:\[^\n\r\"\]*#index-Wcpp\",\n" } */
+/* { dg-begin-multiline-output "" }
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 8,
+                            "byte-column": 8,
+                            "column": 8}}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-4.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-4.c
index cec1cf924b4..899a03f0e5e 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-4.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-4.c
@@ -9,63 +9,36 @@  int test (void)
   return 5;
 }
 
-/* Use dg-regexp to consume the JSON output starting with
-   the innermost values, and working outwards.  */
-
-/* Verify nested diagnostics.  */
-
-/* The nested diagnostic.  */
-
-/* { dg-regexp "\"kind\": \"note\"" } */
-/* { dg-regexp "\"message\": \"...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'\"" } */
-/* { dg-regexp "\"escape-source\": false" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-4.c\"" } */
-/* { dg-regexp "\"line\": 8" } */
-/* { dg-regexp "\"column\": 5" } */
-/* { dg-regexp "\"display-column\": 5" } */
-/* { dg-regexp "\"byte-column\": 5" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-4.c\"" } */
-/* { dg-regexp "\"line\": 8" } */
-/* { dg-regexp "\"column\": 10" } */
-/* { dg-regexp "\"display-column\": 10" } */
-/* { dg-regexp "\"byte-column\": 10" } */
-
-/* The outer diagnostic.  */
-
-/* { dg-regexp "\"kind\": \"warning\"" } */
-/* { dg-regexp "\"column-origin\": 1" } */
-/* { dg-regexp "\"message\": \"this 'if' clause does not guard...\"" } */
-/* { dg-regexp "\"escape-source\": false" } */
-/* { dg-regexp "\"option\": \"-Wmisleading-indentation\"" } */
-/* { dg-regexp "\"option_url\": \"https:\[^\n\r\"\]*#index-Wmisleading-indentation\"" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-4.c\"" } */
-/* { dg-regexp "\"line\": 6" } */
-/* { dg-regexp "\"column\": 3" } */
-/* { dg-regexp "\"display-column\": 3" } */
-/* { dg-regexp "\"byte-column\": 3" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-4.c\"" } */
-/* { dg-regexp "\"line\": 6" } */
-/* { dg-regexp "\"column\": 4" } */
-/* { dg-regexp "\"display-column\": 4" } */
-/* { dg-regexp "\"byte-column\": 4" } */
-
-/* More from the nested diagnostic (we can't guarantee what order the
-   "file" keys are consumed).  */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-
-/* More from the outer diagnostic.  */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-
-/* { dg-regexp "\"children\": \[\[\{\}, \]*\]" } */
-/* { dg-regexp "\[\[\{\}, \]*\]" } */
-
+/* { dg-begin-multiline-output "" }
+[{"kind": "warning",
+  "message": "this 'if' clause does not guard...",
+  "option": "-Wmisleading-indentation",
+   { dg-end-multiline-output "" } */
+/* { dg-regexp "  \"option_url\": \"https:\[^\n\r\"\]*#index-Wmisleading-indentation\",\n" } */
+/* { dg-begin-multiline-output "" }
+  "children": [{"kind": "note",
+                "message": "...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'",
+                "locations": [{"caret": {"file":
+                                         "line": 8,
+                                         "display-column": 5,
+                                         "byte-column": 5,
+                                         "column": 5},
+                               "finish": {"file":
+                                          "line": 8,
+                                          "display-column": 10,
+                                          "byte-column": 10,
+                                          "column": 10}}],
+                "escape-source": false}],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 6,
+                           "display-column": 3,
+                           "byte-column": 3,
+                           "column": 3},
+                 "finish": {"file":
+                            "line": 6,
+                            "display-column": 4,
+                            "byte-column": 4,
+                            "column": 4}}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-5.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-5.c
index 86f8c5fb374..ed3139c7f1b 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-5.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-5.c
@@ -8,61 +8,31 @@  int test (struct s *ptr)
   return ptr->colour;
 }
 
-/* Verify fix-it hints.
-
-   Use dg-regexp to consume the JSON output from start to
-   finish, relying on the ordering of the keys.
-   The following uses indentation to visualize the structure
-   of the JSON (although the actual output is all on one line).
-
-  { dg-regexp {\[} }
-    { dg-regexp {\{} }
-     { dg-regexp {"kind": "error"} }
-     { dg-regexp {, "message": "'struct s' has no member named 'colour'; did you mean 'color'\?"} }
-     { dg-regexp {, "children": \[\]} }
-     { dg-regexp {, "column-origin": 1} }
-     { dg-regexp {, "locations": } }
-       { dg-regexp {\[} }
-         { dg-regexp {\{} }
-           { dg-regexp {"caret": } }
-             { dg-regexp {\{} }
-               { dg-regexp {"file": "[^\n\r"]*diagnostic-format-json-5.c"} }
-               { dg-regexp {, "line": 8} }
-               { dg-regexp {, "display-column": 15} }
-               { dg-regexp {, "byte-column": 15} }
-               { dg-regexp {, "column": 15} }
-             { dg-regexp {\}} }
-           { dg-regexp {, "finish": } }
-             { dg-regexp {\{} }
-               { dg-regexp {"file": "[^\n\r"]*diagnostic-format-json-5.c"} }
-               { dg-regexp {, "line": 8} }
-               { dg-regexp {, "display-column": 20} }
-               { dg-regexp {, "byte-column": 20} }
-               { dg-regexp {, "column": 20} }
-             { dg-regexp {\}} }
-           { dg-regexp {\}} }
-         { dg-regexp {\]} }
-       { dg-regexp {, "fixits": } }
-       { dg-regexp {\[} }
-         { dg-regexp {\{} }
-           { dg-regexp {"start": } }
-             { dg-regexp {\{} }
-               { dg-regexp {"file": "[^\n\r"]*diagnostic-format-json-5.c"} }
-               { dg-regexp {, "line": 8} }
-               { dg-regexp {, "display-column": 15} }
-               { dg-regexp {, "byte-column": 15} }
-               { dg-regexp {, "column": 15} }
-             { dg-regexp {\}} }
-           { dg-regexp {, "next": } }
-             { dg-regexp {\{} }
-               { dg-regexp {"file": "[^\n\r"]*diagnostic-format-json-5.c"} }
-               { dg-regexp {, "line": 8} }
-               { dg-regexp {, "display-column": 21} }
-               { dg-regexp {, "byte-column": 21} }
-               { dg-regexp {, "column": 21} }
-             { dg-regexp {\}} }
-           { dg-regexp {, "string": "color"} }
-         { dg-regexp {\}} }
-       { dg-regexp {\]} }
-     { dg-regexp {, "escape-source": false\}} }
-   { dg-regexp {\]} }  */
+/* { dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "'struct s' has no member named 'colour'; did you mean 'color'?",
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 8,
+                           "display-column": 15,
+                           "byte-column": 15,
+                           "column": 15},
+                 "finish": {"file":
+                            "line": 8,
+                            "display-column": 20,
+                            "byte-column": 20,
+                            "column": 20}}],
+  "fixits": [{"start": {"file":
+                        "line": 8,
+                        "display-column": 15,
+                        "byte-column": 15,
+                        "column": 15},
+              "next": {"file":
+                       "line": 8,
+                       "display-column": 21,
+                       "byte-column": 21,
+                       "column": 21},
+              "string": "color"}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-json-stderr-1.c b/gcc/testsuite/c-c++-common/diagnostic-format-json-stderr-1.c
index bcfa92110f5..e798c6b21e1 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-json-stderr-1.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-json-stderr-1.c
@@ -5,28 +5,20 @@ 
 
 #error message
 
-/* Use dg-regexp to consume the JSON output starting with
-   the innermost values, and working outwards.  */
-
-/* { dg-regexp "\"kind\": \"error\"" } */
-/* { dg-regexp "\"column-origin\": 1" } */
-/* { dg-regexp "\"escape-source\": false" } */
-/* { dg-regexp "\"message\": \"#error message\"" } */
-
-/* { dg-regexp "\"caret\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-stderr-1.c\"" } */
-/* { dg-regexp "\"line\": 6" } */
-/* { dg-regexp "\"column\": 2" } */
-/* { dg-regexp "\"display-column\": 2" } */
-/* { dg-regexp "\"byte-column\": 2" } */
-
-/* { dg-regexp "\"finish\": \{" } */
-/* { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-stderr-1.c\"" } */
-/* { dg-regexp "\"line\": 6" } */
-/* { dg-regexp "\"column\": 6" } */
-/* { dg-regexp "\"display-column\": 6" } */
-/* { dg-regexp "\"byte-column\": 6" } */
-
-/* { dg-regexp "\"locations\": \[\[\{\}, \]*\]" } */
-/* { dg-regexp "\"children\": \[\[\]\[\]\]" } */
-/* { dg-regexp "\[\[\{\}, \]*\]" } */
+/* { dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "#error message",
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 6,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 6,
+                            "display-column": 6,
+                            "byte-column": 6,
+                            "column": 6}}],
+  "escape-source": false}]
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/pr90462.C b/gcc/testsuite/g++.dg/pr90462.C
index 2585ba0dcdb..b35e41921a6 100644
--- a/gcc/testsuite/g++.dg/pr90462.C
+++ b/gcc/testsuite/g++.dg/pr90462.C
@@ -1,4 +1,4 @@ 
-/* { dg-options "-Wdeprecated-copy -fdiagnostics-format=json" } */
+/* { dg-options "-Wdeprecated-copy -fno-diagnostics-json-formatting -fdiagnostics-format=json" } */
 
 template <class> class b;
 struct B {
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-sarif-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-sarif-1.c
index 3d798e687e6..19ac89f2b67 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-sarif-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-sarif-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-additional-options "-fdiagnostics-format=sarif-file" } */
+/* { dg-additional-options " -fno-diagnostics-json-formatting -fdiagnostics-format=sarif-file" } */
 
 #include <stdlib.h>
 
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
index 6971d7cb38b..a315d208cab 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
@@ -32,7 +32,44 @@  make_a_list_of_random_ints_badly(PyObject *self,
   return list;
 }
 
-/* FIXME: test the events within a path.  */
-/* { dg-regexp "\"kind\": \"error\"" } */
-/* { dg-regexp "\"path\": " } */
-/* { dg-regexp ".*" } */
+/* { dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter",
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file": "
+                           "line": 29,
+                           "display-column": 5,
+                           "byte-column": 5,
+                           "column": 5},
+                 "finish": {"file": "
+                            "line": 29,
+                            "display-column": 29,
+                            "byte-column": 29,
+                            "column": 29}}],
+  "path": [{"location": {"file": "
+                         "line": 25,
+                         "display-column": 10,
+                         "byte-column": 10,
+                         "column": 10},
+            "description": "when 'PyList_New' fails, returning NULL",
+            "function": "make_a_list_of_random_ints_badly",
+            "depth": 0},
+           {"location": {"file": "
+                         "line": 27,
+                         "display-column": 17,
+                         "byte-column": 17,
+                         "column": 17},
+            "description": "when 'i < count'",
+            "function": "make_a_list_of_random_ints_badly",
+            "depth": 0},
+           {"location": {"file": "
+                         "line": 29,
+                         "display-column": 5,
+                         "byte-column": 5,
+                         "column": 5},
+            "description": "when calling 'PyList_Append', passing NULL from (1) as argument 1",
+            "function": "make_a_list_of_random_ints_badly",
+            "depth": 0}],
+  "escape-source": false}]
+{ dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gfortran.dg/diagnostic-format-json-1.F90 b/gcc/testsuite/gfortran.dg/diagnostic-format-json-1.F90
index 2993f7c852b..b8cd61cff23 100644
--- a/gcc/testsuite/gfortran.dg/diagnostic-format-json-1.F90
+++ b/gcc/testsuite/gfortran.dg/diagnostic-format-json-1.F90
@@ -3,29 +3,22 @@ 
 
 #error message
 
-! Use dg-regexp to consume the JSON output starting with
-! the innermost values, and working outwards.
-! We can't rely on any ordering of the keys.
-
-! { dg-regexp "\"kind\": \"error\"" }
-! { dg-regexp "\"column-origin\": 1" }
-! { dg-regexp "\"escape-source\": false" }
-! { dg-regexp "\"message\": \"#error message\"" }
-
-! { dg-regexp "\"caret\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-1.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 2" }
-! { dg-regexp "\"display-column\": 2" }
-! { dg-regexp "\"byte-column\": 2" }
-
-! { dg-regexp "\"finish\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-1.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 6" }
-! { dg-regexp "\"display-column\": 6" }
-! { dg-regexp "\"byte-column\": 6" }
-
-! { dg-regexp "\"locations\": \[\[\{\}, \]*\]" }
-! { dg-regexp "\"children\": \[\[\]\[\]\]" }
-! { dg-regexp "\[\[\{\}, \]*\]" }
+#if 0
+{ dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "#error message",
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 6,
+                            "byte-column": 6,
+                            "column": 6}}],
+  "escape-source": false}]
+{ dg-end-multiline-output "" }
+#endif  
diff --git a/gcc/testsuite/gfortran.dg/diagnostic-format-json-2.F90 b/gcc/testsuite/gfortran.dg/diagnostic-format-json-2.F90
index 1681462fa08..9ff1ef59b34 100644
--- a/gcc/testsuite/gfortran.dg/diagnostic-format-json-2.F90
+++ b/gcc/testsuite/gfortran.dg/diagnostic-format-json-2.F90
@@ -3,31 +3,24 @@ 
 
 #warning message
 
-! Use dg-regexp to consume the JSON output starting with
-! the innermost values, and working outwards.
-! We can't rely on any ordering of the keys. 
-
-! { dg-regexp "\"kind\": \"warning\"" }
-! { dg-regexp "\"column-origin\": 1" }
-! { dg-regexp "\"escape-source\": false" }
-! { dg-regexp "\"message\": \"#warning message\"" }
-! { dg-regexp "\"option\": \"-Wcpp\"" }
-! { dg-regexp "\"option_url\": \"\[^\n\r\"\]*#index-Wcpp\"" }
-
-! { dg-regexp "\"caret\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-2.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 2" }
-! { dg-regexp "\"display-column\": 2" }
-! { dg-regexp "\"byte-column\": 2" }
-
-! { dg-regexp "\"finish\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-2.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 8" }
-! { dg-regexp "\"display-column\": 8" }
-! { dg-regexp "\"byte-column\": 8" }
-
-! { dg-regexp "\"locations\": \[\[\{\}, \]*\]" }
-! { dg-regexp "\"children\": \[\[\]\[\]\]" }
-! { dg-regexp "\[\[\{\}, \]*\]" }
+#if 0
+{ dg-begin-multiline-output "" }
+[{"kind": "warning",
+  "message": "#warning message",
+  "option": "-Wcpp",
+  "option_url":
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 8,
+                            "byte-column": 8,
+                            "column": 8}}],
+  "escape-source": false}]
+{ dg-end-multiline-output "" }
+#endif  
diff --git a/gcc/testsuite/gfortran.dg/diagnostic-format-json-3.F90 b/gcc/testsuite/gfortran.dg/diagnostic-format-json-3.F90
index f0a67de76b0..750e186c8ac 100644
--- a/gcc/testsuite/gfortran.dg/diagnostic-format-json-3.F90
+++ b/gcc/testsuite/gfortran.dg/diagnostic-format-json-3.F90
@@ -3,31 +3,24 @@ 
 
 #warning message
 
-! Use dg-regexp to consume the JSON output starting with
-! the innermost values, and working outwards.
-! We can't rely on any ordering of the keys.
-
-! { dg-regexp "\"kind\": \"error\"" }
-! { dg-regexp "\"column-origin\": 1" }
-! { dg-regexp "\"escape-source\": false" }
-! { dg-regexp "\"message\": \"#warning message\"" }
-! { dg-regexp "\"option\": \"-Werror=cpp\"" }
-! { dg-regexp "\"option_url\": \"\[^\n\r\"\]*#index-Wcpp\"" }
-
-! { dg-regexp "\"caret\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-3.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 2" }
-! { dg-regexp "\"display-column\": 2" }
-! { dg-regexp "\"byte-column\": 2" }
-
-! { dg-regexp "\"finish\": \{" }
-! { dg-regexp "\"file\": \"\[^\n\r\"\]*diagnostic-format-json-3.F90\"" }
-! { dg-regexp "\"line\": 4" }
-! { dg-regexp "\"column\": 8" }
-! { dg-regexp "\"display-column\": 8" }
-! { dg-regexp "\"byte-column\": 8" }
-
-! { dg-regexp "\"locations\": \[\[\{\}, \]*\]" }
-! { dg-regexp "\"children\": \[\[\]\[\]\]" }
-! { dg-regexp "\[\[\{\}, \]*\]" }
+#if 0
+{ dg-begin-multiline-output "" }
+[{"kind": "error",
+  "message": "#warning message",
+  "option": "-Werror=cpp",
+  "option_url":
+  "children": [],
+  "column-origin": 1,
+  "locations": [{"caret": {"file":
+                           "line": 4,
+                           "display-column": 2,
+                           "byte-column": 2,
+                           "column": 2},
+                 "finish": {"file":
+                            "line": 4,
+                            "display-column": 8,
+                            "byte-column": 8,
+                            "column": 8}}],
+  "escape-source": false}]
+{ dg-end-multiline-output "" }
+#endif