]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
json: implement JSON pointer; use it in sarif-replay [PR117988]
authorDavid Malcolm <dmalcolm@redhat.com>
Tue, 6 May 2025 13:26:19 +0000 (09:26 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Tue, 6 May 2025 13:26:19 +0000 (09:26 -0400)
This patch extends our json class to track JSON pointers (RFC 6901),
and then uses this within sarif-replay to provide logical locations
within the JSON when reporting on issues in the SARIF.

gcc/ChangeLog:
PR sarif-replay/117988
* json.cc (json::pointer::token::token): New ctors.
(json::pointer::token::~token): New.
(json::pointer::token::operator=): New.
(json::object::set): Set the value's m_pointer_token.
(json::array::append): Likewise.
* json.h (json::pointer::token): New struct.
(json::value::get_pointer_token): New accessor.
(json::value::m_pointer_token): New field.
* libsarifreplay.cc (get_logical_location_kind_for_json_kind):
New.
(make_logical_location_from_jv): New.
(sarif_replayer::report_problem): Set the logical location of the
diagnostic.

gcc/testsuite/ChangeLog:
PR sarif-replay/117988
* sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif: Add
expected logical location.
* sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif:
Likewise.
* sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif:
Likewise.
* sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif: Likewise.
* sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif: Likewise.
* sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif: Likewise.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
15 files changed:
gcc/json.cc
gcc/json.h
gcc/libsarifreplay.cc
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif
gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif

index c54401bc530e3cf07fbe359185b332a761f12015..f3f364598569ae9fcfdcf4c6e1ffb2181e169f6f 100644 (file)
@@ -74,6 +74,52 @@ print_escaped_json_string (pretty_printer *pp,
   pp_character (pp, '"');
 }
 
+/* class pointer::token.  */
+
+pointer::token::token ()
+{
+  m_parent = nullptr;
+  m_data.u_member = nullptr;
+  m_kind = kind::root_value;
+}
+
+pointer::token::token (json::object &parent, const char *member)
+{
+  m_parent = &parent;
+  m_data.u_member = xstrdup (member); // ideally we'd share
+  m_kind = kind::object_member;
+}
+
+pointer::token::token (json::array &parent, size_t index)
+{
+  m_parent = &parent;
+  m_data.u_index = index;
+  m_kind = kind::array_index;
+}
+
+pointer::token::~token ()
+{
+  if (m_kind == kind::object_member)
+    {
+      gcc_assert (m_data.u_member);
+      free (m_data.u_member);
+    }
+}
+
+pointer::token &
+pointer::token::operator= (pointer::token &&other)
+{
+  m_parent = other.m_parent;
+  m_data = other.m_data;
+  m_kind = other.m_kind;
+
+  other.m_parent = nullptr;
+  other.m_data.u_member = nullptr;
+  other.m_kind = kind::root_value;
+
+  return *this;
+}
+
 /* class json::value.  */
 
 /* Dump this json::value tree to OUTF.
@@ -268,6 +314,8 @@ object::set (const char *key, value *v)
       m_map.put (owned_key, v);
       m_keys.safe_push (owned_key);
     }
+
+  v->m_pointer_token = pointer::token (*this, key);
 }
 
 /* Get the json::value * for KEY.
@@ -401,6 +449,7 @@ void
 array::append (value *v)
 {
   gcc_assert (v);
+  v->m_pointer_token = pointer::token (*this, m_elements.length ());
   m_elements.safe_push (v);
 }
 
index d1a1553c1a8b2114316f1a5068a71b3fa75471cc..da4da852a1ed0b54e6360c03b1b76faf17b41ecf 100644 (file)
@@ -73,6 +73,49 @@ enum kind
   JSON_NULL
 };
 
+namespace pointer { // json::pointer
+
+/* Implementation of JSON pointer (RFC 6901).  */
+
+/* A token within a JSON pointer, expressing the parent of a particular
+   JSON value, and how it is descended from that parent.
+
+   A JSON pointer can be built as a list of these tokens.  */
+
+struct token
+{
+  enum class kind
+  {
+    root_value,
+    object_member,
+    array_index
+  };
+
+  token ();
+  token (json::object &parent, const char *member);
+  token (json::array &parent, size_t index);
+  token (const token &other) = delete;
+  token (token &&other) = delete;
+
+  ~token ();
+
+  token &
+  operator= (const token &other) = delete;
+
+  token &
+  operator= (token &&other);
+
+  json::value *m_parent;
+  union u
+  {
+    char *u_member;
+    size_t u_index;
+  } m_data;
+  enum kind m_kind;
+};
+
+} // namespace json::pointer
+
 /* Base class of JSON value.  */
 
 class value
