public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: David Malcolm <dmalcolm@redhat.com>
To: gcc-patches@gcc.gnu.org
Cc: David Malcolm <dmalcolm@redhat.com>
Subject: [PATCH/RFC] json.cc: format JSON output
Date: Tue, 14 Nov 2023 19:54:57 -0500	[thread overview]
Message-ID: <20231115005457.3748674-1-dmalcolm@redhat.com> (raw)

Previously our JSON output emitted the JSON all on one line, with
no indentation or newlines 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, and it seems to be a quality-of-life
improvement for users.

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 considered adding params and an option to control this formatting, but
it seems something you'd always want enabled; we already gzip some of
our json output, so it seems unlikely to affect sizes.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Thoughts?

gcc/ChangeLog:
	* doc/invoke.texi (-fdiagnostics-format=json): Remove discussion
	about JSON output needing formatting.
	* json.cc (object::print): Add whitespace to format the JSON
	output.
	(array::print): Likewise.
	(selftest::test_writing_objects): Update expected output.
	(selftest::test_writing_arrays): Likewise.
	(selftest::test_formatting): New.
	(selftest::json_cc_tests): Call it.
	* optinfo-emit-json.cc
	(selftest::test_building_json_from_dump_calls): Update search
	string to reflect indentation.
---
 gcc/doc/invoke.texi      |  3 +-
 gcc/json.cc              | 62 +++++++++++++++++++++++++++++++++++++---
 gcc/optinfo-emit-json.cc |  3 +-
 3 files changed, 61 insertions(+), 7 deletions(-)

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1748afdbfe0a..0c4d27bd0241 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5712,8 +5712,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:
 
diff --git a/gcc/json.cc b/gcc/json.cc
index d0f157f0dfe7..d5a38c773f4d 100644
--- a/gcc/json.cc
+++ b/gcc/json.cc
@@ -66,6 +66,7 @@ void
 object::print (pretty_printer *pp) const
 {
   pp_character (pp, '{');
+  pp_indentation (pp) += 1;
 
   /* Iterate in the order that the keys were inserted.  */
   unsigned i;
@@ -73,15 +74,23 @@ object::print (pretty_printer *pp) const
   FOR_EACH_VEC_ELT (m_keys, i, key)
     {
       if (i > 0)
-	pp_string (pp, ", ");
+	{
+	  pp_string (pp, ",");
+	  pp_newline (pp);
+	  pp_indent (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, ": ");
+      const int indent = strlen (key) + 4;
+      pp_indentation (pp) += indent;
       value->print (pp);
+      pp_indentation (pp) -= indent;
     }
+  pp_indentation (pp) -= 1;
   pp_character (pp, '}');
 }
 
@@ -183,14 +192,20 @@ void
 array::print (pretty_printer *pp) const
 {
   pp_character (pp, '[');
+  pp_indentation (pp) += 1;
   unsigned i;
   value *v;
   FOR_EACH_VEC_ELT (m_elements, i, v)
     {
       if (i)
-	pp_string (pp, ", ");
+	{
+	  pp_string (pp, ",");
+	  pp_newline (pp);
+	  pp_indent (pp);
+	}
       v->print (pp);
     }
+  pp_indentation (pp) -= 1;
   pp_character (pp, ']');
 }
 
@@ -354,7 +369,9 @@ 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,
+		   "{\"foo\": \"bar\",\n"
+		   " \"baz\": \"quux\"}");
 }
 
 /* Verify that JSON arrays are written correctly.  */
@@ -369,7 +386,9 @@ test_writing_arrays ()
   ASSERT_PRINT_EQ (arr, "[\"foo\"]");
 
   arr.append (new json::string ("bar"));
-  ASSERT_PRINT_EQ (arr, "[\"foo\", \"bar\"]");
+  ASSERT_PRINT_EQ (arr,
+		   "[\"foo\",\n"
+		   " \"bar\"]");
 }
 
 /* Verify that JSON numbers are written correctly.  */
@@ -424,6 +443,40 @@ test_writing_literals ()
   ASSERT_PRINT_EQ (literal (false), "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,
+		   ("{\"str\": \"bar\",\n"
+		    " \"child\": {\"grandchild\": {\"arr\": [0,\n"
+		    "                                  1,\n"
+		    "                                  2],\n"
+		    "                          \"int\": 1066},\n"
+		    "           \"int\": 1776},\n"
+		    " \"int\": 42}"));
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -436,6 +489,7 @@ json_cc_tests ()
   test_writing_integer_numbers ();
   test_writing_strings ();
   test_writing_literals ();
+  test_formatting ();
 }
 
 } // namespace selftest
diff --git a/gcc/optinfo-emit-json.cc b/gcc/optinfo-emit-json.cc
index 11cad42a4330..1f8cb9b04e8e 100644
--- a/gcc/optinfo-emit-json.cc
+++ b/gcc/optinfo-emit-json.cc
@@ -471,7 +471,8 @@ test_building_json_from_dump_calls ()
   ASSERT_STR_CONTAINS (json_str, "impl_location");
   ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\"");
   ASSERT_STR_CONTAINS (json_str,
-		       "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]");
+		       " \"message\": [\"test of tree: \",\n"
+		       "             {\"expr\": \"0\"}]");
   delete json_obj;
 }
 
-- 
2.26.3


             reply	other threads:[~2023-11-15  0:55 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-15  0:54 David Malcolm [this message]
2023-12-06 17:50 ` [pushed] v2: diagnostics: prettify JSON output formats David Malcolm

Reply instructions:

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

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

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

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

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

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

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