Compare commits

..
Author SHA1 Message Date
Niels Lohmann db73a6044f UBJSON/BJData reader: reject a container count equal to SIZE_MAX
The reader marks a container whose count is not known by npos, which
is SIZE_MAX. A count of exactly that value was stored unchanged and
then read as "no count", so the BJData input

    [ # M ff ff ff ff ff ff ff ff i 01 ]

parsed as [1] instead of throwing out_of_range.408 as a count of
2^64-2 does. Where size_t is 32 bits, the same holds for 'm' and 'L'
counts of 2^32-1, in UBJSON too.

The counts of 'm', 'M' and the signed types now go through
store_ubjson_count(), which rejects npos with "integer value overflow"
(the message a count beyond size_t already gets), as the CBOR reader
does in get_cbor_container_size(). An ND-array dimension of 2^64-1 gets
the same message now, where it was "excessive ndarray size caused
overflow" (the product check) on 64-bit platforms.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-02 07:01:16 +01:00
6 changed files with 210 additions and 240 deletions

No files matched your search

@@ -3123,6 +3123,27 @@ class binary_reader
return true; return true;
} }
/*!
@brief store the count of an optimized container (or the length of a string)
A count equal to npos is rejected: npos marks a container whose count is
not known, so a count of that value would be read as "no count" (compare
get_cbor_container_size()). No container can hold that many elements.
@param[in] count the count read from the input
@param[out] result set to @a count unless it is rejected
@return whether @a count is usable
*/
bool store_ubjson_count(const std::size_t count, std::size_t& result)
{
if (JSON_HEDLEY_UNLIKELY(count == npos))
{
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
}
result = count;
return true;
}
/*! /*!
@brief read a UBJSON/BJData optimized-container count of a signed marker @brief read a UBJSON/BJData optimized-container count of a signed marker
type ('i', 'I', 'l', 'L') and narrow it to std::size_t type ('i', 'I', 'l', 'L') and narrow it to std::size_t
@@ -3153,8 +3174,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return store_ubjson_count(static_cast<std::size_t>(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
return true;
} }
/*! /*!
@@ -3231,8 +3251,7 @@ class binary_reader
{ {
return false; return false;
} }
result = conditional_static_cast<std::size_t>(number); return store_ubjson_count(conditional_static_cast<std::size_t>(number), result);
return true;
} }
case 'M': case 'M':
@@ -3250,8 +3269,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = detail::conditional_static_cast<std::size_t>(number); return store_ubjson_count(detail::conditional_static_cast<std::size_t>(number), result);
return true;
} }
case '[': case '[':
+63 -67
View File
@@ -543,10 +543,6 @@ 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
@@ -723,28 +719,27 @@ 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 the type and value from src to // raw, allocation-free transfer of m_data from src to dst: no
// dst: no set_parents()/assert_invariant() (the former is // set_parents()/assert_invariant() (the former is O(#children) per
// O(#children) per call under JSON_DIAGNOSTICS, which would make the // call under JSON_DIAGNOSTICS, which would make the walk below
// walk below quadratic); dst takes ownership, src is left as // quadratic); dst takes ownership, src is left as value_t::null.
// value_t::null. static void take(basic_json& dst, basic_json& src) noexcept
static void take(data& dst, data& src) noexcept
{ {
dst.m_type = src.m_type; dst.m_data.m_type = src.m_data.m_type;
dst.m_value = src.m_value; dst.m_data.m_value = src.m_data.m_value;
src.m_type = value_t::null; src.m_data.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 data& v) noexcept static bool has_no_children(const basic_json& v) noexcept
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->empty(); return v.m_data.m_value.array->empty();
} }
if (v.m_type == value_t::object) if (v.m_data.m_type == value_t::object)
{ {
return v.m_value.object->empty(); return v.m_data.m_value.object->empty();
} }
return true; return true;
} }
@@ -753,21 +748,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 data& v) noexcept static bool all_children_leaves(const basic_json& 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_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_value.array; const auto& a = *v.m_data.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.m_data); return has_no_children(el);
}); });
} }
const auto& o = *v.m_value.object; const auto& o = *v.m_data.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.m_data); return has_no_children(el.second);
}); });
} }
@@ -799,30 +794,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 data& walk_child(data& v) static basic_json& walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->back().m_data; return v.m_data.m_value.array->back();
} }
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
return walk_child_it(*v.m_value.object)->second.m_data; return walk_child_it(*v.m_data.m_value.object)->second;
} }
// 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(data& v) static void pop_walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
v.m_value.array->pop_back(); v.m_data.m_value.array->pop_back();
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
v.m_value.object->erase(walk_child_it(*v.m_value.object)); v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
} }
} }
@@ -831,23 +826,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(data& v) noexcept static void free_container(basic_json& 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_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
AllocatorType<array_t> alloc; AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
AllocatorType<object_t> alloc; AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
} }
v.m_type = value_t::null; // avoid a double free if v is later destructed v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
} }
public: public:
@@ -878,16 +873,14 @@ 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 data has a null pointer in its m_value // default-constructed basic_json has a null pointer in its
// (see data::m_value's initializer), so swapping it with *this // m_value (see data::m_value's initializer), so swapping it
// leaves this union's own pointer null, and it is never looked // with *this leaves this union's own pointer null, and it is
// at or freed a second time. The stand-ins are data, not // never looked at or freed a second time.
// basic_json, so that destroying a value never constructs a basic_json cur;
// CustomBaseClass, whose constructor may allocate (#5995). cur.m_data.m_type = t;
data cur;
cur.m_type = t;
using std::swap; using std::swap;
swap(cur.m_value, *this); swap(cur.m_data.m_value, *this);
destroy_bounded(cur, 0); destroy_bounded(cur, 0);
} }
@@ -896,7 +889,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(data& v, std::size_t depth) noexcept static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
{ {
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit())) if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{ {
@@ -904,23 +897,23 @@ public:
return; return;
} }
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
for (auto& child : *v.m_value.array) for (auto& child : *v.m_data.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.m_data, depth + 1); destroy_bounded(child, depth + 1);
} }
} }
} }
else else
{ {
for (auto& child : *v.m_value.object) for (auto& child : *v.m_data.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.m_data, depth + 1); destroy_bounded(child.second, depth + 1);
} }
} }
} }
@@ -947,15 +940,18 @@ 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.
// //
// Like destroy_container(), the walk keeps the containers it // This json_value is not itself a basic_json, so the
// holds in data stand-ins (cur, prev, gp, tmp), which never // top-level container is first moved into a local stand-in
// construct a CustomBaseClass. // ("cur"); a default-constructed basic_json has a null
static void destroy_walk(data& top) noexcept // pointer in its m_value (see data::m_value's initializer),
// 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
{ {
data cur; basic_json cur;
take(cur, top); take(cur, top);
data prev; // value_t::null: no parent basic_json 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
@@ -969,7 +965,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_type == value_t::null) if (prev.m_data.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
@@ -978,7 +974,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
data gp; basic_json gp;
take(gp, walk_child(prev)); take(gp, walk_child(prev));
pop_walk_child(prev); pop_walk_child(prev);
@@ -989,7 +985,7 @@ public:
continue; continue;
} }
data& cur_child_ref = walk_child(cur); basic_json& cur_child_ref = walk_child(cur);
if (has_no_children(cur_child_ref)) if (has_no_children(cur_child_ref))
{ {
@@ -1001,7 +997,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
data tmp; basic_json 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);
+87 -73
View File
@@ -21130,6 +21130,27 @@ class binary_reader
return true; return true;
} }
/*!
@brief store the count of an optimized container (or the length of a string)
A count equal to npos is rejected: npos marks a container whose count is
not known, so a count of that value would be read as "no count" (compare
get_cbor_container_size()). No container can hold that many elements.
@param[in] count the count read from the input
@param[out] result set to @a count unless it is rejected
@return whether @a count is usable
*/
bool store_ubjson_count(const std::size_t count, std::size_t& result)
{
if (JSON_HEDLEY_UNLIKELY(count == npos))
{
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
}
result = count;
return true;
}
/*! /*!
@brief read a UBJSON/BJData optimized-container count of a signed marker @brief read a UBJSON/BJData optimized-container count of a signed marker
type ('i', 'I', 'l', 'L') and narrow it to std::size_t type ('i', 'I', 'l', 'L') and narrow it to std::size_t
@@ -21160,8 +21181,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char return store_ubjson_count(static_cast<std::size_t>(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
return true;
} }
/*! /*!
@@ -21238,8 +21258,7 @@ class binary_reader
{ {
return false; return false;
} }
result = conditional_static_cast<std::size_t>(number); return store_ubjson_count(conditional_static_cast<std::size_t>(number), result);
return true;
} }
case 'M': case 'M':
@@ -21257,8 +21276,7 @@ class binary_reader
{ {
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
} }
result = detail::conditional_static_cast<std::size_t>(number); return store_ubjson_count(detail::conditional_static_cast<std::size_t>(number), result);
return true;
} }
case '[': case '[':
@@ -38373,10 +38391,6 @@ 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
@@ -38553,28 +38567,27 @@ 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 the type and value from src to // raw, allocation-free transfer of m_data from src to dst: no
// dst: no set_parents()/assert_invariant() (the former is // set_parents()/assert_invariant() (the former is O(#children) per
// O(#children) per call under JSON_DIAGNOSTICS, which would make the // call under JSON_DIAGNOSTICS, which would make the walk below
// walk below quadratic); dst takes ownership, src is left as // quadratic); dst takes ownership, src is left as value_t::null.
// value_t::null. static void take(basic_json& dst, basic_json& src) noexcept
static void take(data& dst, data& src) noexcept
{ {
dst.m_type = src.m_type; dst.m_data.m_type = src.m_data.m_type;
dst.m_value = src.m_value; dst.m_data.m_value = src.m_data.m_value;
src.m_type = value_t::null; src.m_data.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 data& v) noexcept static bool has_no_children(const basic_json& v) noexcept
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->empty(); return v.m_data.m_value.array->empty();
} }
if (v.m_type == value_t::object) if (v.m_data.m_type == value_t::object)
{ {
return v.m_value.object->empty(); return v.m_data.m_value.object->empty();
} }
return true; return true;
} }
@@ -38583,21 +38596,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 data& v) noexcept static bool all_children_leaves(const basic_json& 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_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_value.array; const auto& a = *v.m_data.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.m_data); return has_no_children(el);
}); });
} }
const auto& o = *v.m_value.object; const auto& o = *v.m_data.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.m_data); return has_no_children(el.second);
}); });
} }
@@ -38629,30 +38642,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 data& walk_child(data& v) static basic_json& walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->back().m_data; return v.m_data.m_value.array->back();
} }
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
return walk_child_it(*v.m_value.object)->second.m_data; return walk_child_it(*v.m_data.m_value.object)->second;
} }
// 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(data& v) static void pop_walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
v.m_value.array->pop_back(); v.m_data.m_value.array->pop_back();
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
v.m_value.object->erase(walk_child_it(*v.m_value.object)); v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
} }
} }
@@ -38661,23 +38674,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(data& v) noexcept static void free_container(basic_json& 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_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
AllocatorType<array_t> alloc; AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
AllocatorType<object_t> alloc; AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
} }
v.m_type = value_t::null; // avoid a double free if v is later destructed v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
} }
public: public:
@@ -38708,16 +38721,14 @@ 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 data has a null pointer in its m_value // default-constructed basic_json has a null pointer in its
// (see data::m_value's initializer), so swapping it with *this // m_value (see data::m_value's initializer), so swapping it
// leaves this union's own pointer null, and it is never looked // with *this leaves this union's own pointer null, and it is
// at or freed a second time. The stand-ins are data, not // never looked at or freed a second time.
// basic_json, so that destroying a value never constructs a basic_json cur;
// CustomBaseClass, whose constructor may allocate (#5995). cur.m_data.m_type = t;
data cur;
cur.m_type = t;
using std::swap; using std::swap;
swap(cur.m_value, *this); swap(cur.m_data.m_value, *this);
destroy_bounded(cur, 0); destroy_bounded(cur, 0);
} }
@@ -38726,7 +38737,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(data& v, std::size_t depth) noexcept static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
{ {
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit())) if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{ {
@@ -38734,23 +38745,23 @@ public:
return; return;
} }
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
for (auto& child : *v.m_value.array) for (auto& child : *v.m_data.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.m_data, depth + 1); destroy_bounded(child, depth + 1);
} }
} }
} }
else else
{ {
for (auto& child : *v.m_value.object) for (auto& child : *v.m_data.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.m_data, depth + 1); destroy_bounded(child.second, depth + 1);
} }
} }
} }
@@ -38777,15 +38788,18 @@ 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.
// //
// Like destroy_container(), the walk keeps the containers it // This json_value is not itself a basic_json, so the
// holds in data stand-ins (cur, prev, gp, tmp), which never // top-level container is first moved into a local stand-in
// construct a CustomBaseClass. // ("cur"); a default-constructed basic_json has a null
static void destroy_walk(data& top) noexcept // pointer in its m_value (see data::m_value's initializer),
// 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
{ {
data cur; basic_json cur;
take(cur, top); take(cur, top);
data prev; // value_t::null: no parent basic_json 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
@@ -38799,7 +38813,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_type == value_t::null) if (prev.m_data.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
@@ -38808,7 +38822,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
data gp; basic_json gp;
take(gp, walk_child(prev)); take(gp, walk_child(prev));
pop_walk_child(prev); pop_walk_child(prev);
@@ -38819,7 +38833,7 @@ public:
continue; continue;
} }
data& cur_child_ref = walk_child(cur); basic_json& cur_child_ref = walk_child(cur);
if (has_no_children(cur_child_ref)) if (has_no_children(cur_child_ref))
{ {
@@ -38831,7 +38845,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
data tmp; basic_json 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);
+24 -4
View File
@@ -3211,14 +3211,34 @@ TEST_CASE("BJData")
#endif #endif
CHECK(json::from_bjdata(vM, true, false).is_discarded()); CHECK(json::from_bjdata(vM, true, false).is_discarded());
#if SIZE_MAX != 0xffffffff // the first dimension is 2^64-1, which is rejected as a count (see below)
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
#endif
CHECK(json::from_bjdata(vMX, true, false).is_discarded()); CHECK(json::from_bjdata(vMX, true, false).is_discarded());
} }
SECTION("optimized array: count equal to SIZE_MAX")
{
// the reader marks a container without a count by SIZE_MAX, so
// a count of that value must not be read as "no count"
// (it used to parse as [1])
json _;
#if SIZE_MAX != 0xffffffff
std::vector<uint8_t> const vM = {'[', '#', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vM, true, false).is_discarded());
// one less is an ordinary excessive count
std::vector<uint8_t> const vM1 = {'[', '#', 'M', 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM1), "[json.exception.out_of_range.408] excessive array size: 18446744073709551614", json::out_of_range&);
#else
std::vector<uint8_t> const vm = {'[', '#', 'm', 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vm, true, false).is_discarded());
std::vector<uint8_t> const vL = {'[', '#', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&);
CHECK(json::from_bjdata(vL, true, false).is_discarded());
#endif
}
SECTION("optimized array: integer value overflow") SECTION("optimized array: integer value overflow")
{ {
#if SIZE_MAX == 0xffffffff #if SIZE_MAX == 0xffffffff
-90
View File
@@ -7,7 +7,6 @@
// 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>
@@ -496,92 +495,3 @@ 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));
}
}
+12
View File
@@ -2360,6 +2360,18 @@ TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
} }
} }
SECTION("a count equal to SIZE_MAX is rejected")
{
// the reader marks a container without a count by SIZE_MAX, so a
// count of that value must not be read as "no count"; UBJSON's
// largest count type (int64) can only reach it where size_t is 32 bits
#if SIZE_MAX == 0xffffffff
const std::vector<uint8_t> input = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(input), "[json.exception.out_of_range.408] syntax error while parsing UBJSON size: integer value overflow", json::out_of_range&);
CHECK(json::from_ubjson(input, true, false).is_discarded());
#endif
}
SECTION("ordinary counts are unaffected") SECTION("ordinary counts are unaffected")
{ {
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', '$', 'Z', '#', 'i', 3})) == json({nullptr, nullptr, nullptr})); CHECK(json::from_ubjson(std::vector<uint8_t>({'[', '$', 'Z', '#', 'i', 3})) == json({nullptr, nullptr, nullptr}));