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