@@ -88,6 +131,10 @@ class value
   virtual object *dyn_cast_object () { return nullptr; }
 
   static int compare (const json::value &val_a, const json::value &val_b);
+
+  const pointer::token &get_pointer_token () const { return m_pointer_token; }
+
+  pointer::token m_pointer_token;
 };
 
 /* Subclass of value for objects: a collection of key/value pairs
index 7c7be8d986421d44be16e9a100ea5cf052128d38..6c797624f82893c1dac916b09e80a8149b244db9 100644 (file)
@@ -106,6 +106,82 @@ make_physical_location (libgdiagnostics::manager &mgr,
   return mgr.new_location_from_range (start, start, end);
 }
 
+static enum diagnostic_logical_location_kind_t
+get_logical_location_kind_for_json_kind (enum json::kind json_kind)
+{
+  switch (json_kind)
+    {
+    default:
+      gcc_unreachable ();
+
+    case json::JSON_OBJECT:
+      return DIAGNOSTIC_LOGICAL_LOCATION_KIND_OBJECT;
+
+    case json::JSON_ARRAY:
+      return DIAGNOSTIC_LOGICAL_LOCATION_KIND_ARRAY;
+
+    case json::JSON_INTEGER:
+    case json::JSON_FLOAT:
+    case json::JSON_STRING:
+    case json::JSON_TRUE:
+    case json::JSON_FALSE:
+    case json::JSON_NULL:
+      return DIAGNOSTIC_LOGICAL_LOCATION_KIND_PROPERTY;
+      /* Perhaps this should be DIAGNOSTIC_LOGICAL_LOCATION_KIND_VALUE,
+        but then we need to more carefully track context.  */
+    }
+}
+
+static libgdiagnostics::logical_location
+make_logical_location_from_jv (libgdiagnostics::manager &mgr,
+                              const json::value &jv)
+{
+  libgdiagnostics::logical_location parent;
+  const json::pointer::token &pointer_token = jv.get_pointer_token ();
+
+  if (pointer_token.m_parent)
+    /* Recursively ensure that we have ancestor locations.  */
+    parent = make_logical_location_from_jv (mgr,
+                                           *jv.m_pointer_token.m_parent);
+
+  std::string short_name;
+  std::string fully_qualified_name;
+  switch (pointer_token.m_kind)
+    {
+    default:
+      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;
+      break;
+    }
+
+  enum diagnostic_logical_location_kind_t kind
+    = get_logical_location_kind_for_json_kind (jv.get_kind ());
+
+  return mgr.new_logical_location (kind,
+                                  parent,
+                                  short_name.c_str (),
+                                  fully_qualified_name.c_str (),
+                                  nullptr);
+}
+
+
 enum class status
 {
   ok,
@@ -417,6 +493,9 @@ private:
                                m_json_location_map.get_range_for_value (jv));
     diag.set_location (loc_range);
 
+    diag.set_logical_location (make_logical_location_from_jv (m_control_mgr,
+                                                             jv));
+
     diag.finish_va (gmsgid, args);
   }
 
index 4743bad3ba3c0c1310d8bad0ff14aef046a65a74..9f0b87b1d270ba8ceafcb7970ee5e0b8fd8a9dca 100644 (file)
@@ -1,5 +1,8 @@
 [ null ] // { dg-error "expected a sarifLog object as the top-level value \\\[SARIF v2.1.0 §3.1\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON array '':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     1 | [ null ]
       | ^~~~~~~~
