Merge remote-tracking branch 'origin/develop' into claude/fix-issue-3989-db7e45

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-07 07:42:58 +02:00
commit a2766df787
58 files changed
+3400 -2866

No files matched your search

+43 -96
View File
@@ -13,7 +13,6 @@
#include <optional> // optional
#endif
#include <algorithm> // copy
#include <iterator> // begin, end
#include <memory> // allocator_traits
#include <string> // basic_string, char_traits
@@ -39,10 +38,14 @@ namespace detail
//////////////////
/*
* Note all external_constructor<>::construct functions need to call
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
* allocated value (e.g., a string). See bug issue
* Note all external_constructor<>::construct functions need to store the new
* value with j.replace_value(), which destroys the old one to avoid a memory
* leak in case j contains an allocated value (e.g., a string). See bug issue
* https://github.com/nlohmann/json/issues/2865 for more information.
*
* A value that has to be allocated is created before the old one is destroyed:
* were it the other way around, an exception while creating the new value would
* leave j with the type of the new value, but the pointer to the destroyed old one.
*/
template<value_t> struct external_constructor;
@@ -53,10 +56,7 @@ struct external_constructor<value_t::boolean>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::boolean;
j.m_data.m_value = b;
j.assert_invariant();
j.replace_value(value_t::boolean, b);
}
};
@@ -66,19 +66,15 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = s;
j.assert_invariant();
const typename BasicJsonType::json_value value(s);
j.replace_value(value_t::string, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value = std::move(s);
j.assert_invariant();
const typename BasicJsonType::json_value value(std::move(s));
j.replace_value(value_t::string, value);
}
template < typename BasicJsonType, typename CompatibleStringType,
@@ -86,10 +82,8 @@ struct external_constructor<value_t::string>
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::string;
j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
j.assert_invariant();
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::string_t>(str));
j.replace_value(value_t::string, value);
}
};
@@ -99,19 +93,15 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(b);
j.assert_invariant();
const typename BasicJsonType::json_value value(b);
j.replace_value(value_t::binary, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::binary;
j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
j.assert_invariant();
const typename BasicJsonType::json_value value(std::move(b));
j.replace_value(value_t::binary, value);
}
};
@@ -121,10 +111,7 @@ struct external_constructor<value_t::number_float>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::number_float;
j.m_data.m_value = val;
j.assert_invariant();
j.replace_value(value_t::number_float, val);
}
};
@@ -134,10 +121,7 @@ struct external_constructor<value_t::number_unsigned>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::number_unsigned;
j.m_data.m_value = val;
j.assert_invariant();
j.replace_value(value_t::number_unsigned, val);
}
};
@@ -147,10 +131,7 @@ struct external_constructor<value_t::number_integer>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::number_integer;
j.m_data.m_value = val;
j.assert_invariant();
j.replace_value(value_t::number_integer, val);
}
};
@@ -160,21 +141,15 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = arr;
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(arr);
j.replace_value(value_t::array, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = std::move(arr);
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(std::move(arr));
j.replace_value(value_t::array, value);
}
template < typename BasicJsonType, typename CompatibleArrayType,
@@ -188,39 +163,23 @@ struct external_constructor<value_t::array>
using std::begin;
using std::end;
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)));
j.replace_value(value_t::array, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->reserve(arr.size());
for (const bool x : arr)
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
}
j.assert_invariant();
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(arr.begin(), arr.end()));
j.replace_value(value_t::array, value);
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
j.m_data.m_value.array->resize(arr.size());
std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::begin(arr), std::end(arr)));
j.replace_value(value_t::array, value);
}
#if JSON_HAS_RANGE_VIEW_CONVERSION
@@ -228,18 +187,15 @@ struct external_constructor<value_t::array>
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
// no range constructor: a view's begin() and end() may have different
// types, and the view may only be iterable once
typename BasicJsonType::array_t elements;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
elements.push_back(x);
}
// set the parents only once all elements are in place: a push_back
// that reallocates moves the earlier elements, which does not keep
// their parent pointers
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(std::move(elements));
j.replace_value(value_t::array, value);
}
#endif
};
@@ -250,21 +206,15 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = obj;
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(obj);
j.replace_value(value_t::object, value);
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value = std::move(obj);
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(std::move(obj));
j.replace_value(value_t::object, value);
}
template < typename BasicJsonType, typename CompatibleObjectType,
@@ -274,11 +224,8 @@ struct external_constructor<value_t::object>
using std::begin;
using std::end;
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::object;
j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.set_parents();
j.assert_invariant();
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)));
j.replace_value(value_t::object, value);
}
};
+64 -43
View File
@@ -1270,6 +1270,20 @@ class binary_reader
}
}
/*!
@brief reports a nested indefinite-length CBOR string or byte array
@param[in] type_name name of the rejected string type
@param[in] context parsing context for the error message
@return false, so that the caller stops reading
*/
bool cbor_indefinite_string_error(const char* type_name, const char* context)
{
auto last_token = get_token_string();
return report_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("indefinite-length ", type_name,
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
}
/*!
@brief reads a definite-length CBOR string
@@ -1279,12 +1293,13 @@ class binary_reader
into the same string.
@param[out] result string the bytes are appended to
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
@return whether string creation completed
@pre @a current is not EOF
*/
bool get_cbor_string_chunk(string_t& result)
bool get_cbor_string_chunk(string_t& result, const bool inside_indefinite)
{
switch (current)
{
@@ -1345,7 +1360,7 @@ class binary_reader
{
auto last_token = get_token_string();
return report_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr));
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
}
}
}
@@ -1363,13 +1378,9 @@ class binary_reader
*/
bool get_cbor_string(string_t& result, const char* context = "string")
{
// number of indefinite-length strings that have been opened and not
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
// but this reader has always accepted it, so the open levels are
// counted instead of recursed through, which overflowed the stack for
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
// to the same result, so no per-level state is needed.
std::size_t open = 0;
// read chunks iteratively, but reject a second indefinite-length
// level as required by RFC 8949, Section 3.2.3
bool indefinite = false;
while (true)
{
@@ -1380,29 +1391,28 @@ class binary_reader
if (current == 0x7F) // UTF-8 string (indefinite length)
{
++open;
get();
continue;
}
// a break marker closes the innermost indefinite-length string;
// outside of one it is not a string and falls through to the error
if (open != 0 && current == 0xFF)
{
if (--open == 0)
if (JSON_HEDLEY_UNLIKELY(indefinite))
{
return check_string_utf8(result, context);
return cbor_indefinite_string_error("string", "string");
}
indefinite = true;
get();
continue;
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
// a break marker closes the indefinite-length string; outside
// of one it falls through to the error below
if (indefinite && current == 0xFF)
{
return check_string_utf8(result, context);
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
{
return false;
}
if (open == 0)
if (!indefinite)
{
return check_string_utf8(result, context);
}
@@ -1498,12 +1508,13 @@ class binary_reader
read into the same byte array.
@param[out] result byte array the bytes are appended to
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
@return whether byte array creation completed
@pre @a current is not EOF
*/
bool get_cbor_binary_chunk(binary_t& result)
bool get_cbor_binary_chunk(binary_t& result, const bool inside_indefinite)
{
switch (current)
{
@@ -1568,7 +1579,7 @@ class binary_reader
{
auto last_token = get_token_string();
return report_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr));
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
}
}
}
@@ -1586,9 +1597,9 @@ class binary_reader
*/
bool get_cbor_binary(binary_t& result)
{
// the open indefinite-length byte arrays are counted rather than
// recursed through, for the reason given in @ref get_cbor_string
std::size_t open = 0;
// read chunks iteratively, but reject a second indefinite-length
// level as required by RFC 8949, Section 3.2.3
bool indefinite = false;
while (true)
{
@@ -1599,29 +1610,28 @@ class binary_reader
if (current == 0x5F) // Binary data (indefinite length)
{
++open;
get();
continue;
}
// a break marker closes the innermost indefinite-length byte
// array; outside of one it falls through to the error below
if (open != 0 && current == 0xFF)
{
if (--open == 0)
if (JSON_HEDLEY_UNLIKELY(indefinite))
{
return true;
return cbor_indefinite_string_error("binary array", "binary");
}
indefinite = true;
get();
continue;
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
// a break marker closes the indefinite-length string; outside
// of one it falls through to the error below
if (indefinite && current == 0xFF)
{
return true;
}
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result, indefinite)))
{
return false;
}
if (open == 0)
if (!indefinite)
{
return true;
}
@@ -3737,6 +3747,15 @@ class binary_reader
{
return false;
}
// the lexer would stop at a NUL and accept the digits before it
if (JSON_HEDLEY_UNLIKELY(current == '\0'))
{
static_cast<void>(report_error(chars_read, "00", parse_error::create(115, chars_read,
exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)));
// when recovering, the number ends at the NUL, as it would in
// JSON text, and the rest of its bytes are skipped
return recover_high_precision_number(number_vector, size - i - 1);
}
number_vector.push_back(static_cast<char>(current));
}
@@ -3812,12 +3831,14 @@ class binary_reader
parser for JSON text, or passes null if there is none.
@param[in] number_vector the number's text
@param[in] remaining the number of bytes of the number that are not
read yet; they are skipped
@return whether the SAX parser asked to recover and accepted the value
*/
template<bool Recover = AllowRecovery, enable_if_t<Recover, int> = 0>
bool recover_high_precision_number(const std::vector<char>& number_vector)
bool recover_high_precision_number(const std::vector<char>& number_vector, const std::size_t remaining = 0)
{
if (!repair_requested())
if (!repair_requested() || !skip_bytes(remaining, "number"))
{
return false;
}
@@ -3861,7 +3882,7 @@ class binary_reader
/// @copydoc recover_high_precision_number
template < bool Recover = AllowRecovery, enable_if_t < !Recover, int > = 0 >
constexpr std::false_type recover_high_precision_number(const std::vector<char>& /*number_vector*/) const noexcept
constexpr std::false_type recover_high_precision_number(const std::vector<char>& /*number_vector*/, const std::size_t /*remaining*/ = 0) const noexcept
{
return {};
}
+9
View File
@@ -34,6 +34,7 @@ class json_ref
json_ref(std::initializer_list<json_ref> init)
: owned_value(init)
, braced_list(true)
{}
template <
@@ -69,9 +70,17 @@ class json_ref
return &** this;
}
/// whether the value was written as a braced list, such as {"key", 1},
/// rather than given as a value
bool is_braced_list() const noexcept
{
return braced_list;
}
private:
mutable value_type owned_value = nullptr;
value_type const* value_ref = nullptr;
bool braced_list = false;
};
} // namespace detail