Previously our SARIF output did not capture nesting of logical locations: any time a result or event referred to a logical location it would simply put a copy of the logical location into the pertinent location object without a "parentIndex" property.
With this patch we instead populate such locations with minimal logical locations with an "index" that refers to theRuns.logicalLocations, populating theRuns.logicalLocations with the full logical locations, including "parentIndex", recursively adding entries for the ancestor locations as needed, so that the SARIF output captures the hierarchical structure of the logical locations. 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-414-gf25e178b8c2cc8. gcc/ChangeLog: PR other/116176 * diagnostic-format-sarif.cc (class sarif_array_of_unique): New template. (class sarif_logical_location): Move here from diagnostic-format-sarif.h. (sarif_builder::m_cached_logical_locs): New. (sarif_builder::sarif_builder): Initialize it. (sarif_builder::set_any_logical_locs_arr): Call make_minimal_sarif_logical_location rather than make_sarif_logical_location_object. (sarif_property_bag::set_logical_location): Likewise. (make_sarif_logical_location_object): Replace with... (sarif_builder::ensure_sarif_logical_location_for): ...this. Capture "parentIndex" property. Consolidate into theRuns.logicalLocations. (sarif_builder::make_minimal_sarif_logical_location): New. (sarif_builder::make_run_object): Add "index" properties to m_cached_logical_locs and move it to theRuns.logicalLocations. (selftest::test_sarif_array_of_unique_1): New. (selftest::test_sarif_array_of_unique_2): New. (selftest::diagnostic_format_sarif_cc_tests): Call the new selftests. * diagnostic-format-sarif.h (class sarif_logical_location): Move to diagnostic-format-sarif.cc. (make_sarif_logical_location_object): Drop decl. * json.cc (value::compare): New. (object::compare): New. (selftest::fail_comparison): New. (selftest::assert_json_equal): New. (ASSERT_JSON_EQ): New. (selftest::assert_json_non_equal): New. (ASSERT_JSON_NE): New. (selftest::test_comparisons): New. (selftest::json_cc_tests): Call the new selftest. * json.h (json::value::dyn_cast_object): New vfunc. (json::object::dyn_cast_object): New vfunc impl. (json::object::compare): New decl. * libgdiagnostics.cc (impl_logical_location_manager::get_parent): New. * logical-location.h (logical_location_manager::get_parent): New vfunc impl. * selftest-logical-location.h (test_logical_location_manager::get_parent): New vfunc impl. * tree-logical-location.cc (assert_valid_tree): New. (tree_logical_location_manager::get_short_name): Support types as well as decls. (tree_logical_location_manager::get_name_with_scope): Gracefully handle non-decl nodes. (tree_logical_location_manager::get_internal_name): Likewise. (tree_logical_location_manager::get_kind): Don't attempt to handle null nodes. Handle NAMESPACE_DECL and RECORD_TYPE. (tree_logical_location_manager::get_name_for_path_output): Gracefully handle non-decl nodes. (tree_logical_location_manager::get_parent): New. * tree-logical-location.h (tree_logical_location_manager::get_parent): New vfunc impl. gcc/testsuite/ChangeLog: PR other/116176 * g++.dg/sarif-output/logical-locations-1.C: New test. * g++.dg/sarif-output/logical-locations-1.py: New test script. * g++.dg/sarif-output/logical-locations-2.C: New test. * g++.dg/sarif-output/logical-locations-2.py: New test script. * g++.dg/sarif-output/logical-locations-3.C: New test. * g++.dg/sarif-output/logical-locations-3.py: New test script. * g++.dg/sarif-output/sarif-output.exp: New script, adapted from gcc.dg/sarif-output/sarif-output.exp. * libgdiagnostics.dg/test-logical-location-c.py: Update for using theRun.logicalLocations. * libgdiagnostics.dg/test-warning-with-path-c.py: Likewise. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- gcc/diagnostic-format-sarif.cc | 225 +++++++++++- gcc/diagnostic-format-sarif.h | 11 - gcc/json.cc | 326 ++++++++++++++++++ gcc/json.h | 8 + gcc/libgdiagnostics.cc | 7 + gcc/logical-location.h | 3 + gcc/selftest-logical-location.h | 4 + .../g++.dg/sarif-output/logical-locations-1.C | 27 ++ .../sarif-output/logical-locations-1.py | 79 +++++ .../g++.dg/sarif-output/logical-locations-2.C | 69 ++++ .../sarif-output/logical-locations-2.py | 90 +++++ .../g++.dg/sarif-output/logical-locations-3.C | 85 +++++ .../sarif-output/logical-locations-3.py | 61 ++++ .../g++.dg/sarif-output/sarif-output.exp | 31 ++ .../test-logical-location-c.py | 9 + .../test-warning-with-path-c.py | 14 +- gcc/tree-logical-location.cc | 89 ++++- gcc/tree-logical-location.h | 1 + 18 files changed, 1091 insertions(+), 48 deletions(-) create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-1.C create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-1.py create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-2.C create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-2.py create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C create mode 100644 gcc/testsuite/g++.dg/sarif-output/logical-locations-3.py create mode 100644 gcc/testsuite/g++.dg/sarif-output/sarif-output.exp diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index e9a387291421..1b0743cb3c7d 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -51,6 +51,52 @@ along with GCC; see the file COPYING3. If not see #include "demangle.h" #include "backtrace.h" +/* A json::array where the values are "unique" as per + SARIF v2.1.0 section 3.7.3 ("Array properties with unique values"). */ + +template <typename JsonElementType> +class sarif_array_of_unique : public json::array +{ + public: + size_t append_uniquely (std::unique_ptr<JsonElementType> val) + { + /* This should be O(log(n)) due to the std::map. */ + auto search = m_index_by_value.find (val.get ()); + if (search != m_index_by_value.end()) + return (*search).second; + + const size_t insertion_idx = size (); + m_index_by_value.insert ({val.get (), insertion_idx}); + append (std::move (val)); + return insertion_idx; + } + + /* For ease of reading output, add "index": idx to all + objects in the array. + We don't do this until we've added everything, since + the "index" property would otherwise confuse the + comparison against new elements. */ + void add_explicit_index_values () + { + for (size_t idx = 0; idx < length (); ++idx) + if (json::object *obj = get (idx)->dyn_cast_object ()) + obj->set_integer ("index", idx); + } + +private: + struct comparator_t { + bool operator () (const json::value *a, const json::value *b) const + { + gcc_assert (a); + gcc_assert (b); + return json::value::compare (*a, *b) < 0; + } + }; + + // json::value * here is borrowed from m_elements + std::map<json::value *, int, comparator_t> m_index_by_value; +}; + /* Forward decls. */ class sarif_builder; class content_renderer; @@ -446,6 +492,13 @@ class sarif_physical_location : public sarif_object {}; class sarif_region : public sarif_object {}; +/* Subclass of sarif_object for SARIF "logicalLocation" objects + (SARIF v2.1.0 section 3.33). */ + +class sarif_logical_location : public sarif_object +{ +}; + /* Subclass of sarif_object for SARIF "locationRelationship" objects (SARIF v2.1.0 section 3.34). */ @@ -772,6 +825,9 @@ public: const sarif_generation_options &get_opts () const { return m_sarif_gen_opts; } + std::unique_ptr<sarif_logical_location> + make_minimal_sarif_logical_location (logical_location); + private: class sarif_token_printer : public token_printer { @@ -829,6 +885,10 @@ private: const content_renderer *snippet_renderer) const; std::unique_ptr<sarif_region> make_region_object_for_hint (const fixit_hint &hint) const; + + int + ensure_sarif_logical_location_for (logical_location k); + std::unique_ptr<sarif_multiformat_message_string> make_multiformat_message_string (const char *msg) const; std::unique_ptr<sarif_log> @@ -909,6 +969,8 @@ private: /* The set of all CWE IDs we've seen, if any. */ hash_set <int_hash <int, 0, 1> > m_cwe_id_set; + std::unique_ptr<sarif_array_of_unique<sarif_logical_location>> m_cached_logical_locs; + int m_tabstop; std::unique_ptr<sarif_serialization_format> m_serialization_format; @@ -1597,6 +1659,8 @@ sarif_builder::sarif_builder (diagnostic_context &context, m_seen_any_relative_paths (false), m_rule_id_set (), m_rules_arr (new json::array ()), + m_cached_logical_locs + (std::make_unique<sarif_array_of_unique<sarif_logical_location>> ()), m_tabstop (context.m_tabstop), m_serialization_format (std::move (serialization_format)), m_sarif_gen_opts (sarif_gen_opts), @@ -2116,7 +2180,8 @@ sarif_builder::make_locations_arr (sarif_location_manager &loc_mgr, } /* If LOGICAL_LOC is non-null, use it to create a "logicalLocations" property - within LOCATION_OBJ (SARIF v2.1.0 section 3.28.4). */ + within LOCATION_OBJ (SARIF v2.1.0 section 3.28.4) with a minimal logical + location object referencing theRuns.logicalLocations (3.33.3). */ void sarif_builder:: @@ -2127,9 +2192,12 @@ set_any_logical_locs_arr (sarif_location &location_obj, return; gcc_assert (m_logical_loc_mgr); auto location_locs_arr = std::make_unique<json::array> (); + + auto logical_loc_obj = make_minimal_sarif_logical_location (logical_loc); + location_locs_arr->append<sarif_logical_location> - (make_sarif_logical_location_object (logical_loc, - *m_logical_loc_mgr)); + (std::move (logical_loc_obj)); + location_obj.set<json::array> ("logicalLocations", std::move (location_locs_arr)); } @@ -2694,43 +2762,81 @@ sarif_property_bag::set_logical_location (const char *property_name, sarif_builder &builder, logical_location logical_loc) { - gcc_assert (logical_loc); - const logical_location_manager *mgr - = builder.get_logical_location_manager (); - gcc_assert (mgr); set<sarif_logical_location> (property_name, - make_sarif_logical_location_object (logical_loc, *mgr)); + builder.make_minimal_sarif_logical_location (logical_loc)); } -/* Make a "logicalLocation" object (SARIF v2.1.0 section 3.33) for - LOGICAL_LOC, which must be non-null. */ +/* Ensure that m_cached_logical_locs has a "logicalLocation" object + (SARIF v2.1.0 section 3.33) for K, and return its index within the + array. */ -std::unique_ptr<sarif_logical_location> -make_sarif_logical_location_object (logical_location logical_loc, - const logical_location_manager &mgr) +int +sarif_builder:: +ensure_sarif_logical_location_for (logical_location k) { - gcc_assert (logical_loc); + gcc_assert (m_logical_loc_mgr); auto sarif_logical_loc = std::make_unique<sarif_logical_location> (); - /* "name" property (SARIF v2.1.0 section 3.33.4). */ - if (const char *short_name = mgr.get_short_name (logical_loc)) + if (const char *short_name = m_logical_loc_mgr->get_short_name (k)) sarif_logical_loc->set_string ("name", short_name); /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5). */ - if (const char *name_with_scope = mgr.get_name_with_scope (logical_loc)) + if (const char *name_with_scope = m_logical_loc_mgr->get_name_with_scope (k)) sarif_logical_loc->set_string ("fullyQualifiedName", name_with_scope); /* "decoratedName" property (SARIF v2.1.0 section 3.33.6). */ - if (const char *internal_name = mgr.get_internal_name (logical_loc)) + if (const char *internal_name = m_logical_loc_mgr->get_internal_name (k)) sarif_logical_loc->set_string ("decoratedName", internal_name); /* "kind" property (SARIF v2.1.0 section 3.33.7). */ - enum logical_location_kind kind = mgr.get_kind (logical_loc); + enum logical_location_kind kind = m_logical_loc_mgr->get_kind (k); if (const char *sarif_kind_str = maybe_get_sarif_kind (kind)) sarif_logical_loc->set_string ("kind", sarif_kind_str); + /* "parentIndex" property (SARIF v2.1.0 section 3.33.8). */ + if (auto parent_key = m_logical_loc_mgr->get_parent (k)) + { + /* Recurse upwards. */ + int parent_index = ensure_sarif_logical_location_for (parent_key); + sarif_logical_loc->set_integer ("parentIndex", parent_index); + } + + /* Consolidate if this logical location already exists. */ + int index + = m_cached_logical_locs->append_uniquely (std::move (sarif_logical_loc)); + + return index; +} + +/* Ensure that theRuns.logicalLocations (3.14.17) has a "logicalLocation" object + (SARIF v2.1.0 section 3.33) for LOGICAL_LOC. + Create and return a minimal logicalLocation object referring to the + full object by index. */ + +std::unique_ptr<sarif_logical_location> +sarif_builder:: +make_minimal_sarif_logical_location (logical_location logical_loc) +{ + gcc_assert (m_logical_loc_mgr); + + /* Ensure that m_cached_logical_locs has a "logicalLocation" object + (SARIF v2.1.0 section 3.33) for LOGICAL_LOC, and return its index within + the array. */ + + auto sarif_logical_loc = std::make_unique <sarif_logical_location> (); + + int index = ensure_sarif_logical_location_for (logical_loc); + + // 3.33.3 index property + sarif_logical_loc->set_integer ("index", index); + + /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5). */ + if (const char *name_with_scope + = m_logical_loc_mgr->get_name_with_scope (logical_loc)) + sarif_logical_loc->set_string ("fullyQualifiedName", name_with_scope); + return sarif_logical_loc; } @@ -3074,6 +3180,14 @@ make_run_object (std::unique_ptr<sarif_invocation> invocation_obj, /* "results" property (SARIF v2.1.0 section 3.14.23). */ run_obj->set<json::array> ("results", std::move (results)); + /* "logicalLocations" property (SARIF v2.1.0 3.14.17). */ + if (m_cached_logical_locs->size () > 0) + { + m_cached_logical_locs->add_explicit_index_values (); + run_obj->set<json::array> ("logicalLocations", + std::move (m_cached_logical_locs)); + } + return run_obj; } @@ -3960,6 +4074,76 @@ sarif_generation_options::sarif_generation_options () namespace selftest { +static void +test_sarif_array_of_unique_1 () +{ + sarif_array_of_unique<json::string> arr; + + ASSERT_EQ (arr.length (), 0); + + { + size_t idx = arr.append_uniquely (std::make_unique<json::string> ("foo")); + ASSERT_EQ (idx, 0); + ASSERT_EQ (arr.length (), 1); + } + { + size_t idx = arr.append_uniquely (std::make_unique<json::string> ("bar")); + ASSERT_EQ (idx, 1); + ASSERT_EQ (arr.length (), 2); + } + + /* Try adding them again, should be idempotent. */ + { + size_t idx = arr.append_uniquely (std::make_unique<json::string> ("foo")); + ASSERT_EQ (idx, 0); + ASSERT_EQ (arr.length (), 2); + } + { + size_t idx = arr.append_uniquely (std::make_unique<json::string> ("bar")); + ASSERT_EQ (idx, 1); + ASSERT_EQ (arr.length (), 2); + } +} + +static void +test_sarif_array_of_unique_2 () +{ + sarif_array_of_unique<json::object> arr; + + ASSERT_EQ (arr.length (), 0); + + { + auto obj0 = std::make_unique<json::object> (); + size_t idx = arr.append_uniquely (std::move (obj0)); + ASSERT_EQ (idx, 0); + ASSERT_EQ (arr.length (), 1); + + // Attempting to add another empty objects should be idempotent. + idx = arr.append_uniquely (std::make_unique<json::object> ()); + ASSERT_EQ (idx, 0); + ASSERT_EQ (arr.length (), 1); + } + { + auto obj1 = std::make_unique<json::object> (); + obj1->set_string ("foo", "bar"); + size_t idx = arr.append_uniquely (std::move (obj1)); + ASSERT_EQ (idx, 1); + ASSERT_EQ (arr.length (), 2); + + // Attempting to add an equivalent object should be idempotent. + auto other = std::make_unique<json::object> (); + other->set_string ("foo", "bar"); + idx = arr.append_uniquely (std::move (other)); + ASSERT_EQ (idx, 1); + ASSERT_EQ (arr.length (), 2); + } + + // Verify behavior of add_explicit_index_values. + arr.add_explicit_index_values (); + ASSERT_JSON_INT_PROPERTY_EQ (arr[0], "index", 0); + ASSERT_JSON_INT_PROPERTY_EQ (arr[1], "index", 1); +} + /* A subclass of sarif_output_format for writing selftests. The JSON output is cached internally, rather than written out to a file. */ @@ -4625,6 +4809,9 @@ run_line_table_case_tests_per_version (const line_table_case &case_) void diagnostic_format_sarif_cc_tests () { + test_sarif_array_of_unique_1 (); + test_sarif_array_of_unique_2 (); + for_each_sarif_gen_option (test_simple_log); for_each_sarif_gen_option (test_message_with_embedded_link); for_each_sarif_gen_option (test_message_with_braces); diff --git a/gcc/diagnostic-format-sarif.h b/gcc/diagnostic-format-sarif.h index 354f18a6c690..4e6a525d64c1 100644 --- a/gcc/diagnostic-format-sarif.h +++ b/gcc/diagnostic-format-sarif.h @@ -139,15 +139,4 @@ public: sarif_property_bag &get_or_create_properties (); }; -/* Subclass of sarif_object for SARIF "logicalLocation" objects - (SARIF v2.1.0 section 3.33). */ - -class sarif_logical_location : public sarif_object -{ -}; - -extern std::unique_ptr<sarif_logical_location> -make_sarif_logical_location_object (logical_location logical_loc, - const logical_location_manager &mgr); - #endif /* ! GCC_DIAGNOSTIC_FORMAT_SARIF_H */ diff --git a/gcc/json.cc b/gcc/json.cc index e66a7ae6f741..c54401bc530e 100644 --- a/gcc/json.cc +++ b/gcc/json.cc @@ -100,6 +100,94 @@ value::dump () const fprintf (stderr, "\n"); } +/* A deterministic total ordering for comparing json values, so that we + can e.g. put them in std::map. + + This is intended to follow the condition for equality described in + the JSON Schema standard (§4.3, “Instance equality”), as referenced + by SARIF v2.1.0 (§3.7.3 "Array properties with unique values"), but has + the following limitations: + - numbers are supposed to be checked for "the same mathematical value", + but in this implementation int vs float numbers won't compare as equal, + and float number comparison is bitwise + - strings are supposed to be "the same codepoint-for-codepoint", but + this implementation doesn't take into account canonicalization issues. */ + +int +value::compare (const value &val_a, const value &val_b) +{ + enum kind kind_a = val_a.get_kind (); + enum kind kind_b = val_b.get_kind (); + if (kind_a != kind_b) + return (int)kind_a - (int)kind_b; + + switch (kind_a) + { + default: + gcc_unreachable (); + + case JSON_OBJECT: + { + const object &obj_a = (const object &)val_a; + const object &obj_b = (const object &)val_b; + return object::compare (obj_a, obj_b); + } + break; + + case JSON_ARRAY: + { + const array &arr_a = (const array &)val_a; + const array &arr_b = (const array &)val_b; + if (int cmp_size = (int)arr_a.size () - (int)arr_b.size ()) + return cmp_size; + for (size_t idx = 0; idx < arr_a.size (); ++idx) + if (int cmp_element = compare (*arr_a[idx], *arr_b[idx])) + return cmp_element; + return 0; + } + break; + + case JSON_INTEGER: + { + const integer_number &int_a = (const integer_number &)val_a; + const integer_number &int_b = (const integer_number &)val_b; + return int_a.get () - int_b.get (); + } + break; + + case JSON_FLOAT: + { + const float_number &float_a = (const float_number &)val_a; + const float_number &float_b = (const float_number &)val_b; + union u + { + double u_double; + char u_buf[sizeof(double)]; + }; + union u u_a, u_b; + u_a.u_double = float_a.get (); + u_b.u_double = float_b.get (); + return memcmp (&u_a, &u_b, sizeof(double)); + } + break; + + case JSON_STRING: + { + const string &str_a = (const string &)val_a; + const string &str_b = (const string &)val_b; + return strcmp (str_a.get_string (), str_b.get_string ()); + } + break; + + case JSON_TRUE: + case JSON_FALSE: + case JSON_NULL: + /* All instances of literals compare equal to instances + of the same literal. */ + return 0; + } +} + /* class json::object, a subclass of json::value, representing an ordered collection of key/value pairs. */ @@ -234,6 +322,36 @@ object::set_bool (const char *key, bool v) set (key, new json::literal (v)); } +/* Subroutine of json::compare for comparing a pairs of objects. */ + +int +object::compare (const json::object &obj_a, const json::object &obj_b) +{ + if (int cmp_size = (int)obj_a.m_keys.length () - (int)obj_b.m_keys.length ()) + return cmp_size; + + for (auto iter_a : obj_a.m_map) + { + const char *key = iter_a.first; + const value *value_a = iter_a.second; + gcc_assert (value_a); + + const value *value_b = obj_b.get (key); + if (!value_b) + /* Key is in OBJ_A but not in OBJ_B. */ + return 1; + /* If key is OBJ_B but not in OBJ_A, then the + count of keys will have been different, or + OBJ_A would have had a key not in OBJ_B. */ + if (int cmp_value = value::compare (*value_a, *value_b)) + /* Values for key are non-equal. */ + return cmp_value; + } + + /* Objects are equal. */ + return 0; +} + /* class json::array, a subclass of json::value, representing an ordered collection of values. */ @@ -540,6 +658,213 @@ test_formatting () " \"int\": 1776}, \"int\": 42}")); } +/* Helper function for reporting failure of JSON comparisons. */ + +static void +fail_comparison (const location &loc, + const char *desc, + const value &val_a, const value &val_b, + const char *desc_expected_value, + int actual_value) +{ + fprintf (stderr, "val_a: "); + val_a.dump (); + + fprintf (stderr, "val_b: "); + val_b.dump (); + + selftest::fail_formatted (loc, + "%s: failed JSON comparison:" + " expected: %s got: %i\n", + desc, + desc_expected_value, actual_value); +} + +/* Implementation of ASSERT_JSON_EQ. */ + +static void +assert_json_equal (const location &loc, + const char *desc, + const value &val_a, const value &val_b) +{ + /* Comparison should return zero, both ways, indicating no differences. */ + const int a_vs_b = value::compare (val_a, val_b); + if (a_vs_b != 0) + fail_comparison (loc, desc, val_a, val_b, "zero", a_vs_b); + + const int b_vs_a = value::compare (val_b, val_a); + if (b_vs_a != 0) + fail_comparison (loc, desc, val_b, val_a, "zero", b_vs_a); +} + +/* Verify that json::value::compare returns 0 ("no differences") on + VAL1 and VAL2, in both orders. */ + +#define ASSERT_JSON_EQ(VAL1, VAL2) \ + SELFTEST_BEGIN_STMT \ + assert_json_equal ((SELFTEST_LOCATION), \ + "ASSERT_JSON_EQ", \ + (VAL1), (VAL2)); \ + SELFTEST_END_STMT + +/* Implementation of ASSERT_JSON_NE. */ + +static void +assert_json_non_equal (const location &loc, + const char *desc, + const value &val_a, const value &val_b) +{ + /* Comparison should be non-zero, indicating differences. */ + const int a_vs_b = value::compare (val_a, val_b); + if (a_vs_b == 0) + fail_comparison (loc, desc, val_a, val_b, "non-zero", a_vs_b); + + const int b_vs_a = value::compare (val_b, val_a); + ASSERT_NE_AT (loc, b_vs_a, 0); + if (b_vs_a == 0) + fail_comparison (loc, desc, val_b, val_a, "non-zero", b_vs_a); + + /* Swapping the args should swap the sign of the result + (but isn't necessarily the negation). */ + if ( (a_vs_b > 0) == (b_vs_a > 0) ) + fail_comparison (loc, desc, val_b, val_a, "opposite signs", 1); +} + +/* Verify that json::value::compare returns non-zero ("different") on + VAL1 and VAL2, in both orders, and that they have opposite + sign. */ + +#define ASSERT_JSON_NE(VAL1, VAL2) \ + SELFTEST_BEGIN_STMT \ + assert_json_non_equal ((SELFTEST_LOCATION), \ + "ASSERT_JSON_NE", \ + (VAL1), (VAL2)); \ + SELFTEST_END_STMT + +/* Verify that json::value::compare works as expected. */ + +static void +test_comparisons () +{ + /* Literals. */ + + literal null_lit (JSON_NULL); + ASSERT_JSON_EQ (null_lit, null_lit); + + literal other_null_lit (JSON_NULL); + ASSERT_JSON_EQ (null_lit, other_null_lit); + + literal true_lit (JSON_TRUE); + ASSERT_JSON_EQ (true_lit, true_lit); + ASSERT_JSON_NE (true_lit, null_lit); + + literal false_lit (JSON_FALSE); + ASSERT_JSON_EQ (false_lit, false_lit); + ASSERT_JSON_NE (false_lit, true_lit); + ASSERT_JSON_NE (false_lit, null_lit); + + /* Strings. */ + string str_foo_1 ("foo"); + ASSERT_JSON_EQ (str_foo_1, str_foo_1); + + string str_foo_2 ("foo"); + ASSERT_JSON_EQ (str_foo_1, str_foo_2); + + string str_bar ("bar"); + ASSERT_JSON_NE (str_bar, str_foo_1); + + /* Numbers. */ + integer_number i_42 (42); + ASSERT_JSON_EQ (i_42, i_42); + integer_number i_42_2 (42); + ASSERT_JSON_EQ (i_42, i_42_2); + integer_number i_43 (43); + ASSERT_JSON_NE (i_42, i_43); + + float_number f_zero (0.0); + ASSERT_JSON_EQ (f_zero, f_zero); + float_number f_zero_2 (0.0); + ASSERT_JSON_EQ (f_zero, f_zero_2); + float_number f_one (1.0); + ASSERT_JSON_NE (f_zero, f_one); + /* We don't yet test the more awkward cases e.g. NaN. */ + + /* Objects. */ + + // Empty object + // Self comparison should be 0 + object empty_obj_a; + ASSERT_JSON_EQ (empty_obj_a, empty_obj_a); + + // Instances of empty objects should compare equal to each other + object empty_obj_b; + ASSERT_JSON_EQ (empty_obj_a, empty_obj_b); + + // Object with one field: + object obj_1; + obj_1.set_string ("foo", "bar"); + // Self comparison should be 0 + ASSERT_JSON_EQ (obj_1, obj_1); + + // but should be different to an empty object: + ASSERT_JSON_NE (obj_1, empty_obj_a); + + // Another with one field, with same key/value: + object obj_2; + obj_2.set_string ("foo", "bar"); + ASSERT_JSON_EQ (obj_1, obj_2); + + // Same key, different value: + object obj_3; + obj_3.set_string ("foo", "baz"); + ASSERT_JSON_NE (obj_1, obj_3); + + // Adding an extra property: + obj_2.set_integer ("year", 1066); + ASSERT_JSON_NE (obj_1, obj_2); + + /* Different insertion order, but same k-v pairs should be equal, + despite having different serialization. */ + object obj_4; + obj_4.set_integer ("year", 1066); + obj_4.set_string ("foo", "bar"); + ASSERT_JSON_EQ (obj_2, obj_4); + ASSERT_PRINT_EQ (obj_2, false, "{\"foo\": \"bar\", \"year\": 1066}"); + ASSERT_PRINT_EQ (obj_4, false, "{\"year\": 1066, \"foo\": \"bar\"}"); + + /* Arrays. */ + + // Empty array + array empty_arr_a; + // Self comparison should be 0 + ASSERT_JSON_EQ (empty_arr_a, empty_arr_a); + + // Objects and arrays are different + ASSERT_JSON_NE (empty_obj_a, empty_arr_a); + + // Instances of empty arrays should compare equal to each other + array empty_arr_b; + ASSERT_JSON_EQ (empty_arr_a, empty_arr_b); + + // Array with one element: + array arr_1; + arr_1.append (std::make_unique<string> ("foo")); + // Self comparison should be 0 + ASSERT_JSON_EQ (arr_1, arr_1); + + // but should be different to an empty array: + ASSERT_JSON_NE (arr_1, empty_arr_a); + + // Another with one element: + array arr_2; + arr_2.append (std::make_unique<string> ("foo")); + ASSERT_JSON_EQ (arr_1, arr_2); + + // Adding an extra element: + arr_2.append (std::make_unique<string> ("bar")); + ASSERT_JSON_NE (arr_1, arr_2); +} + /* Run all of the selftests within this file. */ void @@ -553,6 +878,7 @@ json_cc_tests () test_writing_strings (); test_writing_literals (); test_formatting (); + test_comparisons (); } } // namespace selftest diff --git a/gcc/json.h b/gcc/json.h index e369244cf8ba..d1a1553c1a8b 100644 --- a/gcc/json.h +++ b/gcc/json.h @@ -84,6 +84,10 @@ class value void dump (FILE *, bool formatted) const; void DEBUG_FUNCTION dump () const; + + virtual object *dyn_cast_object () { return nullptr; } + + static int compare (const json::value &val_a, const json::value &val_b); }; /* Subclass of value for objects: a collection of key/value pairs @@ -100,6 +104,8 @@ class object : public value enum kind get_kind () const final override { return JSON_OBJECT; } void print (pretty_printer *pp, bool formatted) const final override; + object *dyn_cast_object () final override { return this; } + bool is_empty () const { return m_map.is_empty (); } void set (const char *key, value *v); @@ -127,6 +133,8 @@ class object : public value /* Set to literal true/false. */ void set_bool (const char *key, bool v); + static int compare (const json::object &obj_a, const json::object &obj_b); + private: typedef hash_map <char *, value *, simple_hashmap_traits<nofree_string_hash, value *> > map_t; diff --git a/gcc/libgdiagnostics.cc b/gcc/libgdiagnostics.cc index c085cd8e941c..39deb47248e1 100644 --- a/gcc/libgdiagnostics.cc +++ b/gcc/libgdiagnostics.cc @@ -330,6 +330,13 @@ public: gcc_assert (loc); return label_text::borrow (loc->m_short_name.get_str ()); } + + key get_parent (key k) const final override + { + auto loc = ptr_from_key (k); + gcc_assert (loc); + return key_from_ptr (loc->m_parent); + } }; class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks diff --git a/gcc/logical-location.h b/gcc/logical-location.h index 3308e2d56640..4374b096fb05 100644 --- a/gcc/logical-location.h +++ b/gcc/logical-location.h @@ -143,6 +143,9 @@ public: /* Get a string for location K in a form suitable for path output. */ virtual label_text get_name_for_path_output (key k) const = 0; + /* Get the parent logical_logical of K, if any, or nullptr. */ + virtual key get_parent (key k) const = 0; + bool function_p (key k) const; }; diff --git a/gcc/selftest-logical-location.h b/gcc/selftest-logical-location.h index bee1419cbaf5..d9bf38fd8d66 100644 --- a/gcc/selftest-logical-location.h +++ b/gcc/selftest-logical-location.h @@ -42,6 +42,10 @@ public: const char *get_internal_name (key) const final override; enum logical_location_kind get_kind (key) const final override; label_text get_name_for_path_output (key) const final override; + key get_parent (key) const final override + { + return key (); + } logical_location logical_location_from_funcname (const char *funcname); diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.C b/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.C new file mode 100644 index 000000000000..75ad3b4633cf --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.C @@ -0,0 +1,27 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-format=sarif-file" } */ + +/* Verify that we can capture the chain of parents of a + logical location (PR 116176). */ + +namespace ns { + class foo + { + void bar () + { + return 0; + } + }; +} + +/* We expect a failing compile due to the error, but the use of + -fdiagnostics-format=sarif-file means there should be no output to stderr. + DejaGnu injects this message; ignore it: + { dg-prune-output "exit status is 1" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest logical-locations-1.C "logical-locations-1.py" } } */ diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.py b/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.py new file mode 100644 index 000000000000..954f6df320bf --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-1.py @@ -0,0 +1,79 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_filename = 'logical-locations-1.C' + +def test_result(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + # The textual form of the diagnostic would look like this: + # . PATH/logical-locations-1.C: In member function ‘void ns::foo::bar()’: + # . PATH/logical-locations-1.C:12:14: error: return-statement with a value, in function returning ‘void’ [-fpermissive] + # . 12 | return 0; + # . | ^ + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == '-fpermissive' + assert result['level'] == 'error' + assert result['message']['text'] \ + == "return-statement with a value, in function returning 'void'" + + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + assert get_location_artifact_uri(location).endswith(expected_filename) + assert get_location_snippet_text(location) == ' return 0;\n' + +def test_logical_locations(sarif): + runs = sarif['runs'] + run = runs[0] + + # We expect 3 logical locations within the run: + assert len(run['logicalLocations']) == 3 + + assert run['logicalLocations'][0] \ + == {"name": "ns", + "fullyQualifiedName": "ns", + "kind": "namespace", + "index": 0} + assert run['logicalLocations'][1] \ + == {"name": "foo", + # Ideally we'd also have: + # "fullyQualifiedName": "ns::foo", + "kind": "type", + "parentIndex": 0, + "index": 1} + assert run['logicalLocations'][2] \ + == {"name": "bar", + "fullyQualifiedName": "ns::foo::bar", + "decoratedName": "_ZN2ns3foo3barEv", + "kind": "function", + "parentIndex": 1, + "index": 2} + + results = run['results'] + + assert len(results) == 1 + + result = results[0] + + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + + # We expect one logical location within the result, referencing + # one in the run + assert len(location['logicalLocations']) == 1 + assert location['logicalLocations'][0] \ + == {'fullyQualifiedName': 'ns::foo::bar', + 'index': 2} diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.C b/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.C new file mode 100644 index 000000000000..67aa4c934759 --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.C @@ -0,0 +1,69 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-format=sarif-file" } */ + +/* Verify that we correctly consolidate logical locations + involving repeated diagnostics within a nested hierarchy + (PR 116176). */ + +namespace ns_outer { + namespace ns_inner_1 { + class klass_1 + { + void member_fn_1 () + { + return 0; + } + void member_fn_2 () + { + return 0; + } + }; + class klass_2 + { + void member_fn_1 () + { + return 0; + } + void member_fn_2 () + { + return 0; + } + }; + } // ns_inner_1 + namespace ns_inner_2 { + class klass_1 + { + void member_fn_1 () + { + return 0; + } + void member_fn_2 () + { + return 0; + } + }; + class klass_2 + { + void member_fn_1 () + { + return 0; + } + void member_fn_2 () + { + return 0; + } + }; + } // ns_inner_2 +} // ns_outer + +/* We expect a failing compile due to the error, but the use of + -fdiagnostics-format=sarif-file means there should be no output to stderr. + DejaGnu injects this message; ignore it: + { dg-prune-output "exit status is 1" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest logical-locations-2.C "logical-locations-2.py" } } */ diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.py b/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.py new file mode 100644 index 000000000000..e34531b82674 --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-2.py @@ -0,0 +1,90 @@ +from sarif import * +from pprint import pprint + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_filename = 'logical-locations-2.C' + +def test_logical_locations(sarif): + runs = sarif['runs'] + run = runs[0] + + # We expect 15 logical locations within the run expressing + # a logical hierarchy, and 8 results with one logical locations + # in each, referencing back into the run. + + # Generate the "gold" output we expect. + expected_in_run = [] + expected_in_results = [] + + outer_ns_index_in_run = len(expected_in_run) + expected_in_run += [{"name": "ns_outer", + "fullyQualifiedName": "ns_outer", + "kind": "namespace", + "index": outer_ns_index_in_run}] + + for inner_ns in ['ns_inner_1', 'ns_inner_2']: + inner_ns_idx_in_run = len(expected_in_run) + expected_in_run += [{"name": inner_ns, + "fullyQualifiedName": f"ns_outer::{inner_ns}", + "kind": "namespace", + "index": inner_ns_idx_in_run, + "parentIndex": 0}] + for klass in ['klass_1', 'klass_2']: + klass_idx_in_run = len(expected_in_run) + expected_in_run += [{"name": klass, + "kind": "type", + "index": klass_idx_in_run, + "parentIndex": inner_ns_idx_in_run}] + for member_fn in ['member_fn_1', 'member_fn_2']: + fqn = f'ns_outer::{inner_ns}::{klass}::{member_fn}' + member_fn_idx_in_run = len(expected_in_run) + expected_in_run += [{"name": member_fn, + "kind": "function", + "fullyQualifiedName": f"ns_outer::{inner_ns}::{klass}::{member_fn}", + "decoratedName": f"_ZN8ns_outer10{inner_ns}7{klass}11{member_fn}Ev", + "index": member_fn_idx_in_run, + "parentIndex": klass_idx_in_run}] + expected_in_results += [{'fullyQualifiedName': fqn, + 'index': member_fn_idx_in_run}] + + pprint(expected_in_run) + pprint(expected_in_results) + assert len(expected_in_run) == 15 + assert len(expected_in_results) == 8 + + # We expect 15 logical locations within the run: + assert len(run['logicalLocations']) == len(expected_in_run) + for actual, expected in zip(run['logicalLocations'], expected_in_run): + assert actual == expected + + # We expect 8 results with one logical location in each + results = run['results'] + assert len(results) == len(expected_in_results) + + index = 0 + for inner_ns in ['ns_inner_1', 'ns_inner_2']: + for klass in ['klass_1', 'klass_2']: + for member_fn in ['member_fn_1', 'member_fn_2']: + result = results[index] + assert result['ruleId'] == '-fpermissive' + assert result['level'] == 'error' + assert result['message']['text'] \ + == "return-statement with a value, in function returning 'void'" + + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + + # We expect one logical location within the result, referencing + # one in the run + assert len(location['logicalLocations']) == 1 + assert location['logicalLocations'][0] \ + == expected_in_results[index] + + index += 1 diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C b/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C new file mode 100644 index 000000000000..59ed84afec85 --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C @@ -0,0 +1,85 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-format=sarif-file" } */ + +/* Verify that we handle deeply nested logical locations + (PR 116176). */ + +#define NS_OPEN(SUFFIX) namespace ns_##SUFFIX { +#define NS_CLOSE } + +#define NS_OPEN_10(SUFFIX) \ + NS_OPEN(SUFFIX##0) \ + NS_OPEN(SUFFIX##1) \ + NS_OPEN(SUFFIX##2) \ + NS_OPEN(SUFFIX##3) \ + NS_OPEN(SUFFIX##4) \ + NS_OPEN(SUFFIX##5) \ + NS_OPEN(SUFFIX##6) \ + NS_OPEN(SUFFIX##7) \ + NS_OPEN(SUFFIX##8) \ + NS_OPEN(SUFFIX##9) + +#define NS_CLOSE_10 \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE \ + NS_CLOSE + +#define NS_OPEN_100(SUFFIX) \ + NS_OPEN_10(SUFFIX##0) \ + NS_OPEN_10(SUFFIX##1) \ + NS_OPEN_10(SUFFIX##2) \ + NS_OPEN_10(SUFFIX##3) \ + NS_OPEN_10(SUFFIX##4) \ + NS_OPEN_10(SUFFIX##5) \ + NS_OPEN_10(SUFFIX##6) \ + NS_OPEN_10(SUFFIX##7) \ + NS_OPEN_10(SUFFIX##8) \ + NS_OPEN_10(SUFFIX##9) + +#define NS_CLOSE_100 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 \ + NS_CLOSE_10 + +#define NS_OPEN_200 \ + NS_OPEN_100(a) \ + NS_OPEN_100(b) + +#define NS_CLOSE_200 \ + NS_CLOSE_100 \ + NS_CLOSE_100 + +NS_OPEN_200 + +void return_from_void () +{ + return 0; +} + +NS_CLOSE_200 + +/* We expect a failing compile due to the error, but the use of + -fdiagnostics-format=sarif-file means there should be no output to stderr. + DejaGnu injects this message; ignore it: + { dg-prune-output "exit status is 1" } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest logical-locations-3.C "logical-locations-3.py" } } */ diff --git a/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.py b/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.py new file mode 100644 index 000000000000..199299cb5ee5 --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.py @@ -0,0 +1,61 @@ +from sarif import * +from pprint import pprint + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +expected_filename = 'logical-locations-3.C' + +def test_logical_locations(sarif): + runs = sarif['runs'] + run = runs[0] + + # We expect a single error with this very long logical location: + # ../../src/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C: In function ‘void ns_a00::ns_a01::ns_a02::ns_a03::ns_a04::ns_a05::ns_a06::ns_a07::ns_a08::ns_a09::ns_a10::ns_a11::ns_a12::ns_a13::ns_a14::ns_a15::ns_a16::ns_a17::ns_a18::ns_a19::ns_a20::ns_a21::ns_a22::ns_a23::ns_a24::ns_a25::ns_a26::ns_a27::ns_a28::ns_a29::ns_a30::ns_a31::ns_a32::ns_a33::ns_a34::ns_a35::ns_a36::ns_a37::ns_a38::ns_a39::ns_a40::ns_a41::ns_a42::ns_a43::ns_a44::ns_a45::ns_a46::ns_a47::ns_a48::ns_a49::ns_a50::ns_a51::ns_a52::ns_a53::ns_a54::ns_a55::ns_a56::ns_a57::ns_a58::ns_a59::ns_a60::ns_a61::ns_a62::ns_a63::ns_a64::ns_a65::ns_a66::ns_a67::ns_a68::ns_a69::ns_a70::ns_a71::ns_a72::ns_a73::ns_a74::ns_a75::ns_a76::ns_a77::ns_a78::ns_a79::ns_a80::ns_a81::ns_a82::ns_a83::ns_a84::ns_a85::ns_a86::ns_a87::ns_a88::ns_a89::ns_a90::ns_a91::ns_a92::ns_a93::ns_a94::ns_a95::ns_a96::ns_a97::ns_a98::ns_a99::ns_b00::ns_b01::ns_b02::ns_b03::ns_b04::ns_b05::ns_b06::ns_b07::ns_b08::ns_b09::ns_b10::ns_b11::ns_b12::ns_b13::ns_b14::ns_b15::ns_b16::ns_b17::ns_b18::ns_b19::ns_b20::ns_b21::ns_b22::ns_b23::ns_b24::ns_b25::ns_b26::ns_b27::ns_b28::ns_b29::ns_b30::ns_b31::ns_b32::ns_b33::ns_b34::ns_b35::ns_b36::ns_b37::ns_b38::ns_b39::ns_b40::ns_b41::ns_b42::ns_b43::ns_b44::ns_b45::ns_b46::ns_b47::ns_b48::ns_b49::ns_b50::ns_b51::ns_b52::ns_b53::ns_b54::ns_b55::ns_b56::ns_b57::ns_b58::ns_b59::ns_b60::ns_b61::ns_b62::ns_b63::ns_b64::ns_b65::ns_b66::ns_b67::ns_b68::ns_b69::ns_b70::ns_b71::ns_b72::ns_b73::ns_b74::ns_b75::ns_b76::ns_b77::ns_b78::ns_b79::ns_b80::ns_b81::ns_b82::ns_b83::ns_b84::ns_b85::ns_b86::ns_b87::ns_b88::ns_b89::ns_b90::ns_b91::ns_b92::ns_b93::ns_b94::ns_b95::ns_b96::ns_b97::ns_b98::ns_b99::return_from_void()’: + # ../../src/gcc/testsuite/g++.dg/sarif-output/logical-locations-3.C:70:10: error: return-statement with a value, in function returning ‘void’ [-fpermissive] + # 70 | return 0; + # | ^ + + # We expect 201 logical locations within the run expressing + # the logical hierarchy: the 200 nested namespaces, and then + # the function within the innermost namespace. + assert len(run['logicalLocations']) == 201 + + outermost = run['logicalLocations'][0] + assert outermost == {'fullyQualifiedName': 'ns_a00', + 'index': 0, + 'kind': 'namespace', + 'name': 'ns_a00'} + + for i in range(1, 200): + ns_i = run['logicalLocations'][i] + assert ns_i['index'] == i + assert ns_i['kind'] == 'namespace' + assert ns_i['parentIndex'] == i - 1 + expected_name = 'ns_' + 'ab'[i // 100] + '%02i' % (i % 100) + assert ns_i['name'] == expected_name + assert ns_i['fullyQualifiedName'] \ + == run['logicalLocations'][i - 1]['fullyQualifiedName'] + "::" + expected_name + + innermost = run['logicalLocations'][200] + assert innermost['index'] == 200 + assert innermost['kind'] == 'function' + assert innermost['name'] == 'return_from_void' + assert innermost['parentIndex'] == 199 + assert innermost['fullyQualifiedName'] \ + == run['logicalLocations'][199]['fullyQualifiedName'] + '::return_from_void' + + # We expect 1 error in the run, referring to the innermost + # logical location by index within the run's logical locations + results = run['results'] + assert len(results) == 1 + result = results[0] + assert len(result['locations']) == 1 + assert len(result['locations'][0]['logicalLocations']) == 1 + assert result['locations'][0]['logicalLocations'][0]['index'] \ + == innermost['index'] + assert result['locations'][0]['logicalLocations'][0]['fullyQualifiedName'] \ + == innermost['fullyQualifiedName'] diff --git a/gcc/testsuite/g++.dg/sarif-output/sarif-output.exp b/gcc/testsuite/g++.dg/sarif-output/sarif-output.exp new file mode 100644 index 000000000000..20b284568deb --- /dev/null +++ b/gcc/testsuite/g++.dg/sarif-output/sarif-output.exp @@ -0,0 +1,31 @@ +# Copyright (C) 2012-2025 Free Software Foundation, Inc. +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + +# GCC testsuite that uses the `dg.exp' driver. + +# Load support procs. +load_lib g++-dg.exp + +# Initialize `dg'. +dg-init + +# Main loop. +dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.C]] "" "" + +# All done. +dg-finish diff --git a/gcc/testsuite/libgdiagnostics.dg/test-logical-location-c.py b/gcc/testsuite/libgdiagnostics.dg/test-logical-location-c.py index 7448a1e0dac9..55f338beac69 100644 --- a/gcc/testsuite/libgdiagnostics.dg/test-logical-location-c.py +++ b/gcc/testsuite/libgdiagnostics.dg/test-logical-location-c.py @@ -31,7 +31,16 @@ def test_sarif_output_with_logical_location(sarif): assert len(location['logicalLocations']) == 1 logical_loc = location['logicalLocations'][0] + assert logical_loc['index'] == 0 + assert logical_loc['fullyQualifiedName'] == 'test_qualified_name' + + # Check theRun.logicalLocations + assert 'logicalLocations' in run + assert len(run['logicalLocations']) == 1 + logical_loc = run['logicalLocations'][0] assert logical_loc['name'] == 'test_short_name' assert logical_loc['fullyQualifiedName'] == 'test_qualified_name' assert logical_loc['decoratedName'] == 'test_decorated_name' assert logical_loc['kind'] == 'function' + assert logical_loc['index'] == 0 + diff --git a/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py b/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py index 5d3bbc47e171..af1e7b980fa9 100644 --- a/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py +++ b/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py @@ -51,10 +51,8 @@ def test_sarif_output_for_warning_with_path(sarif): assert len(location['logicalLocations']) == 1 logical_loc = location['logicalLocations'][0] - assert logical_loc['name'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['index'] == 0 assert logical_loc['fullyQualifiedName'] == 'make_a_list_of_random_ints_badly' - assert logical_loc['decoratedName'] == 'make_a_list_of_random_ints_badly' - assert logical_loc['kind'] == 'function' assert len(result['codeFlows']) == 1 assert len(result['codeFlows'][0]['threadFlows']) == 1 @@ -106,3 +104,13 @@ def test_sarif_output_for_warning_with_path(sarif): == "when calling 'PyList_Append', passing NULL from (1) as argument 1" assert tfl_2['nestingLevel'] == 0 assert tfl_2['executionOrder'] == 3 + + # Check theRun.logicalLocations + assert 'logicalLocations' in run + assert len(run['logicalLocations']) == 1 + logical_loc = run['logicalLocations'][0] + assert logical_loc['name'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['fullyQualifiedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['decoratedName'] == 'make_a_list_of_random_ints_badly' + assert logical_loc['kind'] == 'function' + assert logical_loc['index'] == 0 diff --git a/gcc/tree-logical-location.cc b/gcc/tree-logical-location.cc index 854f9e839f8d..1b2702fdb8a0 100644 --- a/gcc/tree-logical-location.cc +++ b/gcc/tree-logical-location.cc @@ -27,32 +27,57 @@ along with GCC; see the file COPYING3. If not see #include "langhooks.h" #include "intl.h" +static void +assert_valid_tree (const_tree node) +{ + gcc_assert (node); + gcc_assert (DECL_P (node) || TYPE_P (node)); + gcc_assert (TREE_CODE (node) != TRANSLATION_UNIT_DECL); +} + /* class tree_logical_location_manager : public logical_location_manager. */ const char * tree_logical_location_manager::get_short_name (key k) const { tree node = tree_from_key (k); - gcc_assert (node); - return identifier_to_locale (lang_hooks.decl_printable_name (node, 0)); + assert_valid_tree (node); + + if (DECL_P (node)) + return identifier_to_locale (lang_hooks.decl_printable_name (node, 0)); + if (TYPE_P (node)) + return IDENTIFIER_POINTER (TYPE_IDENTIFIER (node)); + return nullptr; } const char * tree_logical_location_manager::get_name_with_scope (key k) const { tree node = tree_from_key (k); - gcc_assert (node); - return identifier_to_locale (lang_hooks.decl_printable_name (node, 1)); + assert_valid_tree (node); + + if (DECL_P (node)) + return identifier_to_locale (lang_hooks.decl_printable_name (node, 1)); + if (TYPE_P (node)) + return nullptr; + return nullptr; } const char * tree_logical_location_manager::get_internal_name (key k) const { tree node = tree_from_key (k); - gcc_assert (node); - if (HAS_DECL_ASSEMBLER_NAME_P (node)) - if (tree id = DECL_ASSEMBLER_NAME (node)) - return IDENTIFIER_POINTER (id); + assert_valid_tree (node); + + if (DECL_P (node)) + { + if (HAS_DECL_ASSEMBLER_NAME_P (node) + && TREE_CODE (node) != NAMESPACE_DECL) // FIXME + if (tree id = DECL_ASSEMBLER_NAME (node)) + return IDENTIFIER_POINTER (id); + } + else if (TYPE_P (node)) + return nullptr; return NULL; } @@ -60,8 +85,7 @@ enum logical_location_kind tree_logical_location_manager::get_kind (key k) const { tree node = tree_from_key (k); - if (!node) - return LOGICAL_LOCATION_KIND_UNKNOWN; + assert_valid_tree (node); switch (TREE_CODE (node)) { @@ -73,6 +97,11 @@ tree_logical_location_manager::get_kind (key k) const return LOGICAL_LOCATION_KIND_PARAMETER; case VAR_DECL: return LOGICAL_LOCATION_KIND_VARIABLE; + case NAMESPACE_DECL: + return LOGICAL_LOCATION_KIND_NAMESPACE; + + case RECORD_TYPE: + return LOGICAL_LOCATION_KIND_TYPE; } } @@ -80,9 +109,39 @@ label_text tree_logical_location_manager::get_name_for_path_output (key k) const { tree node = tree_from_key (k); - gcc_assert (node); - const char *n = DECL_NAME (node) - ? identifier_to_locale (lang_hooks.decl_printable_name (node, 2)) - : _("<anonymous>"); - return label_text::borrow (n); + assert_valid_tree (node); + + if (DECL_P (node)) + { + const char *n = DECL_NAME (node) + ? identifier_to_locale (lang_hooks.decl_printable_name (node, 2)) + : _("<anonymous>"); + return label_text::borrow (n); + } + else if (TYPE_P (node)) + return label_text (); + return label_text (); +} + +logical_location +tree_logical_location_manager::get_parent (key k) const +{ + tree node = tree_from_key (k); + assert_valid_tree (node); + + if (DECL_P (node)) + { + if (!DECL_CONTEXT (node)) + return logical_location (); + if (TREE_CODE (DECL_CONTEXT (node)) == TRANSLATION_UNIT_DECL) + return logical_location (); + return key_from_tree (DECL_CONTEXT (node)); + } + else if (TYPE_P (node)) + { + if (!TYPE_CONTEXT (node)) + return logical_location (); + return key_from_tree (TYPE_CONTEXT (node)); + } + return logical_location (); } diff --git a/gcc/tree-logical-location.h b/gcc/tree-logical-location.h index bd9637a5d3d4..0d57888fef7f 100644 --- a/gcc/tree-logical-location.h +++ b/gcc/tree-logical-location.h @@ -35,6 +35,7 @@ public: const char *get_internal_name (key) const final override; enum logical_location_kind get_kind (key) const final override; label_text get_name_for_path_output (key) const final override; + key get_parent (key) const final override; static tree tree_from_key (logical_location k) { -- 2.26.3