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.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Successful run of analyzer integration tests on x86_64-pc-linux-gnu.
Pushed to trunk as r16-416-g52fe9502eb153f.

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 <dmalc...@redhat.com>
---
 gcc/json.cc                                   | 49 ++++++++++++
 gcc/json.h                                    | 47 +++++++++++
 gcc/libsarifreplay.cc                         | 79 +++++++++++++++++++
 .../2.1.0-invalid/3.1-not-an-object.sarif     |  3 +
 ...1-missing-arguments-for-placeholders.sarif |  3 +
 ...ot-enough-arguments-for-placeholders.sarif |  3 +
 .../3.11.5-unescaped-braces.sarif             |  3 +
 .../2.1.0-invalid/3.13.2-no-version.sarif     |  3 +
 .../3.13.2-version-not-a-string.sarif         |  3 +
 .../2.1.0-invalid/3.13.4-bad-runs.sarif       |  3 +
 .../2.1.0-invalid/3.13.4-no-runs.sarif        |  3 +
 .../3.13.4-non-object-in-runs.sarif           |  3 +
 .../2.1.0-invalid/3.27.10-bad-level.sarif     |  3 +
 .../3.33.3-index-out-of-range.sarif           |  3 +
 .../2.1.0-unhandled/3.27.10-none-level.sarif  |  3 +
 15 files changed, 211 insertions(+)

diff --git a/gcc/json.cc b/gcc/json.cc
index c54401bc530e..f3f364598569 100644
--- a/gcc/json.cc
+++ b/gcc/json.cc
@@ -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);
 }
 
diff --git a/gcc/json.h b/gcc/json.h
index d1a1553c1a8b..da4da852a1ed 100644
--- a/gcc/json.h
+++ b/gcc/json.h
@@ -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
diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc
index 7c7be8d98642..6c797624f828 100644
--- a/gcc/libsarifreplay.cc
+++ b/gcc/libsarifreplay.cc
@@ -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);
   }
 
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
index 4743bad3ba3c..9f0b87b1d270 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.1-not-an-object.sarif
@@ -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 ]
       | ^~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
index c4354d4ef23d..be2316a70bae 100644
--- 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
+++ 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-missing-arguments-for-placeholders.sarif
@@ -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" 
} }
       |                    
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
index e3eb53411101..98c3dc9f115c 100644
--- 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
+++ 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.11-not-enough-arguments-for-placeholders.sarif
@@ -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"] } }
       |                    
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif
index 29460e19c698..f81685954985 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.11.5-unescaped-braces.sarif
@@ -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" },
       |                               ^~~~~~~~~~~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
index 771bd9c0c05d..d2906041b090 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-no-version.sarif
@@ -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 | { }
       | ^~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
index 87bfd0de7898..c83be50ba28e 100644
--- 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
+++ 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.2-version-not-a-string.sarif
@@ -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 }
       |               ^~
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
index c5a26f055164..202698eb77be 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-bad-runs.sarif
@@ -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 }
       |           ^~
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
index f142321642c3..a4870c19296f 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-no-runs.sarif
@@ -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" }
       | ^~~~~~~~~~~~~~~~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
index 4eeaaaa7b242..b8709598b7ad 100644
--- 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
+++ 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.13.4-non-object-in-runs.sarif
@@ -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] }
       |             ^~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
index b5c3fbe55c37..eb60b6696060 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.27.10-bad-level.sarif
@@ -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",
       |                    ^~~~~~~~~~~~~~~~~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif
index ef22614776c5..d7baac92a0d6 100644
--- 
a/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif
+++ 
b/gcc/testsuite/sarif-replay.dg/2.1.0-invalid/3.33.3-index-out-of-range.sarif
@@ -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
       |                                              ^~
diff --git 
a/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
index 7f5867739ffb..20552a3aa117 100644
--- a/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-unhandled/3.27.10-none-level.sarif
@@ -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",
       |                    ^~~~~~
-- 
2.26.3

Reply via email to