Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98cfd55317 | ||
|
|
e96469f783 | ||
|
|
3720e4b3aa |
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // remove, fill, find, none_of, min
|
||||
#include <algorithm> // copy, none_of, min, search
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // isfinite
|
||||
@@ -2095,31 +2095,35 @@ class serializer
|
||||
// check if the buffer was large enough
|
||||
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// look up the locale's thousands separator and decimal point now,
|
||||
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
||||
// look up the locale's decimal point now, matching what
|
||||
// snprintf_float() just used (see get_decimal_point() in
|
||||
// number_parse.hpp); it is a string, which can be longer than one
|
||||
// byte (U+066B in ps_AF, #5993).
|
||||
// A thousands separator needs no handling: %g never groups digits.
|
||||
const auto* loc = std::localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
||||
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
||||
|
||||
// erase thousands separators
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
const char* const decimal_point = (loc->decimal_point == nullptr) ? "" : loc->decimal_point;
|
||||
const std::size_t decimal_point_len = std::char_traits<char>::length(decimal_point);
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' && decimal_point != '.')
|
||||
if (decimal_point_len != 0 && !(decimal_point_len == 1 && decimal_point[0] == '.'))
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
const auto first = number_buffer.begin();
|
||||
const auto last = first + len;
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::search returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::search(first, last, decimal_point, decimal_point + decimal_point_len);
|
||||
if (dec_pos != last)
|
||||
{
|
||||
*dec_pos = '.';
|
||||
if (decimal_point_len > 1)
|
||||
{
|
||||
// move the fraction and exponent behind the '.' (std::copy
|
||||
// allows the overlap, as the destination is before the source)
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::copy(dec_pos + static_cast<std::ptrdiff_t>(decimal_point_len), last, dec_pos + 1);
|
||||
*end = '\0';
|
||||
len = end - first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+63
-67
@@ -543,10 +543,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
////////////////////////
|
||||
|
||||
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
|
||||
|
||||
@@ -723,28 +719,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of the type and value from src to
|
||||
// dst: no set_parents()/assert_invariant() (the former is
|
||||
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
||||
// walk below quadratic); dst takes ownership, src is left as
|
||||
// value_t::null.
|
||||
static void take(data& dst, data& src) noexcept
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_type = src.m_type;
|
||||
dst.m_value = src.m_value;
|
||||
src.m_type = value_t::null;
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -753,21 +748,21 @@ private:
|
||||
// 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
|
||||
// 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)
|
||||
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 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 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
|
||||
// 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);
|
||||
return walk_child_it(*v.m_value.object)->second.m_data;
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// 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
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// 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
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::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
|
||||
// free the old recursive implementation used, just factored out so
|
||||
// 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));
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
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:
|
||||
@@ -878,16 +873,14 @@ public:
|
||||
|
||||
// This json_value is not itself a basic_json, so the top-level
|
||||
// container is first moved into a local stand-in ("cur"); a
|
||||
// default-constructed data has a null 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. The stand-ins are data, not
|
||||
// basic_json, so that destroying a value never constructs a
|
||||
// CustomBaseClass, whose constructor may allocate (#5995).
|
||||
data cur;
|
||||
cur.m_type = t;
|
||||
// default-constructed basic_json has a null 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.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_value, *this);
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
destroy_bounded(cur, 0);
|
||||
}
|
||||
@@ -896,7 +889,7 @@ public:
|
||||
// The first recursion_depth_limit() levels are destroyed by plain
|
||||
// recursion, which is the fastest way and cannot throw; anything
|
||||
// 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()))
|
||||
{
|
||||
@@ -904,23 +897,23 @@ public:
|
||||
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)
|
||||
{
|
||||
destroy_bounded(child.m_data, depth + 1);
|
||||
destroy_bounded(child, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
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
|
||||
// than one level deep.
|
||||
//
|
||||
// Like destroy_container(), the walk keeps the containers it
|
||||
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
||||
// construct a CustomBaseClass.
|
||||
static void destroy_walk(data& top) noexcept
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// 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);
|
||||
|
||||
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
|
||||
// looked at yet. If none of them has children of its own, cur is
|
||||
@@ -969,7 +965,7 @@ public:
|
||||
fresh = false;
|
||||
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);
|
||||
return; // back at the top with nothing left to do
|
||||
@@ -978,7 +974,7 @@ public:
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
data gp;
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
@@ -989,7 +985,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
data& cur_child_ref = walk_child(cur);
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
@@ -1001,7 +997,7 @@ public:
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
data tmp;
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
|
||||
Generated
+86
-86
@@ -33473,7 +33473,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
|
||||
#include <algorithm> // remove, fill, find, none_of, min
|
||||
#include <algorithm> // copy, none_of, min, search
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // isfinite
|
||||
@@ -37342,31 +37342,35 @@ class serializer
|
||||
// check if the buffer was large enough
|
||||
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// look up the locale's thousands separator and decimal point now,
|
||||
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
||||
// look up the locale's decimal point now, matching what
|
||||
// snprintf_float() just used (see get_decimal_point() in
|
||||
// number_parse.hpp); it is a string, which can be longer than one
|
||||
// byte (U+066B in ps_AF, #5993).
|
||||
// A thousands separator needs no handling: %g never groups digits.
|
||||
const auto* loc = std::localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
||||
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
||||
|
||||
// erase thousands separators
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
const char* const decimal_point = (loc->decimal_point == nullptr) ? "" : loc->decimal_point;
|
||||
const std::size_t decimal_point_len = std::char_traits<char>::length(decimal_point);
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' && decimal_point != '.')
|
||||
if (decimal_point_len != 0 && !(decimal_point_len == 1 && decimal_point[0] == '.'))
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
const auto first = number_buffer.begin();
|
||||
const auto last = first + len;
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::search returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::search(first, last, decimal_point, decimal_point + decimal_point_len);
|
||||
if (dec_pos != last)
|
||||
{
|
||||
*dec_pos = '.';
|
||||
if (decimal_point_len > 1)
|
||||
{
|
||||
// move the fraction and exponent behind the '.' (std::copy
|
||||
// allows the overlap, as the destination is before the source)
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::copy(dec_pos + static_cast<std::ptrdiff_t>(decimal_point_len), last, dec_pos + 1);
|
||||
*end = '\0';
|
||||
len = end - first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38373,10 +38377,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
////////////////////////
|
||||
|
||||
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
|
||||
|
||||
@@ -38553,28 +38553,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of the type and value from src to
|
||||
// dst: no set_parents()/assert_invariant() (the former is
|
||||
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
||||
// walk below quadratic); dst takes ownership, src is left as
|
||||
// value_t::null.
|
||||
static void take(data& dst, data& src) noexcept
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_type = src.m_type;
|
||||
dst.m_value = src.m_value;
|
||||
src.m_type = value_t::null;
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -38583,21 +38582,21 @@ private:
|
||||
// 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
|
||||
// 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)
|
||||
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 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 has_no_children(el.second.m_data);
|
||||
return has_no_children(el.second);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38629,30 +38628,30 @@ private:
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// 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);
|
||||
return walk_child_it(*v.m_value.object)->second.m_data;
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// 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
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// 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
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38661,23 +38660,23 @@ private:
|
||||
// recurses at most one level deep); this is the same allocator-based
|
||||
// free the old recursive implementation used, just factored out so
|
||||
// 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));
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
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:
|
||||
@@ -38708,16 +38707,14 @@ public:
|
||||
|
||||
// This json_value is not itself a basic_json, so the top-level
|
||||
// container is first moved into a local stand-in ("cur"); a
|
||||
// default-constructed data has a null 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. The stand-ins are data, not
|
||||
// basic_json, so that destroying a value never constructs a
|
||||
// CustomBaseClass, whose constructor may allocate (#5995).
|
||||
data cur;
|
||||
cur.m_type = t;
|
||||
// default-constructed basic_json has a null 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.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_value, *this);
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
destroy_bounded(cur, 0);
|
||||
}
|
||||
@@ -38726,7 +38723,7 @@ public:
|
||||
// The first recursion_depth_limit() levels are destroyed by plain
|
||||
// recursion, which is the fastest way and cannot throw; anything
|
||||
// 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()))
|
||||
{
|
||||
@@ -38734,23 +38731,23 @@ public:
|
||||
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)
|
||||
{
|
||||
destroy_bounded(child.m_data, depth + 1);
|
||||
destroy_bounded(child, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
destroy_bounded(child.second.m_data, depth + 1);
|
||||
destroy_bounded(child.second, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38777,15 +38774,18 @@ public:
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// Like destroy_container(), the walk keeps the containers it
|
||||
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
||||
// construct a CustomBaseClass.
|
||||
static void destroy_walk(data& top) noexcept
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// 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);
|
||||
|
||||
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
|
||||
// looked at yet. If none of them has children of its own, cur is
|
||||
@@ -38799,7 +38799,7 @@ public:
|
||||
fresh = false;
|
||||
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);
|
||||
return; // back at the top with nothing left to do
|
||||
@@ -38808,7 +38808,7 @@ public:
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
data gp;
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
@@ -38819,7 +38819,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
data& cur_child_ref = walk_child(cur);
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
@@ -38831,7 +38831,7 @@ public:
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
data tmp;
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -496,92 +495,3 @@ TEST_CASE("Regression test for issue #5674 - copy construction must not require
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -397,6 +397,14 @@ TEST_CASE("locale with a multi-byte decimal point")
|
||||
|
||||
// a value the locale-independent paths convert is not affected
|
||||
CHECK(json::parse("12.5") == 12.5);
|
||||
|
||||
// dump() of a long double that takes the snprintf path replaces the
|
||||
// whole decimal point, not only its first byte (#5993)
|
||||
CHECK(long_double_json(1.5L).dump() == "1.5");
|
||||
CHECK(long_double_json(2.0L).dump() == "2.0");
|
||||
CHECK(long_double_json(-0.25L).dump() == "-0.25");
|
||||
CHECK(long_double_json::parse(long_double_json(1.5e-7L).dump()) == long_double_json(1.5e-7L));
|
||||
CHECK(long_double_json::parse(ld.dump()) == expected_long_double);
|
||||
}
|
||||
if (!tested)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user