]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
sarif-replay: fix escaping of JSON Pointer
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 13 Feb 2026 22:18:49 +0000 (17:18 -0500)
committerDavid Malcolm <dmalcolm@redhat.com>
Fri, 13 Feb 2026 22:18:49 +0000 (17:18 -0500)
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 <dmalcolm@redhat.com>
gcc/json-parsing.cc
gcc/json.cc
gcc/json.h
gcc/libsarifreplay.cc

index b0a5d841f3558a3e749d8af35292109efb4cd027..b3711e50f11f54a580b43d7cf8a5225ec213554a 100644 (file)
@@ -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 <const json::object *> (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 <json::array *> (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<error> 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 <const json::object *> (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 ();
index 3f1b0d9e021166974e9791732d4b9e8abcc08ae0..3f87e2910093c6f47444a7dd48d0572ea03f6573 100644 (file)
@@ -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<const pointer::token *> 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.  */
 
index fabe2fe0886a69d644e93f151f56aa2484a9787c..2ea1d13d17918827899cdb8aaf1e1e55e4d6731f 100644 (file)
@@ -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;
 };
index 20a0aec3c7fcb6127b8a6c194c1fb19b6823f435..966e181fa0c58288ca0fff08b074f30c6cdf62ef 100644 (file)
@@ -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;