index c4354d4ef23dc898873a504a84416ece3a10a85f..be2316a70baeda1afa463d6bf0c6ddb71486bc8b 100644 (file)
@@ -8,6 +8,9 @@
   }]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON object '/runs/0/results/0/message':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     6 |       { "message": { "text" : "the {0} {1} fox jumps over the {2} dog" } }
       |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index e3eb534111011bf596f0c318c5e94530735b7337..98c3dc9f115c85d16347abe9fa17b6443952ba6f 100644 (file)
@@ -8,6 +8,9 @@
   }]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON object '/runs/0/results/0/message':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     6 |       { "message": { "text" : "the {0} {1} fox jumps over the {2} dog", "arguments": ["quick", "brown"] } }
       |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index 29460e19c6983d9e44ceb7f5d602cb7788507c42..f81685954985e1baadce5eff74b97df3dd839da4 100644 (file)
@@ -9,6 +9,9 @@
   }]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/results/0/message/text':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     6 |       { "message": { "text" : "before {} after" },
       |                               ^~~~~~~~~~~~~~~~~
index 771bd9c0c05d2f17080b207d40b339a9e9071cc9..d2906041b090e63b3a80f896a7626bad97a39bd6 100644 (file)
@@ -1,5 +1,8 @@
 { } // { dg-error "expected sarifLog object to have a 'version' property \\\[SARIF v2.1.0 §3.13.2\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON object '':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     1 | { }
       | ^~~
index 87bfd0de78989e9d85cf4a80461e3870c51e1ff8..c83be50ba28effe138cdff70cd398618cf7d3f66 100644 (file)
@@ -1,5 +1,8 @@
 { "version" : 42 } // { dg-error "15: expected sarifLog.version to be a JSON string; got JSON number \\\[SARIF v2.1.0 §3.13.2\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/version':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     1 | { "version" : 42 }
       |               ^~
index c5a26f0551644bcd78578b4aa272da09a6cf47bd..202698eb77bebdb667bfbb341ceeef08257089e4 100644 (file)
@@ -1,6 +1,9 @@
 { "version": "2.1.0",
   "runs": 42 } // { dg-error "expected sarifLog.runs to be 'null' or an array \\\[SARIF v2.1.0 §3.13.4\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     2 |   "runs": 42 }
       |           ^~
index f142321642c35f790eb21255647366a741a09cdc..a4870c19296f48e2f74e04cac5cab5adabd57b86 100644 (file)
@@ -1,5 +1,8 @@
 { "version": "2.1.0" } // { dg-error "expected sarifLog object to have a 'runs' property \\\[SARIF v2.1.0 §3.13.4\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON object '':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     1 | { "version": "2.1.0" }
       | ^~~~~~~~~~~~~~~~~~~~~~
index 4eeaaaa7b242600ca2783a4d928f08cb389a57b8..b8709598b7ad08a468738ca555b29ac0fc3469ba 100644 (file)
@@ -1,6 +1,9 @@
 { "version": "2.1.0",
   "runs" : [42] } // { dg-error "expected element of sarifLog.runs array to be an object \\\[SARIF v2.1.0 §3.13.4\\\]" }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
     2 |   "runs" : [42] }
       |             ^~
index b5c3fbe55c37cd6d437b769429067c17073c5437..eb60b669606011bc0042b4738ae4f28bb10fcbb3 100644 (file)
@@ -19,6 +19,9 @@
   ]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/results/0/level':
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
    12 |           "level": "mostly harmless",
       |                    ^~~~~~~~~~~~~~~~~
index ef22614776c531845f03a644aa4457d1dbb5912b..d7baac92a0d6029d5154bcaacc7e7f0fb9a262cc 100644 (file)
@@ -26,6 +26,9 @@
     ]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/results/0/locations/0/logicalLocations/0/index'
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
    17 |                                     "index": 42
       |                                              ^~
index 7f5867739ffb5a8b2632c58671667624357aba99..20552a3aa1170a2a5fb266c48d097495feea8794 100644 (file)
@@ -19,6 +19,9 @@
   ]
 }
 
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/results/0/level'
+   { dg-end-multiline-output "" } */
 /* { dg-begin-multiline-output "" }
    12 |           "level": "none",
       |                    ^~~~~~