Merge fix/ubjson-valueless-budget into fix/reserve-unbacked-count, wrap docs, skip ICC-incompatible tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-11-13 07:35:11 +01:00
commit 619fafc087
96 files changed
+2880 -1464

No files matched your search

+207 -31
View File
@@ -2597,6 +2597,12 @@ JSON_HEDLEY_DIAGNOSTIC_POP
#define JSON_NO_THREAD_LOCAL 1
#endif
// JSON_RECURSION_DEPTH_LIMIT (the number of nesting levels the walks over a
// value recurse into before they continue with an explicit stack; 128 by
// default, 0 to 254 allowed) is defined and checked where it is used, in
// detail/recursion_depth_limit.hpp. Like JSON_NO_THREAD_LOCAL it is neither part
// of the ABI tag nor undefined at the end of json.hpp.
// allow disabling exceptions
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
#define JSON_THROW(exception) throw exception
@@ -6939,6 +6945,37 @@ void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyE
from_json_pair_array_to_map(j, m);
}
#if defined(__cpp_lib_char8_t)
// a std::u8string is read from a JSON string; the array of code units written by
// versions before 3.13.0 is still accepted
template<typename BasicJsonType, typename Tr, typename Allocator,
enable_if_t<std::is_same<typename json_type_of_t<BasicJsonType>::string_t::value_type, char>::value, int> = 0>
inline void from_json(const BasicJsonType& j, std::basic_string<char8_t, Tr, Allocator>& s)
{
if (j.is_string())
{
const auto& str = string_value(j);
const char* const data = str.data();
const std::size_t size = str.size();
std::basic_string<char8_t, Tr, Allocator> ret(s.get_allocator());
ret.resize(size);
for (std::size_t i = 0; i < size; ++i)
{
ret[i] = static_cast<char8_t>(static_cast<unsigned char>(data[i]));
}
s = std::move(ret);
}
else if (j.is_array())
{
from_json_array_impl(j, s, priority_tag<3> {});
}
else
{
throw_type_must_be("string or array", j);
}
}
#endif
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
// Workaround for MSVC 19.51 (and possibly later): in large cpp files, the compiler may fail to resolve with generic has_from_json (issue #4996)
@@ -8340,16 +8377,20 @@ inline void to_json(BasicJsonType& j, const T& t)
to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {});
}
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
// a std::u8string is a JSON string (it was an array of code units before 3.13.0);
// defined outside the std::filesystem guard so that the result does not depend
// on whether std::filesystem::path is supported
#if defined(__cpp_lib_char8_t)
template<typename BasicJsonType, typename Tr, typename Allocator>
inline void to_json(BasicJsonType& j, const std::basic_string<char8_t, Tr, Allocator>& s)
{
using OtherAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<char>;
j = std::basic_string<char, std::char_traits<char>, OtherAllocator>(s.begin(), s.end(), s.get_allocator());
using string_t = typename BasicJsonType::string_t;
// char may alias any object; the code units are copied unchanged
j = string_t(reinterpret_cast<const char*>(s.data()), s.size());
}
#endif
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
// Workaround for MSVC 19.51 (and possibly later): in large cpp files, the compiler may fail to resolve with generic has_to_json (issue #4996)
template<typename BasicJsonType>
struct has_to_json<BasicJsonType, std_fs::path, void> : std::true_type {};
@@ -8611,10 +8652,23 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t
#include <type_traits> // false_type, is_integral, remove_cv, true_type
// #include <nlohmann/detail/abi_macros.hpp>
// JSON_RECURSION_DEPTH_LIMIT: the number of nesting levels the operations that
// walk a value recurse into on the call stack before they continue with an
// explicit stack (see detail::recursion_depth_limit() below).
//
// May be defined by the user before the first include of the library; it has
// to be the same in every translation unit of a program (it is not part of the
// ABI tag, see https://json.nlohmann.me/api/macros/json_recursion_depth_limit/).
// Allowed: an integer constant expression from 0 to 254. The default is 128.
#ifndef JSON_RECURSION_DEPTH_LIMIT
#define JSON_RECURSION_DEPTH_LIMIT 128
#endif
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
@@ -8623,21 +8677,71 @@ namespace detail
@brief the number of nesting levels an operation recurses into
Operations that walk a value (copying, comparing, serializing, hashing, merging,
...) recurse once
per nesting level, which is fastest, but a value nested deeply enough would
exhaust the call stack. So they recurse only this many levels deep and finish
whatever lies below with an explicit stack. All of them share this limit.
diffing, converting, destroying, and writing the binary formats CBOR,
MessagePack, UBJSON, BJData, and BON8) recurse once per nesting level, which is
fastest, but a value nested deeply enough would exhaust the call stack. So they
recurse only this many levels deep and finish whatever lies below with an
explicit stack that lives on the heap. All of them share this limit and none has
a constant of its own. It is the value of the macro JSON_RECURSION_DEPTH_LIMIT,
128 by default. (Parsing, the binary readers, flatten(), unflatten(), and
to_bson() always use an explicit stack and do not read it, and the lockstep
comparison of json_view documents has a fixed bound of its own, 48 levels.)
Most of them pass the depth down as an argument. The copy constructor and the
comparison operators cannot, as their signatures are fixed, so they count it
in basic_json::nesting_depth() instead, a byte per thread; the limit must
therefore stay below 255.
Most of the operations pass the depth down as an argument. The copy constructor
and the comparison operators cannot, as their signatures are fixed, so they
count it in basic_json::nesting_depth() instead, a byte per thread. Only the
levels that are descended into are counted, so the count never exceeds the limit.
The limit must fit in the byte with some headroom, so that the count cannot wrap
around: it must be below 255, and 254 is the largest value allowed. (More levels
would hardly be useful: 254 levels need up to 576 KiB of stack in an unoptimized
build already, twice the stack of the default.)
The smallest value is 0: every operation then uses its explicit stack from the
first level on and no operation recurses over the nesting of a value at all.
That is slower, but needs a constant amount of call stack. It works because
every use of the limit compares a depth with it (`depth >= limit`, or `<` for
the opposite), which holds for 0 as for any other value; nothing computes
`limit - 1` or `limit + 1`, so nothing wraps around. Each operation's explicit
stack handles any nesting level, including the first, so none of them relies on
having recursed first.
The call stack that an operation needs is roughly a few KiB plus a cost per level
times the limit. At the default of 128 levels, an unoptimized build needs up to
about 290 KiB (`dump()` with Clang at -O0: about 2.3 KiB per level) and an
optimized build up to about 48 KiB; with 254 levels it is up to 576 KiB and
112 KiB, and with 0 it is 16 KiB or less. Lower the limit for threads with small
stacks (RTOS tasks, fibers, musl threads), raise it to trade stack for speed on
deeply nested values. All of this, with the measurements, is in the
documentation of the macro.
@sa https://json.nlohmann.me/api/macros/json_recursion_depth_limit/
@sa https://github.com/nlohmann/json/issues/5387
*/
/// the type of the value of JSON_RECURSION_DEPTH_LIMIT is bool or a character type
template<typename T> struct recursion_depth_limit_is_char_or_bool : std::false_type {};
template<> struct recursion_depth_limit_is_char_or_bool<bool> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<char> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<signed char> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<unsigned char> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<wchar_t> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<char16_t> : std::true_type {};
template<> struct recursion_depth_limit_is_char_or_bool<char32_t> : std::true_type {};
#if defined(__cpp_char8_t)
template<> struct recursion_depth_limit_is_char_or_bool<char8_t> : std::true_type {};
#endif
constexpr std::size_t recursion_depth_limit() noexcept
{
return 128;
// an integer: not a floating-point number, whose range check would pass for 1.5 or 254.0
// (bool and the character types are integer types, too, but true, false or 'a' are
// no way to say a limit; JSON_UBJSON_VALUELESS_ELEMENTS_PER_BYTE refuses them as well)
static_assert(std::is_integral<decltype(JSON_RECURSION_DEPTH_LIMIT)>::value
&& !recursion_depth_limit_is_char_or_bool<typename std::remove_cv<decltype(JSON_RECURSION_DEPTH_LIMIT)>::type>::value,
"JSON_RECURSION_DEPTH_LIMIT must be an integer constant (not bool or a character type)");
static_assert(JSON_RECURSION_DEPTH_LIMIT >= 0 && JSON_RECURSION_DEPTH_LIMIT <= 254,
"JSON_RECURSION_DEPTH_LIMIT must be between 0 and 254: the nesting depth is counted in a byte per thread "
"and may exceed the limit by one (see https://json.nlohmann.me/api/macros/json_recursion_depth_limit/)");
return static_cast<std::size_t>(JSON_RECURSION_DEPTH_LIMIT);
}
} // namespace detail
@@ -10433,15 +10537,23 @@ struct can_compare_ne_either_order : std::integral_constant < bool,
can_compare_ne_impl<IteratorType, SentinelType>::value ||
can_compare_ne_reversed<IteratorType, SentinelType>::value > {};
// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a
// trailing default argument (e.g. parse(s, nullptr, ...)) must never be
// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably
// SFINAE the `operator!=` detection above for std::nullptr_t against
// container/string types, which would otherwise make such calls ambiguous
// with the compatible-input overload.
// Two kinds of SentinelType are excluded explicitly:
//
// - std::nullptr_t: a literal `nullptr` passed as a trailing default argument
// (e.g. parse(s, nullptr, ...)) must never be mistaken for a sentinel, and
// some compilers (e.g. GCC 4.8) unreliably SFINAE the `operator!=` detection
// above for std::nullptr_t against container/string types, which would
// otherwise make such calls ambiguous with the compatible-input overload.
// - function pointers and functions: a parser callback passed as plain function
// (e.g. parse(s, cb)) is never a sentinel. The callback's parameter types
// make basic_json an associated class, so ADL finds the friend
// operator!=(const_reference, ScalarType) of basic_json, which makes
// `s != cb` well-formed (both convert to basic_json) and would select the
// iterator/sentinel overload.
template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool,
!std::is_same<SentinelType, std::nullptr_t>::value &&
!std::is_function<typename std::remove_pointer<SentinelType>::type>::value &&
can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
template<typename T>
@@ -27629,6 +27741,32 @@ class json_pointer
}
};
/*!
@brief the number of the deepest step of the previous path that is a prefix of the new one
Comparing every one of many steps with the beginning of the path would take
quadratic time. Instead, the length of the common beginning of the two paths
is computed once: a step whose prefix is not longer is a prefix of the new
path, too. For few steps, which is the usual case, @ref resolve_plain
compares them, which is cheaper.
*/
template<typename Steps>
JSON_HEDLEY_NEVER_INLINE static std::size_t deepest_shared_step(const string_t& path, const std::size_t length, const string_t& previous, const Steps& steps) noexcept
{
const std::size_t same = common_prefix_length(path.data(), previous.data(), (std::min)(length, previous.size()));
std::size_t depth = steps.size();
for (; depth > 1; --depth)
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= same && (prefix == length || path[prefix] == '/'))
{
break;
}
}
return depth;
}
/*!
@brief resolve a plain JSON pointer without building its reference tokens
@@ -27657,13 +27795,20 @@ class json_pointer
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
{
const string_t& previous = *cache.path;
for (depth = steps.size(); depth > 1; --depth)
if (JSON_HEDLEY_UNLIKELY(steps.size() > 16))
{
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
depth = deepest_shared_step(path, length, previous, steps);
}
else
{
for (depth = steps.size(); depth > 1; --depth)
{
break;
// a step ends at a token boundary of the previous path
const std::size_t prefix = steps[depth - 1].first;
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
{
break;
}
}
}
steps.resize(depth);
@@ -30874,6 +31019,14 @@ class binary_writer
write_container_iterative<msgpack_walk>(root);
}
// a function, not an inline condition: for an object the type is never
// valueless and MSVC would warn about a constant expression (C4127)
static constexpr bool valueless_array_fits(const bool valueless_type, const std::size_t size,
const std::size_t written) noexcept
{
return !valueless_type || size <= detail::valueless_elements_base - written;
}
/*!
@brief write the start of an array (@a IsArray) or object: its opening
marker, the optimized type, and the count
@@ -30922,7 +31075,7 @@ class binary_writer
if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
{
const bool valueless_type = IsArray && (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
if (!valueless_type || size <= detail::valueless_elements_base - valueless_elements_written)
if (valueless_array_fits(valueless_type, size, valueless_elements_written))
{
prefix_required = false;
oa.write_character(to_char_type('$'));
@@ -40782,8 +40935,10 @@ public:
}
#ifndef JSON_NO_THREAD_LOCAL
// nesting_depth() is a byte and may exceed the limit by one level
static_assert(detail::recursion_depth_limit() < 255, "the nesting depth count must fit in a byte");
// nesting_depth() is a byte that never exceeds the limit (JSON_RECURSION_DEPTH_LIMIT),
// which is at most 254: the largest value stays one below the byte's maximum, so
// that the count cannot wrap around to 0 and let a descent go on without a bound.
// recursion_depth_limit() checks this range with a static_assert.
/*!
@brief how many levels the operation going on in this thread has descended into
@@ -40794,8 +40949,15 @@ public:
user code nests them anyway, sharing the count only ends a descent sooner
than it had to, which costs a little speed and is never wrong.
A byte is enough: the count never exceeds the limit by more than the single
level that notices the limit has been reached.
A byte is enough: only the levels that are descended into are counted
(@ref nesting_depth_guard), so the count never exceeds the limit, and the
limit is at most 254.
The count is per thread and not per value or per library configuration: all
translation units of a program share it, even if they were compiled with
different values of JSON_RECURSION_DEPTH_LIMIT (which is not supported, see
the documentation of the macro); each of them then compares the shared count
with its own limit.
*/
static std::uint8_t& nesting_depth() noexcept
{
@@ -40853,14 +41015,25 @@ public:
#endif
{
#ifndef JSON_NO_THREAD_LOCAL
++nesting_depth();
// Only a level that is entered is counted, so that the count never
// exceeds the limit. A level that is not entered is finished without
// descending, and whatever runs while it is (a copy made by a custom
// base class, say) sees the same count as the level itself did and
// is not entered either.
if (m_okay)
{
++nesting_depth();
}
#endif
}
~nesting_depth_guard()
{
#ifndef JSON_NO_THREAD_LOCAL
--nesting_depth();
if (m_okay)
{
--nesting_depth();
}
#endif
}
@@ -44772,7 +44945,10 @@ public:
*/
void update_members(const iterator& first, const iterator& last, const bool merge_objects, const std::size_t depth)
{
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
// without merge_objects the loop below never descends, so there is no
// call stack to save (and the explicit stack always merges); the
// distinction matters for a limit of 0, which reaches this at depth 0
if (JSON_HEDLEY_UNLIKELY(merge_objects && depth >= detail::recursion_depth_limit()))
{
update_members_iteratively(first, last);
return;
+97 -5
View File
@@ -106,6 +106,7 @@
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t
#include <cstring> // memcmp, memcpy
#include <functional> // less
#include <limits> // numeric_limits
#include <map> // map
#include <memory> // unique_ptr
@@ -188,6 +189,11 @@
#define NLOHMANN_VIEW_THROW JSON_THROW_USER
#endif
// debug checks of preconditions (assert(), off with NDEBUG): JSON_ASSERT is
// one of the macros that json.hpp undefines at its end
#include <cassert> // assert
#define NLOHMANN_VIEW_ASSERT(x) assert(x)
// the parser stores a node's first word at once where the layout of `node` is
// known to be little-endian (MSVC targets are); elsewhere field by field
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER)
@@ -488,6 +494,24 @@ struct document_data
node_capacity = inline_capacity;
}
/// whether the n bytes at p overlap memory that the next parse releases or
/// reuses: the owned text, the decoded strings, an owned image, and the
/// current buffer of strings written by edits (earlier edit buffers are not
/// covered, as their sizes are not kept); for the debug check in read()
bool holds(const char* p, std::size_t n) const noexcept
{
const auto overlaps = [p, n](const char* b, std::size_t m) noexcept
{
// (std::less: a total order also for pointers into different objects; std::less<> needs C++14)
// NOLINTNEXTLINE(modernize-use-transparent-functors)
return n != 0 && m != 0 && std::less<const char*>()(p, b + m) && std::less<const char*>()(b, p + n);
};
return overlaps(owned.data(), owned.capacity())
|| overlaps(arena.data(), arena.capacity())
|| overlaps(reinterpret_cast<const char*>(owned_image.data()), owned_image.capacity()) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|| (edits && !edits->texts.empty() && overlaps(edits->texts.back().get(), edits->text_cap));
}
/// the largest node count whose size in bytes fits a std::size_t
static constexpr std::size_t max_nodes() noexcept
{
@@ -3239,6 +3263,14 @@ NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::
return true;
}
/// whether a float node holds the text nan, which an edit writes for a NaN (and records without a digit layout, so
/// that a parsed float is told apart by one comparison of what the walk has loaded anyway): equal tokens are an
/// equal value, except for this one
NLOHMANN_VIEW_ALWAYS_INLINE bool float_token_is_nan(const node& n, const char* s) noexcept
{
return NLOHMANN_VIEW_UNLIKELY(n.extra == 0xFFFFu) && n.len == 3 && s[0] == 'n';
}
/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first);
/// the first difference is found eight bytes at a time
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept
@@ -3848,7 +3880,7 @@ class lockstep
case value_t::string:
return a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len);
case value_t::number_float: // (the token, and the digit layout that the value is read with: they only differ in a damaged image)
return a->extra == b->extra && a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len);
return same_float(a, b, std::integral_constant<bool, EA> {});
case value_t::number_integer:
case value_t::number_unsigned:
return integer_bits(*a) == integer_bits(*b);
@@ -3943,6 +3975,17 @@ class lockstep
return true;
}
/// (a read-only document holds no NaN)
bool same_float(const node* a, const node* b, std::false_type /*editable*/) const noexcept
{
return a->extra == b->extra && a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len);
}
bool same_float(const node* a, const node* b, std::true_type /*editable*/) const noexcept
{
return a->extra == b->extra && a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len) && !float_token_is_nan(*a, m_a.str(*a));
}
bool elements(const node* a, const node* b, unsigned budget, lockstep_path::entry& stop) noexcept
{
const node* pa = nav_a::first(m_a, a);
@@ -3999,9 +4042,9 @@ bool certainly_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<B
{
return false;
}
if (na == nb)
if (na == nb && (!view_side<BasicJsonType, ViewA>::nav::editable() || a.document().edits == nullptr))
{
return true; // (the same node of the same document: documents have distinct nodes)
return true; // (the same node of the same document: documents have distinct nodes; a document with edits is walked, as an edit can have written a NaN, which is not equal to itself)
}
path.resume = nullptr;
if (path.next < path.length && path.nodes[path.next].a == na && path.nodes[path.next].b == nb)
@@ -4046,7 +4089,9 @@ void resume_array(const lockstep_path& /*path*/, const A& /*a*/, const B& /*b*/,
template<typename BasicJsonType, typename ViewA, typename ViewB>
bool floats_equal(const view_side<BasicJsonType, ViewA>& a, const view_side<BasicJsonType, ViewB>& b)
{
return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token()) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating());
// float_token_is_nan() reads at most the node length of the token, which is not null terminated
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token() && !(view_side<BasicJsonType, ViewA>::nav::editable() && float_token_is_nan(*a.value_node(), a.float_token().data()))) || std::equal_to<typename BasicJsonType::number_float_t>()(a.floating(), b.floating());
}
template<typename A, typename B>
@@ -7416,6 +7461,28 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu
return true;
}
/// whether an integer token (well-formed, no fraction or exponent) fits 64
/// bits: the parser stores a number_integer or number_unsigned node for it,
/// and a number_float node only for a token that does not fit
inline bool integer_token_fits(const number_token& t)
{
if (t.int_digits > 20)
{
return false;
}
if (t.int_digits == 20)
{
// (20 digits are compared with 2^64 - 1; a negative one is below -2^63)
// (only a leading 1 needs the comparison: the first digit of 20 is not 0)
return !t.negative && *t.int_start == '1' && std::memcmp(t.int_start, "18446744073709551615", 20) <= 0;
}
if (t.negative && t.int_digits == 19)
{
return *t.int_start != '9' || std::memcmp(t.int_start, "9223372036854775808", 19) <= 0;
}
return true;
}
/// whether an integer node matches its token the way the parser records it
/// (after the bounds check; the token has at most 256 characters)
inline bool check_integer(const node& n, const unsigned char* text)
@@ -7535,7 +7602,8 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
if (!plain_float_token(s, r.len, r.extra))
{
number_token t{};
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
// (an integer token is a float node only if it does not fit 64 bits)
if (!scan_number_token(s, r.len, t) || (!t.is_float && integer_token_fits(t)) || !float_token_finite(s, r.len, t))
{
return false;
}
@@ -11630,6 +11698,10 @@ class basic_json_document
/// sentinel: src[size] is readable and 0 (std::string, C strings)
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
{
// precondition of read(): a borrowed input is not held by this document,
// which releases what it holds below (checked in debug builds only, as
// it would cost every read() of a small message a few percent)
NLOHMANN_VIEW_ASSERT(owned || !m_data || !m_data->holds(src, size));
ensure_data(src, size);
document_data& d = *m_data;
if (!owned)
@@ -11719,6 +11791,10 @@ class basic_json_document
template<typename T>
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
{
// (the precondition of read() as for a borrowed input, although only an
// input in the owned text breaks a copy: the same call borrows, for
// example a std::string_view in C++17, and must not depend on that)
NLOHMANN_VIEW_ASSERT(!m_data || !m_data->holds(reinterpret_cast<const char*>(s.data()), static_cast<std::size_t>(s.size()))); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
build_copy(s, ae, c, tc);
}
@@ -11737,9 +11813,24 @@ class basic_json_document
template<typename T>
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
{
NLOHMANN_VIEW_ASSERT(!held(input)); // (see the copy_range overload)
build_copy(std::forward<T>(input), ae, c, tc);
}
/// whether an input read through the input adapter is held by this document:
/// string_view_t before C++17 (a string handed out by a view), for the
/// debug check of read(); other inputs of this kind do not point into it
bool held(const detail::view::string_ref& s) const noexcept
{
return m_data && m_data->holds(s.data(), s.size());
}
template<typename T>
bool held(const T& /*input*/) const noexcept
{
return false;
}
template<typename IteratorType>
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc)
{
@@ -11895,6 +11986,7 @@ class tuple_element<N, ::nlohmann::detail::view::view_item<View>> // NOLINT(cert
#undef NLOHMANN_VIEW_NOCLONE
#undef NLOHMANN_VIEW_NODISCARD
#undef NLOHMANN_VIEW_THROW
#undef NLOHMANN_VIEW_ASSERT
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
#undef NLOHMANN_VIEW_REPEAT16
#undef NLOHMANN_VIEW_REPEAT_SECOND_8