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
@@ -406,7 +406,6 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
|||||||
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
||||||
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
||||||
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
||||||
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
|
|
||||||
|
|
||||||
### Compatible types
|
### Compatible types
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class output_buffer
|
|||||||
: m_out(sized(out, estimate))
|
: m_out(sized(out, estimate))
|
||||||
, m_pos(&m_out[0])
|
, m_pos(&m_out[0])
|
||||||
, m_end(m_pos + m_out.size())
|
, m_end(m_pos + m_out.size())
|
||||||
, m_reserved((std::max)(estimate, m_out.size()))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void finish()
|
void finish()
|
||||||
@@ -117,12 +116,11 @@ class output_buffer
|
|||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation; not a clamp: the
|
// estimate is never copied to a larger allocation; not a clamp: the
|
||||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_reserved)
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_reserved = (std::max)(m_reserved * 2, size);
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
m_out.reserve(m_reserved);
|
|
||||||
}
|
}
|
||||||
m_out.resize(size);
|
m_out.resize(size);
|
||||||
m_pos = &m_out[0] + used;
|
m_pos = &m_out[0] + used;
|
||||||
@@ -132,9 +130,6 @@ class output_buffer
|
|||||||
StringType& m_out;
|
StringType& m_out;
|
||||||
char* m_pos;
|
char* m_pos;
|
||||||
char* m_end;
|
char* m_end;
|
||||||
/// what was last passed to reserve(), a lower bound of the capacity
|
|
||||||
/// (StringType need not have a capacity() member)
|
|
||||||
std::size_t m_reserved;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The length of the run at s that dump() writes unchanged without
|
/// The length of the run at s that dump() writes unchanged without
|
||||||
|
|||||||
+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
+67
-63
@@ -38373,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
|
||||||
|
|
||||||
@@ -38549,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;
|
||||||
}
|
}
|
||||||
@@ -38578,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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38624,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38656,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:
|
||||||
@@ -38703,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);
|
||||||
}
|
}
|
||||||
@@ -38719,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()))
|
||||||
{
|
{
|
||||||
@@ -38727,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38770,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
|
||||||
@@ -38795,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
|
||||||
@@ -38804,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);
|
||||||
|
|
||||||
@@ -38815,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))
|
||||||
{
|
{
|
||||||
@@ -38827,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);
|
||||||
|
|||||||
Generated
+4
-9
@@ -8511,7 +8511,6 @@ class output_buffer
|
|||||||
: m_out(sized(out, estimate))
|
: m_out(sized(out, estimate))
|
||||||
, m_pos(&m_out[0])
|
, m_pos(&m_out[0])
|
||||||
, m_end(m_pos + m_out.size())
|
, m_end(m_pos + m_out.size())
|
||||||
, m_reserved((std::max)(estimate, m_out.size()))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void finish()
|
void finish()
|
||||||
@@ -8581,12 +8580,11 @@ class output_buffer
|
|||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation; not a clamp: the
|
// estimate is never copied to a larger allocation; not a clamp: the
|
||||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_reserved)
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_reserved = (std::max)(m_reserved * 2, size);
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
m_out.reserve(m_reserved);
|
|
||||||
}
|
}
|
||||||
m_out.resize(size);
|
m_out.resize(size);
|
||||||
m_pos = &m_out[0] + used;
|
m_pos = &m_out[0] + used;
|
||||||
@@ -8596,9 +8594,6 @@ class output_buffer
|
|||||||
StringType& m_out;
|
StringType& m_out;
|
||||||
char* m_pos;
|
char* m_pos;
|
||||||
char* m_end;
|
char* m_end;
|
||||||
/// what was last passed to reserve(), a lower bound of the capacity
|
|
||||||
/// (StringType need not have a capacity() member)
|
|
||||||
std::size_t m_reserved;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The length of the run at s that dump() writes unchanged without
|
/// The length of the run at s that dump() writes unchanged without
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <nlohmann/json_view.hpp>
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -484,56 +483,4 @@ TEST_CASE("alternative string type")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("alternative string type with json_view")
|
|
||||||
{
|
|
||||||
// regression test for #5994: basic_json_view::dump() called
|
|
||||||
// StringType::capacity(), which alt_string (and the documented StringType
|
|
||||||
// requirements) do not provide
|
|
||||||
using alt_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::read_only>;
|
|
||||||
using alt_editable_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::editable>;
|
|
||||||
|
|
||||||
const char* text = R"({"a":[1,"x\u00e4"],"b":{"c":true,"d":null},"e":2.5})";
|
|
||||||
const alt_json j = alt_json::parse(text);
|
|
||||||
|
|
||||||
SECTION("read-only document")
|
|
||||||
{
|
|
||||||
const alt_document d = alt_document::parse(text);
|
|
||||||
CHECK(d.root().dump() == j.dump());
|
|
||||||
CHECK(d.root().dump(2) == j.dump(2));
|
|
||||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
|
||||||
CHECK(d.root().materialize() == j);
|
|
||||||
CHECK(d.root().contains(alt_string("b")));
|
|
||||||
CHECK(d.root()[alt_string("b")][alt_string("c")].dump() == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("long output")
|
|
||||||
{
|
|
||||||
// more than one growth step of the output buffer
|
|
||||||
alt_json big = alt_json::array();
|
|
||||||
for (int i = 0; i < 20000; ++i)
|
|
||||||
{
|
|
||||||
big.push_back(alt_json::parse(text));
|
|
||||||
}
|
|
||||||
const alt_string big_text = big.dump();
|
|
||||||
const alt_document d = alt_document::parse(big_text);
|
|
||||||
CHECK(d.root().dump() == big_text);
|
|
||||||
CHECK(d.root().dump(4) == big.dump(4));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("editable document")
|
|
||||||
{
|
|
||||||
alt_editable_document d = alt_editable_document::parse(text);
|
|
||||||
d.set(d.root(), "f", "new");
|
|
||||||
d.push_back(d.root()[alt_string("a")], 3);
|
|
||||||
alt_json expected = j;
|
|
||||||
expected["f"] = "new";
|
|
||||||
expected["a"].push_back(3);
|
|
||||||
CHECK(d.root().materialize() == expected);
|
|
||||||
CHECK(alt_json::parse(d.root().dump()) == expected);
|
|
||||||
|
|
||||||
const alt_editable_document loaded = alt_editable_document::load(d.save());
|
|
||||||
CHECK(loaded.root().materialize() == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -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