aboutsummaryrefslogtreecommitdiff
path: root/gcc/json.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/json.cc')
-rw-r--r--gcc/json.cc375
1 files changed, 375 insertions, 0 deletions
diff --git a/gcc/json.cc b/gcc/json.cc
index e66a7ae..f3f3645 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.
@@ -100,6 +146,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. */
@@ -180,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.
@@ -234,6 +370,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. */
@@ -283,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);
}
@@ -540,6 +707,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 +927,7 @@ json_cc_tests ()
test_writing_strings ();
test_writing_literals ();
test_formatting ();
+ test_comparisons ();
}
} // namespace selftest