#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;
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). */
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
{
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>
/* 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;
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),
}
/* 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::
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));
}
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;
}
/* "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;
}
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. */
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);
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 */
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. */
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. */
" \"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
test_writing_strings ();
test_writing_literals ();
test_formatting ();
+ test_comparisons ();
}
} // namespace selftest
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
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);
/* 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;
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
/* 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;
};
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);
--- /dev/null
+/* { 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" } } */
--- /dev/null
+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}
--- /dev/null
+/* { 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" } } */
--- /dev/null
+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
--- /dev/null
+/* { 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" } } */
--- /dev/null
+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']
--- /dev/null
+# 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
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
+
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
== "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
#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;
}
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))
{
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;
}
}
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 ();
}
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)
{