mirror of git://gcc.gnu.org/git/gcc.git
1081 lines
25 KiB
C++
1081 lines
25 KiB
C++
/* JSON trees
|
||
Copyright (C) 2017-2025 Free Software Foundation, Inc.
|
||
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||
|
||
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/>. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "json.h"
|
||
#include "pretty-print.h"
|
||
#include "math.h"
|
||
#include "selftest.h"
|
||
|
||
using namespace json;
|
||
|
||
/* Print a JSON string to PP, escaping '"', control characters,
|
||
and embedded null bytes.
|
||
The string is required to be UTF-8 encoded. */
|
||
|
||
static void
|
||
print_escaped_json_string (pretty_printer *pp,
|
||
const char *utf8_str,
|
||
size_t len)
|
||
{
|
||
pp_character (pp, '"');
|
||
for (size_t i = 0; i != len; ++i)
|
||
{
|
||
char ch = utf8_str[i];
|
||
switch (ch)
|
||
{
|
||
case '"':
|
||
pp_string (pp, "\\\"");
|
||
break;
|
||
case '\\':
|
||
pp_string (pp, "\\\\");
|
||
break;
|
||
case '\b':
|
||
pp_string (pp, "\\b");
|
||
break;
|
||
case '\f':
|
||
pp_string (pp, "\\f");
|
||
break;
|
||
case '\n':
|
||
pp_string (pp, "\\n");
|
||
break;
|
||
case '\r':
|
||
pp_string (pp, "\\r");
|
||
break;
|
||
case '\t':
|
||
pp_string (pp, "\\t");
|
||
break;
|
||
case '\0':
|
||
pp_string (pp, "\\0");
|
||
break;
|
||
default:
|
||
pp_character (pp, ch);
|
||
}
|
||
}
|
||
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.
|
||
|
||
The key/value pairs of json::objects are printed in the order
|
||
in which the keys were originally inserted. */
|
||
|
||
void
|
||
value::dump (FILE *outf, bool formatted) const
|
||
{
|
||
pretty_printer pp;
|
||
pp_buffer (&pp)->m_stream = outf;
|
||
print (&pp, formatted);
|
||
pp_flush (&pp);
|
||
}
|
||
|
||
/* A convenience function for debugging.
|
||
Dump to stderr with formatting, and a trailing newline. */
|
||
|
||
void
|
||
value::dump () const
|
||
{
|
||
dump (stderr, true);
|
||
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. */
|
||
|
||
/* json:object's dtor. */
|
||
|
||
object::~object ()
|
||
{
|
||
for (map_t::iterator it = m_map.begin (); it != m_map.end (); ++it)
|
||
{
|
||
free (const_cast <char *>((*it).first));
|
||
delete ((*it).second);
|
||
}
|
||
}
|
||
|
||
/* Implementation of json::value::print for json::object. */
|
||
|
||
void
|
||
object::print (pretty_printer *pp, bool formatted) const
|
||
{
|
||
pp_character (pp, '{');
|
||
if (formatted)
|
||
pp_indentation (pp) += 1;
|
||
|
||
/* Iterate in the order that the keys were inserted. */
|
||
unsigned i;
|
||
const char *key;
|
||
FOR_EACH_VEC_ELT (m_keys, i, key)
|
||
{
|
||
if (i > 0)
|
||
{
|
||
pp_string (pp, ",");
|
||
if (formatted)
|
||
{
|
||
pp_newline (pp);
|
||
pp_indent (pp);
|
||
}
|
||
else
|
||
pp_space (pp);
|
||
}
|
||
map_t &mut_map = const_cast<map_t &> (m_map);
|
||
value *value = *mut_map.get (key);
|
||
print_escaped_json_string (pp, key, strlen (key));
|
||
pp_string (pp, ": ");
|
||
const int indent = strlen (key) + 4;
|
||
if (formatted)
|
||
pp_indentation (pp) += indent;
|
||
value->print (pp, formatted);
|
||
if (formatted)
|
||
pp_indentation (pp) -= indent;
|
||
}
|
||
if (formatted)
|
||
pp_indentation (pp) -= 1;
|
||
pp_character (pp, '}');
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
object::clone () const
|
||
{
|
||
return clone_as_object ();
|
||
}
|
||
|
||
std::unique_ptr<object>
|
||
object::clone_as_object () const
|
||
{
|
||
auto result = std::make_unique<object> ();
|
||
|
||
/* Iterate in the order that the keys were inserted. */
|
||
unsigned i;
|
||
const char *key;
|
||
FOR_EACH_VEC_ELT (m_keys, i, key)
|
||
{
|
||
map_t &mut_map = const_cast<map_t &> (m_map);
|
||
value *value = *mut_map.get (key);
|
||
result->set (key, value->clone ());
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Set the json::value * for KEY, taking ownership of V
|
||
(and taking a copy of KEY if necessary). */
|
||
|
||
void
|
||
object::set (const char *key, value *v)
|
||
{
|
||
gcc_assert (key);
|
||
gcc_assert (v);
|
||
|
||
value **ptr = m_map.get (key);
|
||
if (ptr)
|
||
{
|
||
/* If the key is already present, delete the existing value
|
||
and overwrite it. */
|
||
delete *ptr;
|
||
*ptr = v;
|
||
}
|
||
else
|
||
{
|
||
/* If the key wasn't already present, take a copy of the key,
|
||
and store the value. */
|
||
char *owned_key = xstrdup (key);
|
||
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.
|
||
|
||
The object retains ownership of the value. */
|
||
|
||
value *
|
||
object::get (const char *key) const
|
||
{
|
||
gcc_assert (key);
|
||
|
||
value **ptr = const_cast <map_t &> (m_map).get (key);
|
||
if (ptr)
|
||
return *ptr;
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
/* Set value of KEY within this object to a JSON
|
||
string value based on UTF8_VALUE. */
|
||
|
||
void
|
||
object::set_string (const char *key, const char *utf8_value)
|
||
{
|
||
set (key, new json::string (utf8_value));
|
||
}
|
||
|
||
/* Set value of KEY within this object to a JSON
|
||
integer value based on V. */
|
||
|
||
void
|
||
object::set_integer (const char *key, long v)
|
||
{
|
||
set (key, new json::integer_number (v));
|
||
}
|
||
|
||
/* Set value of KEY within this object to a JSON
|
||
floating point value based on V. */
|
||
|
||
void
|
||
object::set_float (const char *key, double v)
|
||
{
|
||
set (key, new json::float_number (v));
|
||
}
|
||
|
||
/* Set value of KEY within this object to the JSON
|
||
literal true or false, based on V. */
|
||
|
||
void
|
||
object::set_bool (const char *key, bool v)
|
||
{
|
||
set (key, new json::literal (v));
|
||
}
|
||
|
||
void
|
||
object::set_string (const string_property &property, const char *utf8_value)
|
||
{
|
||
set_string (property.m_key.get (), utf8_value);
|
||
}
|
||
|
||
void
|
||
object::set_integer (const integer_property &property, long value)
|
||
{
|
||
set_integer (property.m_key.get (), value);
|
||
}
|
||
|
||
void
|
||
object::set_bool (const bool_property &property, bool value)
|
||
{
|
||
set_bool (property.m_key.get (), value);
|
||
}
|
||
|
||
void
|
||
object::set_array_of_string (const array_of_string_property &property,
|
||
std::unique_ptr<json::array> value)
|
||
{
|
||
set<array> (property.m_key.get (), std::move (value));
|
||
}
|
||
|
||
/* 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. */
|
||
|
||
/* json::array's dtor. */
|
||
|
||
array::~array ()
|
||
{
|
||
unsigned i;
|
||
value *v;
|
||
FOR_EACH_VEC_ELT (m_elements, i, v)
|
||
delete v;
|
||
}
|
||
|
||
/* Implementation of json::value::print for json::array. */
|
||
|
||
void
|
||
array::print (pretty_printer *pp, bool formatted) const
|
||
{
|
||
pp_character (pp, '[');
|
||
if (formatted)
|
||
pp_indentation (pp) += 1;
|
||
unsigned i;
|
||
value *v;
|
||
FOR_EACH_VEC_ELT (m_elements, i, v)
|
||
{
|
||
if (i)
|
||
{
|
||
pp_string (pp, ",");
|
||
if (formatted)
|
||
{
|
||
pp_newline (pp);
|
||
pp_indent (pp);
|
||
}
|
||
else
|
||
pp_space (pp);
|
||
}
|
||
v->print (pp, formatted);
|
||
}
|
||
if (formatted)
|
||
pp_indentation (pp) -= 1;
|
||
pp_character (pp, ']');
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
array::clone () const
|
||
{
|
||
auto result = std::make_unique<array> ();
|
||
unsigned i;
|
||
value *v;
|
||
FOR_EACH_VEC_ELT (m_elements, i, v)
|
||
result->append (v->clone ());
|
||
return result;
|
||
}
|
||
|
||
/* Append non-NULL value V to a json::array, taking ownership of V. */
|
||
|
||
void
|
||
array::append (value *v)
|
||
{
|
||
gcc_assert (v);
|
||
v->m_pointer_token = pointer::token (*this, m_elements.length ());
|
||
m_elements.safe_push (v);
|
||
}
|
||
|
||
void
|
||
array::append_string (const char *utf8_value)
|
||
{
|
||
gcc_assert (utf8_value);
|
||
append (new json::string (utf8_value));
|
||
}
|
||
|
||
/* class json::float_number, a subclass of json::value, wrapping a double. */
|
||
|
||
/* Implementation of json::value::print for json::float_number. */
|
||
|
||
void
|
||
float_number::print (pretty_printer *pp,
|
||
bool formatted ATTRIBUTE_UNUSED) const
|
||
{
|
||
char tmp[1024];
|
||
snprintf (tmp, sizeof (tmp), "%g", m_value);
|
||
pp_string (pp, tmp);
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
float_number::clone () const
|
||
{
|
||
return std::make_unique<float_number> (m_value);
|
||
}
|
||
|
||
/* class json::integer_number, a subclass of json::value, wrapping a long. */
|
||
|
||
/* Implementation of json::value::print for json::integer_number. */
|
||
|
||
void
|
||
integer_number::print (pretty_printer *pp,
|
||
bool formatted ATTRIBUTE_UNUSED) const
|
||
{
|
||
char tmp[1024];
|
||
snprintf (tmp, sizeof (tmp), "%ld", m_value);
|
||
pp_string (pp, tmp);
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
integer_number::clone () const
|
||
{
|
||
return std::make_unique<integer_number> (m_value);
|
||
}
|
||
|
||
/* class json::string, a subclass of json::value. */
|
||
|
||
/* json::string's ctor. */
|
||
|
||
string::string (const char *utf8)
|
||
{
|
||
gcc_assert (utf8);
|
||
m_utf8 = xstrdup (utf8);
|
||
m_len = strlen (utf8);
|
||
}
|
||
|
||
string::string (const char *utf8, size_t len)
|
||
{
|
||
gcc_assert (utf8);
|
||
m_utf8 = XNEWVEC (char, len + 1);
|
||
m_len = len;
|
||
memcpy (m_utf8, utf8, len);
|
||
m_utf8[len] = '\0';
|
||
}
|
||
|
||
/* Implementation of json::value::print for json::string. */
|
||
|
||
void
|
||
string::print (pretty_printer *pp,
|
||
bool formatted ATTRIBUTE_UNUSED) const
|
||
{
|
||
print_escaped_json_string (pp, m_utf8, m_len);
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
string::clone () const
|
||
{
|
||
return std::make_unique<string> (m_utf8, m_len);
|
||
}
|
||
|
||
/* class json::literal, a subclass of json::value. */
|
||
|
||
/* Implementation of json::value::print for json::literal. */
|
||
|
||
void
|
||
literal::print (pretty_printer *pp,
|
||
bool formatted ATTRIBUTE_UNUSED) const
|
||
{
|
||
switch (m_kind)
|
||
{
|
||
case JSON_TRUE:
|
||
pp_string (pp, "true");
|
||
break;
|
||
case JSON_FALSE:
|
||
pp_string (pp, "false");
|
||
break;
|
||
case JSON_NULL:
|
||
pp_string (pp, "null");
|
||
break;
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
}
|
||
|
||
std::unique_ptr<value>
|
||
literal::clone () const
|
||
{
|
||
return std::make_unique<literal> (m_kind);
|
||
}
|
||
|
||
|
||
#if CHECKING_P
|
||
|
||
namespace selftest {
|
||
|
||
/* Selftests. */
|
||
|
||
/* Verify that JV->print () prints EXPECTED_JSON. */
|
||
|
||
void
|
||
assert_print_eq (const location &loc,
|
||
const json::value &jv,
|
||
bool formatted,
|
||
const char *expected_json)
|
||
{
|
||
pretty_printer pp;
|
||
jv.print (&pp, formatted);
|
||
ASSERT_STREQ_AT (loc, expected_json, pp_formatted_text (&pp));
|
||
}
|
||
|
||
#define ASSERT_PRINT_EQ(JV, FORMATTED, EXPECTED_JSON) \
|
||
assert_print_eq (SELFTEST_LOCATION, JV, FORMATTED, EXPECTED_JSON)
|
||
|
||
/* Verify that object::get works as expected. */
|
||
|
||
static void
|
||
test_object_get ()
|
||
{
|
||
object obj;
|
||
value *val = new json::string ("value");
|
||
obj.set ("foo", val);
|
||
ASSERT_EQ (obj.get ("foo"), val);
|
||
ASSERT_EQ (obj.get ("not-present"), NULL);
|
||
}
|
||
|
||
/* Verify that JSON objects are written correctly. */
|
||
|
||
static void
|
||
test_writing_objects ()
|
||
{
|
||
object obj;
|
||
obj.set_string ("foo", "bar");
|
||
obj.set_string ("baz", "quux");
|
||
obj.set_string ("\"\\\b\f\n\r\t", "value for awkward key");
|
||
|
||
/* This test relies on json::object writing out key/value pairs
|
||
in key-insertion order. */
|
||
ASSERT_PRINT_EQ (obj, true,
|
||
"{\"foo\": \"bar\",\n"
|
||
" \"baz\": \"quux\",\n"
|
||
" \"\\\"\\\\\\b\\f\\n\\r\\t\": \"value for awkward key\"}");
|
||
ASSERT_PRINT_EQ (obj, false,
|
||
"{\"foo\": \"bar\", \"baz\": \"quux\""
|
||
", \"\\\"\\\\\\b\\f\\n\\r\\t\": \"value for awkward key\"}");
|
||
}
|
||
|
||
/* Verify that JSON arrays are written correctly. */
|
||
|
||
static void
|
||
test_writing_arrays ()
|
||
{
|
||
array arr;
|
||
ASSERT_PRINT_EQ (arr, true, "[]");
|
||
|
||
arr.append (new json::string ("foo"));
|
||
ASSERT_PRINT_EQ (arr, true, "[\"foo\"]");
|
||
|
||
arr.append_string ("bar");
|
||
ASSERT_PRINT_EQ (arr, true,
|
||
"[\"foo\",\n"
|
||
" \"bar\"]");
|
||
ASSERT_PRINT_EQ (arr, false,
|
||
"[\"foo\", \"bar\"]");
|
||
}
|
||
|
||
/* Verify that JSON numbers are written correctly. */
|
||
|
||
static void
|
||
test_writing_float_numbers ()
|
||
{
|
||
ASSERT_PRINT_EQ (float_number (0), true, "0");
|
||
ASSERT_PRINT_EQ (float_number (42), true, "42");
|
||
ASSERT_PRINT_EQ (float_number (-100), true, "-100");
|
||
ASSERT_PRINT_EQ (float_number (123456789), true, "1.23457e+08");
|
||
}
|
||
|
||
static void
|
||
test_writing_integer_numbers ()
|
||
{
|
||
ASSERT_PRINT_EQ (integer_number (0), true, "0");
|
||
ASSERT_PRINT_EQ (integer_number (42), true, "42");
|
||
ASSERT_PRINT_EQ (integer_number (-100), true, "-100");
|
||
ASSERT_PRINT_EQ (integer_number (123456789), true, "123456789");
|
||
ASSERT_PRINT_EQ (integer_number (-123456789), true, "-123456789");
|
||
}
|
||
|
||
/* Verify that JSON strings are written correctly. */
|
||
|
||
static void
|
||
test_writing_strings ()
|
||
{
|
||
string foo ("foo");
|
||
ASSERT_PRINT_EQ (foo, true, "\"foo\"");
|
||
|
||
string contains_quotes ("before \"quoted\" after");
|
||
ASSERT_PRINT_EQ (contains_quotes, true, "\"before \\\"quoted\\\" after\"");
|
||
|
||
const char data[] = {'a', 'b', 'c', 'd', '\0', 'e', 'f'};
|
||
string not_terminated (data, 3);
|
||
ASSERT_PRINT_EQ (not_terminated, true, "\"abc\"");
|
||
string embedded_null (data, sizeof data);
|
||
ASSERT_PRINT_EQ (embedded_null, true, "\"abcd\\0ef\"");
|
||
}
|
||
|
||
/* Verify that JSON literals are written correctly. */
|
||
|
||
static void
|
||
test_writing_literals ()
|
||
{
|
||
ASSERT_PRINT_EQ (literal (JSON_TRUE), true, "true");
|
||
ASSERT_PRINT_EQ (literal (JSON_FALSE), true, "false");
|
||
ASSERT_PRINT_EQ (literal (JSON_NULL), true, "null");
|
||
|
||
ASSERT_PRINT_EQ (literal (true), true, "true");
|
||
ASSERT_PRINT_EQ (literal (false), true, "false");
|
||
}
|
||
|
||
/* Verify that nested values are formatted correctly when written.
|
||
|
||
Also, make use of array::append(std::unique_ptr<value>) and
|
||
object::set (const char *key, std::unique_ptr<value> v).*/
|
||
|
||
static void
|
||
test_formatting ()
|
||
{
|
||
object obj;
|
||
object *child = new object;
|
||
std::unique_ptr<object> grandchild = std::make_unique<object> ();
|
||
|
||
obj.set_string ("str", "bar");
|
||
obj.set ("child", child);
|
||
obj.set_integer ("int", 42);
|
||
|
||
array *arr = new array;
|
||
for (int i = 0; i < 3; i++)
|
||
arr->append (std::make_unique<integer_number> (i));
|
||
grandchild->set ("arr", arr);
|
||
grandchild->set_integer ("int", 1066);
|
||
|
||
child->set ("grandchild", std::move (grandchild));
|
||
child->set_integer ("int", 1776);
|
||
|
||
/* This test relies on json::object writing out key/value pairs
|
||
in key-insertion order. */
|
||
ASSERT_PRINT_EQ (obj, true,
|
||
("{\"str\": \"bar\",\n"
|
||
" \"child\": {\"grandchild\": {\"arr\": [0,\n"
|
||
" 1,\n"
|
||
" 2],\n"
|
||
" \"int\": 1066},\n"
|
||
" \"int\": 1776},\n"
|
||
" \"int\": 42}"));
|
||
ASSERT_PRINT_EQ (obj, false,
|
||
("{\"str\": \"bar\", \"child\": {\"grandchild\":"
|
||
" {\"arr\": [0, 1, 2], \"int\": 1066},"
|
||
" \"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);
|
||
}
|
||
|
||
/* Ensure that json::string's get_string is usable as a C-style string. */
|
||
|
||
static void
|
||
test_strcmp ()
|
||
{
|
||
string str ("foobar", 3);
|
||
ASSERT_EQ (strcmp (str.get_string (), "foo"), 0);
|
||
}
|
||
|
||
static void
|
||
test_cloning ()
|
||
{
|
||
// Objects
|
||
{
|
||
object obj;
|
||
obj.set_string ("foo", "bar");
|
||
|
||
auto obj_clone = obj.clone ();
|
||
ASSERT_JSON_EQ (obj, *obj_clone);
|
||
}
|
||
|
||
// Arrays
|
||
{
|
||
array arr;
|
||
arr.append (std::make_unique<string> ("foo"));
|
||
|
||
auto arr_clone = arr.clone ();
|
||
ASSERT_JSON_EQ (arr, *arr_clone);
|
||
}
|
||
|
||
// float_number
|
||
{
|
||
float_number f_one (1.0);
|
||
auto f_clone = f_one.clone ();
|
||
ASSERT_JSON_EQ (f_one, *f_clone);
|
||
}
|
||
|
||
// integer_number
|
||
{
|
||
integer_number num (42);
|
||
auto num_clone = num.clone ();
|
||
ASSERT_JSON_EQ (num, *num_clone);
|
||
}
|
||
|
||
// string
|
||
{
|
||
string str ("foo");
|
||
auto str_clone = str.clone ();
|
||
ASSERT_JSON_EQ (str, *str_clone);
|
||
}
|
||
|
||
// literal
|
||
{
|
||
literal lit (JSON_TRUE);
|
||
auto lit_clone = lit.clone ();
|
||
ASSERT_JSON_EQ (lit, *lit_clone);
|
||
}
|
||
}
|
||
|
||
/* Run all of the selftests within this file. */
|
||
|
||
void
|
||
json_cc_tests ()
|
||
{
|
||
test_object_get ();
|
||
test_writing_objects ();
|
||
test_writing_arrays ();
|
||
test_writing_float_numbers ();
|
||
test_writing_integer_numbers ();
|
||
test_writing_strings ();
|
||
test_writing_literals ();
|
||
test_formatting ();
|
||
test_comparisons ();
|
||
test_strcmp ();
|
||
test_cloning ();
|
||
}
|
||
|
||
} // namespace selftest
|
||
|
||
#endif /* #if CHECKING_P */
|