From: David Malcolm Date: Fri, 13 Feb 2026 22:18:49 +0000 (-0500) Subject: sarif-replay: fix escaping of JSON Pointer X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7c73f9a347e8c500ccd96d46f27a7125b071a81c;p=thirdparty%2Fgcc.git sarif-replay: fix escaping of JSON Pointer gcc/ChangeLog: * json-parsing.cc (selftest::assert_json_pointer_eq): New. (ASSERT_JSON_POINTER_EQ): New. (selftest::test_parse_object): Add tests of JSON pointer. (selftest::test_pointer_escaping): New test. (selftest::json_parser_cc_tests): Call it. * json.cc (json::pointer::token::print): New. (json::value::print_pointer): New. * json.h (json::pointer::token::print): New decl. (json::value::print_pointer): New decl. * libsarifreplay.cc: Include pretty-print.h. (make_logical_location_from_jv): Use json::pointer::token::print for short_name. Signed-off-by: David Malcolm --- diff --git a/gcc/json-parsing.cc b/gcc/json-parsing.cc index b0a5d841f35..b3711e50f11 100644 --- a/gcc/json-parsing.cc +++ b/gcc/json-parsing.cc @@ -1495,6 +1495,25 @@ assert_err_eq (const location &loc, (END_UNICHAR_IDX), (END_LINE), (END_COLUMN), \ (EXPECTED_MSG)) +/* Implementation detail of ASSERT_JSON_POINTER_EQ. */ + +static void +assert_json_pointer_eq (const location &loc, + const json::value *jv, + const char *expected_str) +{ + pretty_printer pp; + ASSERT_TRUE_AT (loc, jv); + jv->print_pointer (&pp); + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str); +} + +/* Assert that JV is a non-NULL json::value *, and that + jv->print_pointer prints EXPECTED_STR. */ + +#define ASSERT_JSON_POINTER_EQ(JV, EXPECTED_STR) \ + assert_json_pointer_eq ((SELFTEST_LOCATION), (JV), (EXPECTED_STR)) + /* Verify that the JSON lexer works as expected. */ static void @@ -2130,6 +2149,7 @@ test_parse_object () 0, line_1, 0, 32, line_1, 32); const json::object *jo = static_cast (jv); + ASSERT_JSON_POINTER_EQ (jv, ""); json::value *foo_value = jo->get ("foo"); ASSERT_NE (foo_value, nullptr); @@ -2140,6 +2160,7 @@ test_parse_object () ASSERT_RANGE_EQ (*range, 8, line_1, 8, 12, line_1, 12); + ASSERT_JSON_POINTER_EQ (foo_value, "/foo"); json::value *baz_value = jo->get ("baz"); ASSERT_NE (baz_value, nullptr); @@ -2149,6 +2170,7 @@ test_parse_object () ASSERT_RANGE_EQ (*range, 22, line_1, 22, 31, line_1, 31); + ASSERT_JSON_POINTER_EQ (baz_value, "/baz"); json::array *baz_array = as_a (baz_value); ASSERT_EQ (baz_array->length (), 2); @@ -2160,6 +2182,7 @@ test_parse_object () ASSERT_RANGE_EQ (*range, 23, line_1, 23, 24, line_1, 24); + ASSERT_JSON_POINTER_EQ (element0, "/baz/0"); json::value *element1 = baz_array->get (1); ASSERT_EQ (element1->get_kind (), JSON_NULL); @@ -2168,6 +2191,7 @@ test_parse_object () ASSERT_RANGE_EQ (*range, 27, line_1, 27, 30, line_1, 30); + ASSERT_JSON_POINTER_EQ (element1, "/baz/1"); } /* Verify that the JSON literals "true", "false" and "null" are parsed @@ -2297,6 +2321,28 @@ test_parsing_comments () } } +/* Verify that JSON Pointers are correctly escaped. */ + +static void +test_pointer_escaping () +{ + std::unique_ptr err; + /* Example adapted from RFC 6901 section 5. */ + parser_testcase tc + (" {\n" + " \"a/b\": 1,\n" + " \"m~n\": 8\n" + " }\n"); + ASSERT_EQ (tc.get_error (), nullptr); + const json::value *jv = tc.get_value (); + ASSERT_NE (jv, nullptr); + ASSERT_EQ (jv->get_kind (), JSON_OBJECT); + + auto jo = static_cast (jv); + ASSERT_JSON_POINTER_EQ (jo->get ("a/b"), "/a~1b"); + ASSERT_JSON_POINTER_EQ (jo->get ("m~n"), "/m~0n"); +} + /* Verify that we can parse an empty JSON string. */ static void @@ -2378,6 +2424,7 @@ json_parser_cc_tests () test_parse_jsonrpc (); test_parse_empty_object (); test_parsing_comments (); + test_pointer_escaping (); test_error_empty_string (); test_error_bad_token (); test_error_object_with_missing_comma (); diff --git a/gcc/json.cc b/gcc/json.cc index 3f1b0d9e021..3f87e291009 100644 --- a/gcc/json.cc +++ b/gcc/json.cc @@ -120,6 +120,42 @@ pointer::token::operator= (pointer::token &&other) return *this; } +/* Print this to PP as an RFC 6901 section 3 reference-token. */ + +void +pointer::token::print (pretty_printer *pp) const +{ + switch (m_kind) + { + case kind::root_value: + break; + + case kind::object_member: + { + for (const char *ch = m_data.u_member; *ch; ++ch) + { + switch (*ch) + { + case '~': + pp_string (pp, "~0"); + break; + case '/': + pp_string (pp, "~1"); + break; + default: + pp_character (pp, *ch); + break; + } + } + } + break; + + case kind::array_index: + pp_scalar (pp, HOST_SIZE_T_PRINT_UNSIGNED, m_data.u_index); + break; + } +} + /* class json::value. */ /* Dump this json::value tree to OUTF. @@ -234,6 +270,29 @@ value::compare (const value &val_a, const value &val_b) } } +/* Print this value's JSON Pointer to PP. */ + +void +value::print_pointer (pretty_printer *pp) const +{ + /* Get path from this value to root. */ + auto_vec ancestry; + for (auto *iter = this; iter; iter = iter->m_pointer_token.m_parent) + ancestry.safe_push (&iter->m_pointer_token); + + /* Walk backward, going from root to this value. */ + ancestry.reverse (); + bool first = true; + for (auto iter : ancestry) + { + if (first) + first = false; + else + pp_character (pp, '/'); + iter->print (pp); + } +} + /* class json::object, a subclass of json::value, representing an ordered collection of key/value pairs. */ diff --git a/gcc/json.h b/gcc/json.h index fabe2fe0886..2ea1d13d179 100644 --- a/gcc/json.h +++ b/gcc/json.h @@ -107,6 +107,8 @@ struct token token & operator= (token &&other); + void print (pretty_printer *pp) const; + json::value *m_parent; union u { @@ -174,6 +176,7 @@ class value static int compare (const json::value &val_a, const json::value &val_b); const pointer::token &get_pointer_token () const { return m_pointer_token; } + void print_pointer (pretty_printer *pp) const; pointer::token m_pointer_token; }; diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc index 20a0aec3c7f..966e181fa0c 100644 --- a/gcc/libsarifreplay.cc +++ b/gcc/libsarifreplay.cc @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see #include "sarif-spec-urls.def" #include "libsarifreplay.h" #include "label-text.h" +#include "pretty-print.h" namespace { @@ -145,7 +146,9 @@ make_logical_location_from_jv (libgdiagnostics::manager &mgr, parent = make_logical_location_from_jv (mgr, *jv.m_pointer_token.m_parent); - std::string short_name; + pretty_printer pp; + pointer_token.print (&pp); + std::string short_name = pp_formatted_text (&pp); std::string fully_qualified_name; switch (pointer_token.m_kind) { @@ -153,19 +156,16 @@ make_logical_location_from_jv (libgdiagnostics::manager &mgr, gcc_unreachable (); case json::pointer::token::kind::root_value: - short_name = ""; fully_qualified_name = ""; break; case json::pointer::token::kind::object_member: - short_name = pointer_token.m_data.u_member; gcc_assert (parent.m_inner); fully_qualified_name = std::string (parent.get_fully_qualified_name ()) + "/" + short_name; break; case json::pointer::token::kind::array_index: - short_name = std::to_string (pointer_token.m_data.u_index); gcc_assert (parent.m_inner); fully_qualified_name = std::string (parent.get_fully_qualified_name ()) + "/" + short_name;