mirror of
https://github.com/nlohmann/json.git
synced 2026-11-02 10:55:35 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
945752e2af | ||
|
|
4976d054e1 |
No files matched your search
@@ -344,8 +344,8 @@ class binary_reader
|
|||||||
nested document could declare a length that disagrees with where its
|
nested document could declare a length that disagrees with where its
|
||||||
terminator actually falls and quietly hand the bytes in between to the
|
terminator actually falls and quietly hand the bytes in between to the
|
||||||
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
||||||
plus the terminator); open_bson_document() rejects a smaller size before
|
plus the terminator); the equality also rejects those impossible sizes,
|
||||||
it is stored, so @a document_size is never negative here.
|
since at least 5 bytes are always consumed.
|
||||||
|
|
||||||
@param[in] document_start value of chars_read before the size prefix
|
@param[in] document_start value of chars_read before the size prefix
|
||||||
@param[in] document_size the declared document size
|
@param[in] document_size the declared document size
|
||||||
@@ -353,7 +353,7 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start))
|
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
|
||||||
{
|
{
|
||||||
return bson_document_size_error(document_size, chars_read - document_start);
|
return bson_document_size_error(document_size, chars_read - document_start);
|
||||||
}
|
}
|
||||||
@@ -3084,13 +3084,16 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
if (size_and_type.second != 0)
|
if (size_and_type.second != 0)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
if (size_and_type.second != 'N')
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
return false;
|
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dim.push_back(dimlen);
|
||||||
}
|
}
|
||||||
dim.push_back(dimlen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3292,9 +3295,11 @@ class binary_reader
|
|||||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
||||||
if (ndarray_dtype != 0)
|
if (ndarray_dtype != 0)
|
||||||
{
|
{
|
||||||
// every optimized type marker is_valid_optimized_type() accepts for BJData has a name
|
|
||||||
const char* type_name = bjd_type_name(ndarray_dtype);
|
const char* type_name = bjd_type_name(ndarray_dtype);
|
||||||
JSON_ASSERT(type_name != nullptr);
|
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
||||||
|
{
|
||||||
|
return unexpected_byte("invalid byte", "type");
|
||||||
|
}
|
||||||
|
|
||||||
string_t type_key = "_ArrayType_";
|
string_t type_key = "_ArrayType_";
|
||||||
string_t type = type_name; // sax->string() takes a reference
|
string_t type = type_name; // sax->string() takes a reference
|
||||||
@@ -3377,6 +3382,12 @@ class binary_reader
|
|||||||
if (current == '$')
|
if (current == '$')
|
||||||
{
|
{
|
||||||
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
||||||
|
if (input_format == input_format_t::bjdata
|
||||||
|
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
|
||||||
|
{
|
||||||
|
return optimized_type_error();
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -3404,7 +3415,7 @@ class binary_reader
|
|||||||
// an ndarray was read here only if the flag flipped; when it was
|
// an ndarray was read here only if the flag flipped; when it was
|
||||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||||
// dimension vector
|
// dimension vector
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
||||||
}
|
}
|
||||||
@@ -3414,7 +3425,7 @@ class binary_reader
|
|||||||
if (current == '#')
|
if (current == '#')
|
||||||
{
|
{
|
||||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
||||||
}
|
}
|
||||||
@@ -3588,6 +3599,7 @@ class binary_reader
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
|
const bool bjdata = input_format == input_format_t::bjdata;
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
@@ -3601,11 +3613,11 @@ class binary_reader
|
|||||||
case 'L':
|
case 'L':
|
||||||
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
||||||
case 'u':
|
case 'u':
|
||||||
return parse_ubjson_typed_run<std::uint16_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
|
||||||
case 'm':
|
case 'm':
|
||||||
return parse_ubjson_typed_run<std::uint32_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
|
||||||
case 'M':
|
case 'M':
|
||||||
return parse_ubjson_typed_run<std::uint64_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
|
||||||
case 'd':
|
case 'd':
|
||||||
return parse_ubjson_typed_run<float>(remaining);
|
return parse_ubjson_typed_run<float>(remaining);
|
||||||
case 'D':
|
case 'D':
|
||||||
@@ -3686,7 +3698,7 @@ class binary_reader
|
|||||||
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
||||||
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
||||||
|
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||||
|
|
||||||
@@ -3745,7 +3757,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If BJData type marker is 'B' decode as binary
|
// If BJData type marker is 'B' decode as binary
|
||||||
if (size_and_type.first != npos && size_and_type.second == 'B')
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
binary_t result;
|
binary_t result;
|
||||||
return get_binary(size_and_type.first, result) && sax->binary(result);
|
return get_binary(size_and_type.first, result) && sax->binary(result);
|
||||||
@@ -3794,7 +3806,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not accept ND-array size in objects in BJData
|
// do not accept ND-array size in objects in BJData
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
||||||
}
|
}
|
||||||
@@ -5075,9 +5087,8 @@ class binary_reader
|
|||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief check whether @a marker is a type marker that may follow '$'
|
@brief check whether @a marker is a type marker that may follow '$'
|
||||||
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C.
|
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||||
The markers BJData excludes (F H N S T Z [ {) are the ones
|
(BJData's excluded markers are rejected separately).
|
||||||
binary_writer::is_bjdata_excluded_type_marker() lists; keep the two in sync.
|
|
||||||
*/
|
*/
|
||||||
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||||
{
|
{
|
||||||
@@ -5112,6 +5123,20 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||||
|
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||||
|
|
||||||
|
Mirrors binary_writer's @ref binary_writer::is_bjdata_excluded_type_marker
|
||||||
|
"is_bjdata_excluded_type_marker()`, which encodes the same list the other
|
||||||
|
way; keep the two in sync.
|
||||||
|
*/
|
||||||
|
static constexpr bool is_bjd_excluded_optimized_type(const char_int_type marker) noexcept
|
||||||
|
{
|
||||||
|
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|
||||||
|
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief look up the ND-array element type name for a BJData dtype marker
|
@brief look up the ND-array element type name for a BJData dtype marker
|
||||||
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
||||||
|
|||||||
@@ -3129,19 +3129,22 @@ class binary_writer
|
|||||||
// negative fixnum
|
// negative fixnum
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||||
{
|
{
|
||||||
// int 8
|
// int 8
|
||||||
oa.write_character(to_char_type(0xD0));
|
oa.write_character(to_char_type(0xD0));
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||||
{
|
{
|
||||||
// int 16
|
// int 16
|
||||||
oa.write_character(to_char_type(0xD1));
|
oa.write_character(to_char_type(0xD1));
|
||||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||||
{
|
{
|
||||||
// int 32
|
// int 32
|
||||||
oa.write_character(to_char_type(0xD2));
|
oa.write_character(to_char_type(0xD2));
|
||||||
@@ -4057,7 +4060,7 @@ class binary_writer
|
|||||||
Containers, strings, high-precision numbers, booleans and null cannot be
|
Containers, strings, high-precision numbers, booleans and null cannot be
|
||||||
declared as the single type of an optimized container in BJData; such a
|
declared as the single type of an optimized container in BJData; such a
|
||||||
container is written unoptimized. The reader rejects them with the same
|
container is written unoptimized. The reader rejects them with the same
|
||||||
list (binary_reader::is_valid_optimized_type()).
|
list (binary_reader::is_bjd_excluded_optimized_type()).
|
||||||
*/
|
*/
|
||||||
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
+67
-63
@@ -543,6 +543,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
// the type and value of a basic_json without its base class (defined
|
||||||
|
// below); the destruction helpers of json_value use it as a stand-in
|
||||||
|
struct data;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief a JSON value
|
@brief a JSON value
|
||||||
|
|
||||||
@@ -719,27 +723,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// raw, allocation-free transfer of m_data from src to dst: no
|
// raw, allocation-free transfer of the type and value from src to
|
||||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
// dst: no set_parents()/assert_invariant() (the former is
|
||||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
||||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
// walk below quadratic); dst takes ownership, src is left as
|
||||||
static void take(basic_json& dst, basic_json& src) noexcept
|
// value_t::null.
|
||||||
|
static void take(data& dst, data& src) noexcept
|
||||||
{
|
{
|
||||||
dst.m_data.m_type = src.m_data.m_type;
|
dst.m_type = src.m_type;
|
||||||
dst.m_data.m_value = src.m_data.m_value;
|
dst.m_value = src.m_value;
|
||||||
src.m_data.m_type = value_t::null;
|
src.m_type = value_t::null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// true if v is not an array/object, or is an already-empty one
|
// true if v is not an array/object, or is an already-empty one
|
||||||
static bool has_no_children(const basic_json& v) noexcept
|
static bool has_no_children(const data& v) noexcept
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->empty();
|
return v.m_value.array->empty();
|
||||||
}
|
}
|
||||||
if (v.m_data.m_type == value_t::object)
|
if (v.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.object->empty();
|
return v.m_value.object->empty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -748,21 +753,21 @@ private:
|
|||||||
// its own, i.e. v can be freed in one go (its destructor then
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
// destroys scalars and empty containers only, so it recurses at most
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
// one level deep); stops at the first structured child it meets
|
// one level deep); stops at the first structured child it meets
|
||||||
static bool all_children_leaves(const basic_json& v) noexcept
|
static bool all_children_leaves(const data& v) noexcept
|
||||||
{
|
{
|
||||||
// array_t only needs forward iterators (see custom_array_type)
|
// array_t only needs forward iterators (see custom_array_type)
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
const auto& a = *v.m_data.m_value.array;
|
const auto& a = *v.m_value.array;
|
||||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el);
|
return has_no_children(el.m_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const auto& o = *v.m_data.m_value.object;
|
const auto& o = *v.m_value.object;
|
||||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.second);
|
return has_no_children(el.second.m_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,30 +799,30 @@ private:
|
|||||||
|
|
||||||
// the child of a non-empty array/object v that the walk in
|
// the child of a non-empty array/object v that the walk in
|
||||||
// destroy_container() continues with (see walk_child_it() above)
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
static basic_json& walk_child(basic_json& v)
|
static data& walk_child(data& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->back();
|
return v.m_value.array->back().m_data;
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
return walk_child_it(*v.m_value.object)->second.m_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(basic_json& v)
|
static void pop_walk_child(data& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
v.m_data.m_value.array->pop_back();
|
v.m_value.array->pop_back();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,23 +831,23 @@ private:
|
|||||||
// recurses at most one level deep); this is the same allocator-based
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// free the old recursive implementation used, just factored out so
|
// free the old recursive implementation used, just factored out so
|
||||||
// every level of the walk in destroy() can share it
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(basic_json& v) noexcept
|
static void free_container(data& v) noexcept
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
||||||
}
|
}
|
||||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
v.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -873,14 +878,16 @@ public:
|
|||||||
|
|
||||||
// This json_value is not itself a basic_json, so the top-level
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
// container is first moved into a local stand-in ("cur"); a
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
// default-constructed basic_json has a null pointer in its
|
// default-constructed data has a null pointer in its m_value
|
||||||
// m_value (see data::m_value's initializer), so swapping it
|
// (see data::m_value's initializer), so swapping it with *this
|
||||||
// with *this leaves this union's own pointer null, and it is
|
// leaves this union's own pointer null, and it is never looked
|
||||||
// never looked at or freed a second time.
|
// at or freed a second time. The stand-ins are data, not
|
||||||
basic_json cur;
|
// basic_json, so that destroying a value never constructs a
|
||||||
cur.m_data.m_type = t;
|
// CustomBaseClass, whose constructor may allocate (#5995).
|
||||||
|
data cur;
|
||||||
|
cur.m_type = t;
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(cur.m_data.m_value, *this);
|
swap(cur.m_value, *this);
|
||||||
|
|
||||||
destroy_bounded(cur, 0);
|
destroy_bounded(cur, 0);
|
||||||
}
|
}
|
||||||
@@ -889,7 +896,7 @@ public:
|
|||||||
// The first recursion_depth_limit() levels are destroyed by plain
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
// recursion, which is the fastest way and cannot throw; anything
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
static void destroy_bounded(data& v, std::size_t depth) noexcept
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
@@ -897,23 +904,23 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_data.m_value.array)
|
for (auto& child : *v.m_value.array)
|
||||||
{
|
{
|
||||||
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child, depth + 1);
|
destroy_bounded(child.m_data, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_data.m_value.object)
|
for (auto& child : *v.m_value.object)
|
||||||
{
|
{
|
||||||
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.second, depth + 1);
|
destroy_bounded(child.second.m_data, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,18 +947,15 @@ public:
|
|||||||
// array/object, which neither allocates nor recurses more
|
// array/object, which neither allocates nor recurses more
|
||||||
// than one level deep.
|
// than one level deep.
|
||||||
//
|
//
|
||||||
// This json_value is not itself a basic_json, so the
|
// Like destroy_container(), the walk keeps the containers it
|
||||||
// top-level container is first moved into a local stand-in
|
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
// construct a CustomBaseClass.
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
static void destroy_walk(data& top) noexcept
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
static void destroy_walk(basic_json& top) noexcept
|
|
||||||
{
|
{
|
||||||
basic_json cur;
|
data cur;
|
||||||
take(cur, top);
|
take(cur, top);
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
data prev; // value_t::null: no parent
|
||||||
|
|
||||||
// true right after cur was entered: its children have not been
|
// true right after cur was entered: its children have not been
|
||||||
// looked at yet. If none of them has children of its own, cur is
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
@@ -965,7 +969,7 @@ public:
|
|||||||
fresh = false;
|
fresh = false;
|
||||||
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
if (prev.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
return; // back at the top with nothing left to do
|
return; // back at the top with nothing left to do
|
||||||
@@ -974,7 +978,7 @@ public:
|
|||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
basic_json gp;
|
data gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, walk_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_walk_child(prev);
|
||||||
|
|
||||||
@@ -985,7 +989,7 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_child_ref = walk_child(cur);
|
data& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_child_ref))
|
||||||
{
|
{
|
||||||
@@ -997,7 +1001,7 @@ public:
|
|||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
basic_json tmp;
|
data tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_child_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
|
|||||||
Generated
+119
-87
@@ -18351,8 +18351,8 @@ class binary_reader
|
|||||||
nested document could declare a length that disagrees with where its
|
nested document could declare a length that disagrees with where its
|
||||||
terminator actually falls and quietly hand the bytes in between to the
|
terminator actually falls and quietly hand the bytes in between to the
|
||||||
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
||||||
plus the terminator); open_bson_document() rejects a smaller size before
|
plus the terminator); the equality also rejects those impossible sizes,
|
||||||
it is stored, so @a document_size is never negative here.
|
since at least 5 bytes are always consumed.
|
||||||
|
|
||||||
@param[in] document_start value of chars_read before the size prefix
|
@param[in] document_start value of chars_read before the size prefix
|
||||||
@param[in] document_size the declared document size
|
@param[in] document_size the declared document size
|
||||||
@@ -18360,7 +18360,7 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start))
|
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
|
||||||
{
|
{
|
||||||
return bson_document_size_error(document_size, chars_read - document_start);
|
return bson_document_size_error(document_size, chars_read - document_start);
|
||||||
}
|
}
|
||||||
@@ -21091,13 +21091,16 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
if (size_and_type.second != 0)
|
if (size_and_type.second != 0)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
if (size_and_type.second != 'N')
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
return false;
|
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dim.push_back(dimlen);
|
||||||
}
|
}
|
||||||
dim.push_back(dimlen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -21299,9 +21302,11 @@ class binary_reader
|
|||||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
||||||
if (ndarray_dtype != 0)
|
if (ndarray_dtype != 0)
|
||||||
{
|
{
|
||||||
// every optimized type marker is_valid_optimized_type() accepts for BJData has a name
|
|
||||||
const char* type_name = bjd_type_name(ndarray_dtype);
|
const char* type_name = bjd_type_name(ndarray_dtype);
|
||||||
JSON_ASSERT(type_name != nullptr);
|
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
||||||
|
{
|
||||||
|
return unexpected_byte("invalid byte", "type");
|
||||||
|
}
|
||||||
|
|
||||||
string_t type_key = "_ArrayType_";
|
string_t type_key = "_ArrayType_";
|
||||||
string_t type = type_name; // sax->string() takes a reference
|
string_t type = type_name; // sax->string() takes a reference
|
||||||
@@ -21384,6 +21389,12 @@ class binary_reader
|
|||||||
if (current == '$')
|
if (current == '$')
|
||||||
{
|
{
|
||||||
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
||||||
|
if (input_format == input_format_t::bjdata
|
||||||
|
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
|
||||||
|
{
|
||||||
|
return optimized_type_error();
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -21411,7 +21422,7 @@ class binary_reader
|
|||||||
// an ndarray was read here only if the flag flipped; when it was
|
// an ndarray was read here only if the flag flipped; when it was
|
||||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||||
// dimension vector
|
// dimension vector
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
||||||
}
|
}
|
||||||
@@ -21421,7 +21432,7 @@ class binary_reader
|
|||||||
if (current == '#')
|
if (current == '#')
|
||||||
{
|
{
|
||||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
||||||
}
|
}
|
||||||
@@ -21595,6 +21606,7 @@ class binary_reader
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
|
const bool bjdata = input_format == input_format_t::bjdata;
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
@@ -21608,11 +21620,11 @@ class binary_reader
|
|||||||
case 'L':
|
case 'L':
|
||||||
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
||||||
case 'u':
|
case 'u':
|
||||||
return parse_ubjson_typed_run<std::uint16_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
|
||||||
case 'm':
|
case 'm':
|
||||||
return parse_ubjson_typed_run<std::uint32_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
|
||||||
case 'M':
|
case 'M':
|
||||||
return parse_ubjson_typed_run<std::uint64_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
|
||||||
case 'd':
|
case 'd':
|
||||||
return parse_ubjson_typed_run<float>(remaining);
|
return parse_ubjson_typed_run<float>(remaining);
|
||||||
case 'D':
|
case 'D':
|
||||||
@@ -21693,7 +21705,7 @@ class binary_reader
|
|||||||
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
||||||
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
||||||
|
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||||
|
|
||||||
@@ -21752,7 +21764,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If BJData type marker is 'B' decode as binary
|
// If BJData type marker is 'B' decode as binary
|
||||||
if (size_and_type.first != npos && size_and_type.second == 'B')
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
binary_t result;
|
binary_t result;
|
||||||
return get_binary(size_and_type.first, result) && sax->binary(result);
|
return get_binary(size_and_type.first, result) && sax->binary(result);
|
||||||
@@ -21801,7 +21813,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not accept ND-array size in objects in BJData
|
// do not accept ND-array size in objects in BJData
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
||||||
}
|
}
|
||||||
@@ -23082,9 +23094,8 @@ class binary_reader
|
|||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief check whether @a marker is a type marker that may follow '$'
|
@brief check whether @a marker is a type marker that may follow '$'
|
||||||
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C.
|
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||||
The markers BJData excludes (F H N S T Z [ {) are the ones
|
(BJData's excluded markers are rejected separately).
|
||||||
binary_writer::is_bjdata_excluded_type_marker() lists; keep the two in sync.
|
|
||||||
*/
|
*/
|
||||||
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||||
{
|
{
|
||||||
@@ -23119,6 +23130,20 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||||
|
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||||
|
|
||||||
|
Mirrors binary_writer's @ref binary_writer::is_bjdata_excluded_type_marker
|
||||||
|
"is_bjdata_excluded_type_marker()`, which encodes the same list the other
|
||||||
|
way; keep the two in sync.
|
||||||
|
*/
|
||||||
|
static constexpr bool is_bjd_excluded_optimized_type(const char_int_type marker) noexcept
|
||||||
|
{
|
||||||
|
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|
||||||
|
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief look up the ND-array element type name for a BJData dtype marker
|
@brief look up the ND-array element type name for a BJData dtype marker
|
||||||
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
||||||
@@ -31076,19 +31101,22 @@ class binary_writer
|
|||||||
// negative fixnum
|
// negative fixnum
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||||
{
|
{
|
||||||
// int 8
|
// int 8
|
||||||
oa.write_character(to_char_type(0xD0));
|
oa.write_character(to_char_type(0xD0));
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||||
{
|
{
|
||||||
// int 16
|
// int 16
|
||||||
oa.write_character(to_char_type(0xD1));
|
oa.write_character(to_char_type(0xD1));
|
||||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||||
{
|
{
|
||||||
// int 32
|
// int 32
|
||||||
oa.write_character(to_char_type(0xD2));
|
oa.write_character(to_char_type(0xD2));
|
||||||
@@ -32004,7 +32032,7 @@ class binary_writer
|
|||||||
Containers, strings, high-precision numbers, booleans and null cannot be
|
Containers, strings, high-precision numbers, booleans and null cannot be
|
||||||
declared as the single type of an optimized container in BJData; such a
|
declared as the single type of an optimized container in BJData; such a
|
||||||
container is written unoptimized. The reader rejects them with the same
|
container is written unoptimized. The reader rejects them with the same
|
||||||
list (binary_reader::is_valid_optimized_type()).
|
list (binary_reader::is_bjd_excluded_optimized_type()).
|
||||||
*/
|
*/
|
||||||
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
||||||
{
|
{
|
||||||
@@ -38345,6 +38373,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
// the type and value of a basic_json without its base class (defined
|
||||||
|
// below); the destruction helpers of json_value use it as a stand-in
|
||||||
|
struct data;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief a JSON value
|
@brief a JSON value
|
||||||
|
|
||||||
@@ -38521,27 +38553,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// raw, allocation-free transfer of m_data from src to dst: no
|
// raw, allocation-free transfer of the type and value from src to
|
||||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
// dst: no set_parents()/assert_invariant() (the former is
|
||||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
||||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
// walk below quadratic); dst takes ownership, src is left as
|
||||||
static void take(basic_json& dst, basic_json& src) noexcept
|
// value_t::null.
|
||||||
|
static void take(data& dst, data& src) noexcept
|
||||||
{
|
{
|
||||||
dst.m_data.m_type = src.m_data.m_type;
|
dst.m_type = src.m_type;
|
||||||
dst.m_data.m_value = src.m_data.m_value;
|
dst.m_value = src.m_value;
|
||||||
src.m_data.m_type = value_t::null;
|
src.m_type = value_t::null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// true if v is not an array/object, or is an already-empty one
|
// true if v is not an array/object, or is an already-empty one
|
||||||
static bool has_no_children(const basic_json& v) noexcept
|
static bool has_no_children(const data& v) noexcept
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->empty();
|
return v.m_value.array->empty();
|
||||||
}
|
}
|
||||||
if (v.m_data.m_type == value_t::object)
|
if (v.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.object->empty();
|
return v.m_value.object->empty();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -38550,21 +38583,21 @@ private:
|
|||||||
// its own, i.e. v can be freed in one go (its destructor then
|
// its own, i.e. v can be freed in one go (its destructor then
|
||||||
// destroys scalars and empty containers only, so it recurses at most
|
// destroys scalars and empty containers only, so it recurses at most
|
||||||
// one level deep); stops at the first structured child it meets
|
// one level deep); stops at the first structured child it meets
|
||||||
static bool all_children_leaves(const basic_json& v) noexcept
|
static bool all_children_leaves(const data& v) noexcept
|
||||||
{
|
{
|
||||||
// array_t only needs forward iterators (see custom_array_type)
|
// array_t only needs forward iterators (see custom_array_type)
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
const auto& a = *v.m_data.m_value.array;
|
const auto& a = *v.m_value.array;
|
||||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el);
|
return has_no_children(el.m_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const auto& o = *v.m_data.m_value.object;
|
const auto& o = *v.m_value.object;
|
||||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||||
{
|
{
|
||||||
return has_no_children(el.second);
|
return has_no_children(el.second.m_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38596,30 +38629,30 @@ private:
|
|||||||
|
|
||||||
// the child of a non-empty array/object v that the walk in
|
// the child of a non-empty array/object v that the walk in
|
||||||
// destroy_container() continues with (see walk_child_it() above)
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
static basic_json& walk_child(basic_json& v)
|
static data& walk_child(data& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->back();
|
return v.m_value.array->back().m_data;
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
return walk_child_it(*v.m_value.object)->second.m_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(basic_json& v)
|
static void pop_walk_child(data& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
v.m_data.m_value.array->pop_back();
|
v.m_value.array->pop_back();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38628,23 +38661,23 @@ private:
|
|||||||
// recurses at most one level deep); this is the same allocator-based
|
// recurses at most one level deep); this is the same allocator-based
|
||||||
// free the old recursive implementation used, just factored out so
|
// free the old recursive implementation used, just factored out so
|
||||||
// every level of the walk in destroy() can share it
|
// every level of the walk in destroy() can share it
|
||||||
static void free_container(basic_json& v) noexcept
|
static void free_container(data& v) noexcept
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
AllocatorType<array_t> alloc;
|
AllocatorType<array_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_type == value_t::object);
|
||||||
AllocatorType<object_t> alloc;
|
AllocatorType<object_t> alloc;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
||||||
}
|
}
|
||||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
v.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -38675,14 +38708,16 @@ public:
|
|||||||
|
|
||||||
// This json_value is not itself a basic_json, so the top-level
|
// This json_value is not itself a basic_json, so the top-level
|
||||||
// container is first moved into a local stand-in ("cur"); a
|
// container is first moved into a local stand-in ("cur"); a
|
||||||
// default-constructed basic_json has a null pointer in its
|
// default-constructed data has a null pointer in its m_value
|
||||||
// m_value (see data::m_value's initializer), so swapping it
|
// (see data::m_value's initializer), so swapping it with *this
|
||||||
// with *this leaves this union's own pointer null, and it is
|
// leaves this union's own pointer null, and it is never looked
|
||||||
// never looked at or freed a second time.
|
// at or freed a second time. The stand-ins are data, not
|
||||||
basic_json cur;
|
// basic_json, so that destroying a value never constructs a
|
||||||
cur.m_data.m_type = t;
|
// CustomBaseClass, whose constructor may allocate (#5995).
|
||||||
|
data cur;
|
||||||
|
cur.m_type = t;
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(cur.m_data.m_value, *this);
|
swap(cur.m_value, *this);
|
||||||
|
|
||||||
destroy_bounded(cur, 0);
|
destroy_bounded(cur, 0);
|
||||||
}
|
}
|
||||||
@@ -38691,7 +38726,7 @@ public:
|
|||||||
// The first recursion_depth_limit() levels are destroyed by plain
|
// The first recursion_depth_limit() levels are destroyed by plain
|
||||||
// recursion, which is the fastest way and cannot throw; anything
|
// recursion, which is the fastest way and cannot throw; anything
|
||||||
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||||
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
static void destroy_bounded(data& v, std::size_t depth) noexcept
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
@@ -38699,23 +38734,23 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_data.m_value.array)
|
for (auto& child : *v.m_value.array)
|
||||||
{
|
{
|
||||||
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child, depth + 1);
|
destroy_bounded(child.m_data, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (auto& child : *v.m_data.m_value.object)
|
for (auto& child : *v.m_value.object)
|
||||||
{
|
{
|
||||||
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||||
{
|
{
|
||||||
destroy_bounded(child.second, depth + 1);
|
destroy_bounded(child.second.m_data, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38742,18 +38777,15 @@ public:
|
|||||||
// array/object, which neither allocates nor recurses more
|
// array/object, which neither allocates nor recurses more
|
||||||
// than one level deep.
|
// than one level deep.
|
||||||
//
|
//
|
||||||
// This json_value is not itself a basic_json, so the
|
// Like destroy_container(), the walk keeps the containers it
|
||||||
// top-level container is first moved into a local stand-in
|
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
// construct a CustomBaseClass.
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
static void destroy_walk(data& top) noexcept
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
static void destroy_walk(basic_json& top) noexcept
|
|
||||||
{
|
{
|
||||||
basic_json cur;
|
data cur;
|
||||||
take(cur, top);
|
take(cur, top);
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
data prev; // value_t::null: no parent
|
||||||
|
|
||||||
// true right after cur was entered: its children have not been
|
// true right after cur was entered: its children have not been
|
||||||
// looked at yet. If none of them has children of its own, cur is
|
// looked at yet. If none of them has children of its own, cur is
|
||||||
@@ -38767,7 +38799,7 @@ public:
|
|||||||
fresh = false;
|
fresh = false;
|
||||||
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
if (prev.m_type == value_t::null)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
return; // back at the top with nothing left to do
|
return; // back at the top with nothing left to do
|
||||||
@@ -38776,7 +38808,7 @@ public:
|
|||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
basic_json gp;
|
data gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, walk_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_walk_child(prev);
|
||||||
|
|
||||||
@@ -38787,7 +38819,7 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_child_ref = walk_child(cur);
|
data& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_child_ref))
|
||||||
{
|
{
|
||||||
@@ -38799,7 +38831,7 @@ public:
|
|||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
basic_json tmp;
|
data tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_child_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_child_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
|
|||||||
@@ -78,25 +78,23 @@ TEST_CASE("BJData")
|
|||||||
{
|
{
|
||||||
SECTION("binary_reader BJData lookup tables")
|
SECTION("binary_reader BJData lookup tables")
|
||||||
{
|
{
|
||||||
// bjd_type_name() is static; is_valid_optimized_type() needs a BJData reader
|
// both lookups are static member functions
|
||||||
std::vector<std::uint8_t> const data;
|
std::vector<std::uint8_t> const data;
|
||||||
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
|
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
|
||||||
|
|
||||||
// the markers BJData excludes as optimized types must match
|
// the excluded optimized-type markers must match binary_writer's
|
||||||
// binary_writer's is_bjdata_excluded_type_marker(), which encodes the
|
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
|
||||||
// same 8 markers
|
|
||||||
reader_t reader(nlohmann::detail::input_adapter(data), json::input_format_t::bjdata);
|
|
||||||
for (const char marker :
|
for (const char marker :
|
||||||
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z', 'x'
|
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CHECK(!reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
|
CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
|
||||||
}
|
}
|
||||||
for (const char marker :
|
for (const char marker :
|
||||||
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'h'
|
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CHECK(reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
|
CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// every dtype marker must round-trip to its ND-array type name
|
// every dtype marker must round-trip to its ND-array type name
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -495,3 +496,92 @@ TEST_CASE("Regression test for issue #5674 - copy construction must not require
|
|||||||
CHECK(*c == 1);
|
CHECK(*c == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// counts the constructions and destructions of the base class (#5995)
|
||||||
|
struct counting_base
|
||||||
|
{
|
||||||
|
static std::size_t constructions;
|
||||||
|
static std::size_t destructions;
|
||||||
|
|
||||||
|
counting_base() noexcept
|
||||||
|
{
|
||||||
|
++constructions;
|
||||||
|
}
|
||||||
|
counting_base(const counting_base& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
++constructions;
|
||||||
|
}
|
||||||
|
counting_base(counting_base&& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
++constructions;
|
||||||
|
}
|
||||||
|
counting_base& operator=(const counting_base& /*unused*/) noexcept = default;
|
||||||
|
counting_base& operator=(counting_base&& /*unused*/) noexcept = default;
|
||||||
|
~counting_base()
|
||||||
|
{
|
||||||
|
++destructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reset() noexcept
|
||||||
|
{
|
||||||
|
constructions = 0;
|
||||||
|
destructions = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t counting_base::constructions = 0;
|
||||||
|
std::size_t counting_base::destructions = 0;
|
||||||
|
|
||||||
|
using json_with_counting_base = nlohmann::json::with_base_class_t<counting_base>;
|
||||||
|
|
||||||
|
TEST_CASE("destroying a value does not construct the custom base class")
|
||||||
|
{
|
||||||
|
// regression test for #5995: destroying an array or object used a
|
||||||
|
// default-constructed basic_json as a stand-in, which constructed a base
|
||||||
|
// class that belonged to no value (and could allocate in the noexcept
|
||||||
|
// destructor); now every destroyed value destroys exactly its own base
|
||||||
|
// object, and none is constructed
|
||||||
|
const auto check_destroy = [](std::unique_ptr<json_with_counting_base> j, std::size_t values)
|
||||||
|
{
|
||||||
|
counting_base::reset();
|
||||||
|
j = nullptr;
|
||||||
|
CHECK(counting_base::constructions == 0);
|
||||||
|
CHECK(counting_base::destructions == values);
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("scalar")
|
||||||
|
{
|
||||||
|
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(42)), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array and object")
|
||||||
|
{
|
||||||
|
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::parse(R"({"a":[1,2,3],"b":{"c":true}})"))), 7);
|
||||||
|
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::array())), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper than the recursion depth limit")
|
||||||
|
{
|
||||||
|
// alternating arrays and objects, so both kinds of container are
|
||||||
|
// walked by destroy_walk()
|
||||||
|
constexpr std::size_t depth = 1000;
|
||||||
|
std::unique_ptr<json_with_counting_base> j(new json_with_counting_base(json_with_counting_base::array({1, 2})));
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
json_with_counting_base outer = (i % 2 == 0) ? json_with_counting_base::object() : json_with_counting_base::array();
|
||||||
|
if (outer.is_object())
|
||||||
|
{
|
||||||
|
outer["x"] = std::move(*j);
|
||||||
|
outer["y"] = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outer.push_back(std::move(*j));
|
||||||
|
outer.push_back(true);
|
||||||
|
}
|
||||||
|
*j = std::move(outer);
|
||||||
|
}
|
||||||
|
// the innermost array with its two elements, and two values per level
|
||||||
|
check_destroy(std::move(j), 3 + (2 * depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user