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