Merge branch 'develop' into claude/issue-3885-3dcafd

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-27 16:07:30 +02:00
16 changed files with 497 additions and 212 deletions
@@ -18,7 +18,7 @@ ignore
: ignore tags
store
: store tagged values as binary container with subtype (for bytes 0xd8..0xdb)
: store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored.
## Examples
@@ -34,6 +34,15 @@ The exact mapping and its limitations are described on a [dedicated page](../../
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
## Exceptions
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the MessagePack ext type; example:
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
## Complexity
Linear in the size of the JSON value `j`.
@@ -65,3 +74,4 @@ Linear in the size of the JSON value `j`.
## Version history
- Added in version 2.0.9.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
@@ -188,7 +188,7 @@ The library maps CBOR types to JSON value types as follows:
!!! warning "Tagged items"
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
??? example
@@ -65,6 +65,8 @@ specification:
- arrays with more than 4294967295 elements
- objects with more than 4294967295 elements
Serializing such a value throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412).
!!! info "NaN/infinity handling"
`NaN`, `Infinity`, and `-Infinity` are serialized as a MessagePack float 32 (type 0xCA, 5 bytes total),
+10 -4
View File
@@ -932,19 +932,25 @@ A JSON Patch `add` operation cannot be applied because the target location's par
### json.exception.out_of_range.412
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer, and MessagePack
stores the length of strings, binary values, arrays, and objects in at most an unsigned 32-bit integer. This exception
is thrown when a value is too large to be described by such a length field.
!!! failure "Example message"
!!! failure "Example messages"
```
BSON length 2147483661 exceeds maximum of 2147483647
```
```
MessagePack length 4294967296 exceeds maximum of 4294967295
```
!!! note
This exception was added in version 3.13.0. Before that, the length was silently truncated, and
This exception was added in version 3.13.0. Before that, the BSON length was silently truncated, and
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
[`from_bson`](../api/basic_json/from_bson.md) rejected.
[`from_bson`](../api/basic_json/from_bson.md) rejected; [`to_msgpack`](../api/basic_json/to_msgpack.md) wrote such
a value without any length, producing output that could not be read back.
### json.exception.out_of_range.413
@@ -44,7 +44,7 @@ enum class cbor_tag_handler_t
{
error, ///< throw a parse_error exception in case of a tag
ignore, ///< ignore tags
store ///< store tags as binary type
store ///< store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored
};
/*!
@@ -592,14 +592,18 @@ class binary_reader
input (true) or whether the last read character should
be considered instead (false)
@param[in] tag_handler how CBOR tags should be treated
@param[out] tag_pending whether a tag was parsed and its value follows
@param[out] item_read whether the tagged value's initial byte is already in current
@return whether a valid CBOR value was passed to the SAX parser
*/
bool parse_cbor_value(const bool get_char,
const cbor_tag_handler_t tag_handler,
bool& tag_pending)
bool& tag_pending,
bool& item_read)
{
tag_pending = false;
item_read = false;
switch (get_char ? get() : current)
{
@@ -1021,7 +1025,17 @@ class binary_reader
}
}
get();
return get_cbor_binary(b) && sax->binary(b);
// a byte string (the heads accepted by get_cbor_binary) keeps the tag as subtype
if ((current >= 0x40 && current <= 0x5B) || current == 0x5F)
{
return get_cbor_binary(b) && sax->binary(b);
}
// not a byte string: the tagged value, whose first byte
// was just read, is read by the caller like for ignore
tag_pending = true;
item_read = true;
return true;
}
default: // LCOV_EXCL_LINE
@@ -1503,13 +1517,14 @@ class binary_reader
// a tag is not a value of its own: read on until the tagged value
bool tag_pending = false;
bool item_read = false;
do
{
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending, item_read)))
{
return false;
}
fetch = true;
fetch = !item_read;
}
while (tag_pending);
@@ -291,6 +291,23 @@ class binary_writer
}
}
/*!
@brief check that @a length fits into the 32 bits that MessagePack stores
the length of a string, binary value, array, or object in
@return the length as an unsigned 32-bit integer
@throw out_of_range.412 if @a length exceeds the range of std::uint32_t
*/
static std::uint32_t to_msgpack_length(const std::size_t length, const BasicJsonType& j)
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
{
JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
static_cast<void>(j);
return static_cast<std::uint32_t>(length);
}
/*!
@param[in] j JSON value to serialize
*/
@@ -430,7 +447,7 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
const auto N = j.m_data.m_value.string->size();
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
if (N <= 31)
{
// fixstr
@@ -448,17 +465,12 @@ class binary_writer
oa.write_character(to_char_type(0xDA));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// str 32
oa.write_character(to_char_type(0xDB));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write the string
oa.write_characters(
@@ -470,7 +482,7 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = j.m_data.m_value.array->size();
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
if (N <= 15)
{
// fixarray
@@ -482,17 +494,12 @@ class binary_writer
oa.write_character(to_char_type(0xDC));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// array 32
oa.write_character(to_char_type(0xDD));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write each element
for (const auto& el : *j.m_data.m_value.array)
@@ -509,7 +516,7 @@ class binary_writer
const bool use_ext = j.m_data.m_value.binary->has_subtype();
// step 1: write control byte and the byte string length
const auto N = j.m_data.m_value.binary->size();
const auto N = to_msgpack_length(j.m_data.m_value.binary->size(), j);
if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
std::uint8_t output_type{};
@@ -561,7 +568,7 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
const std::uint8_t output_type = use_ext
? 0xC9 // ext 32
@@ -570,11 +577,6 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 1.5: if this is an ext type, write the subtype
if (use_ext)
@@ -598,7 +600,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = j.m_data.m_value.object->size();
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
if (N <= 15)
{
// fixmap
@@ -610,17 +612,12 @@ class binary_writer
oa.write_character(to_char_type(0xDE));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// map 32
oa.write_character(to_char_type(0xDF));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
+20 -5
View File
@@ -1492,13 +1492,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
compare_keys(current.lhs_object_it->first, current.rhs_object_it->first,
std::integral_constant<bool, Ordered> {});
if (key_result != compare_result::equal)
{
return key_result;
}
left = &(current.lhs_object_it->second);
right = &(current.rhs_object_it->second);
if (key_result != compare_result::equal)
{
// An object type without a fixed order of its entries -
// std::unordered_map, say - may enumerate two equal
// objects differently, and its operator== does not care.
// Equality then finds the entry by its key; an ordering,
// or an object type that compares its entries in
// sequence (ordered_map), is decided by the key itself.
const auto* rhs_object = current.rhs_value->m_data.m_value.object;
const auto found = (!Ordered && !detail::is_ordered_map<object_t>::value)
? rhs_object->find(current.lhs_object_it->first)
: rhs_object->cend();
if (found == rhs_object->cend())
{
return key_result;
}
right = &(found->second);
}
++current.lhs_object_it;
++current.rhs_object_it;
}
+65 -38
View File
@@ -12741,7 +12741,7 @@ enum class cbor_tag_handler_t
{
error, ///< throw a parse_error exception in case of a tag
ignore, ///< ignore tags
store ///< store tags as binary type
store ///< store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored
};
/*!
@@ -13289,14 +13289,18 @@ class binary_reader
input (true) or whether the last read character should
be considered instead (false)
@param[in] tag_handler how CBOR tags should be treated
@param[out] tag_pending whether a tag was parsed and its value follows
@param[out] item_read whether the tagged value's initial byte is already in current
@return whether a valid CBOR value was passed to the SAX parser
*/
bool parse_cbor_value(const bool get_char,
const cbor_tag_handler_t tag_handler,
bool& tag_pending)
bool& tag_pending,
bool& item_read)
{
tag_pending = false;
item_read = false;
switch (get_char ? get() : current)
{
@@ -13718,7 +13722,17 @@ class binary_reader
}
}
get();
return get_cbor_binary(b) && sax->binary(b);
// a byte string (the heads accepted by get_cbor_binary) keeps the tag as subtype
if ((current >= 0x40 && current <= 0x5B) || current == 0x5F)
{
return get_cbor_binary(b) && sax->binary(b);
}
// not a byte string: the tagged value, whose first byte
// was just read, is read by the caller like for ignore
tag_pending = true;
item_read = true;
return true;
}
default: // LCOV_EXCL_LINE
@@ -14200,13 +14214,14 @@ class binary_reader
// a tag is not a value of its own: read on until the tagged value
bool tag_pending = false;
bool item_read = false;
do
{
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending, item_read)))
{
return false;
}
fetch = true;
fetch = !item_read;
}
while (tag_pending);
@@ -19756,6 +19771,23 @@ class binary_writer
}
}
/*!
@brief check that @a length fits into the 32 bits that MessagePack stores
the length of a string, binary value, array, or object in
@return the length as an unsigned 32-bit integer
@throw out_of_range.412 if @a length exceeds the range of std::uint32_t
*/
static std::uint32_t to_msgpack_length(const std::size_t length, const BasicJsonType& j)
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::uint32_t>(length)))
{
JSON_THROW(out_of_range::create(412, concat("MessagePack length ", std::to_string(length), " exceeds maximum of ", std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
static_cast<void>(j);
return static_cast<std::uint32_t>(length);
}
/*!
@param[in] j JSON value to serialize
*/
@@ -19895,7 +19927,7 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
const auto N = j.m_data.m_value.string->size();
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
if (N <= 31)
{
// fixstr
@@ -19913,17 +19945,12 @@ class binary_writer
oa.write_character(to_char_type(0xDA));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// str 32
oa.write_character(to_char_type(0xDB));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write the string
oa.write_characters(
@@ -19935,7 +19962,7 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = j.m_data.m_value.array->size();
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
if (N <= 15)
{
// fixarray
@@ -19947,17 +19974,12 @@ class binary_writer
oa.write_character(to_char_type(0xDC));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// array 32
oa.write_character(to_char_type(0xDD));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write each element
for (const auto& el : *j.m_data.m_value.array)
@@ -19974,7 +19996,7 @@ class binary_writer
const bool use_ext = j.m_data.m_value.binary->has_subtype();
// step 1: write control byte and the byte string length
const auto N = j.m_data.m_value.binary->size();
const auto N = to_msgpack_length(j.m_data.m_value.binary->size(), j);
if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
std::uint8_t output_type{};
@@ -20026,7 +20048,7 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
const std::uint8_t output_type = use_ext
? 0xC9 // ext 32
@@ -20035,11 +20057,6 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 1.5: if this is an ext type, write the subtype
if (use_ext)
@@ -20063,7 +20080,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = j.m_data.m_value.object->size();
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
if (N <= 15)
{
// fixmap
@@ -20075,17 +20092,12 @@ class binary_writer
oa.write_character(to_char_type(0xDE));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
else
{
// map 32
oa.write_character(to_char_type(0xDF));
write_number(static_cast<std::uint32_t>(N));
}
else
{
JSON_THROW(out_of_range::create(412, concat("MessagePack size ", std::to_string(N), " exceeds maximum of ",
std::to_string((std::numeric_limits<std::uint32_t>::max)())), &j));
}
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
@@ -26361,13 +26373,28 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
compare_keys(current.lhs_object_it->first, current.rhs_object_it->first,
std::integral_constant<bool, Ordered> {});
if (key_result != compare_result::equal)
{
return key_result;
}
left = &(current.lhs_object_it->second);
right = &(current.rhs_object_it->second);
if (key_result != compare_result::equal)
{
// An object type without a fixed order of its entries -
// std::unordered_map, say - may enumerate two equal
// objects differently, and its operator== does not care.
// Equality then finds the entry by its key; an ordering,
// or an object type that compares its entries in
// sequence (ordered_map), is decided by the key itself.
const auto* rhs_object = current.rhs_value->m_data.m_value.object;
const auto found = (!Ordered && !detail::is_ordered_map<object_t>::value)
? rhs_object->find(current.lhs_object_it->first)
: rhs_object->cend();
if (found == rhs_object->cend())
{
return key_result;
}
right = &(found->second);
}
++current.lhs_object_it;
++current.rhs_object_it;
}
+18
View File
@@ -9,6 +9,7 @@
#pragma once
#include <cstdint> // uint8_t
#include <cstddef> // size_t
#include <fstream> // ifstream, istreambuf_iterator, ios
#include <vector> // vector
@@ -24,6 +25,23 @@ namespace utils
template<typename T>
inline void ignore_return_value(T&& /*unused*/) noexcept {}
// Advance i toward last (inclusive) by stride, always visiting last.
// stride 7 is coprime to 256, so every low-byte residue is still hit.
template<typename T>
T next_integer_sample(T i, T last, T stride)
{
if (i >= last)
{
return static_cast<T>(last + 1);
}
if (stride > 0 && i > static_cast<T>(last - stride))
{
return last;
}
const T n = static_cast<T>(i + stride);
return n < last ? n : last;
}
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
{
std::ifstream file(filename, std::ios::binary);
+7 -25
View File
@@ -418,7 +418,7 @@ TEST_CASE("BJData")
SECTION("-32768..-129 (int16)")
{
for (int32_t i = -32768; i <= -129; ++i)
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
{
CAPTURE(i)
@@ -578,7 +578,7 @@ TEST_CASE("BJData")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; ++i)
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -911,7 +911,7 @@ TEST_CASE("BJData")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; ++i)
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -4541,45 +4541,27 @@ TEST_CASE("BJData roundtrips" * doctest::skip())
{
CAPTURE(filename)
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
auto packed = utils::read_binary_file(filename + ".bjdata");
{
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse BJData file
auto packed = utils::read_binary_file(filename + ".bjdata");
json j2;
CHECK_NOTHROW(j2 = json::from_bjdata(packed));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": std::ifstream");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse BJData file
std::ifstream f_bjdata(filename + ".bjdata", std::ios::binary);
json j2;
CHECK_NOTHROW(j2 = json::from_bjdata(f_bjdata));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": output to output adapters");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse BJData file
auto packed = utils::read_binary_file(filename + ".bjdata");
{
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
std::vector<uint8_t> vec;
+93 -34
View File
@@ -291,7 +291,7 @@ TEST_CASE("CBOR")
SECTION("-65536..-257")
{
for (int32_t i = -65536; i <= -257; ++i)
for (int32_t i = -65536; i <= -257; i = utils::next_integer_sample(i, -257, 7))
{
CAPTURE(i)
@@ -479,7 +479,7 @@ TEST_CASE("CBOR")
SECTION("256..65535")
{
for (size_t i = 256; i <= 65535; ++i)
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -614,7 +614,7 @@ TEST_CASE("CBOR")
SECTION("-32768..-129 (int 16)")
{
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
{
CAPTURE(i)
@@ -719,7 +719,7 @@ TEST_CASE("CBOR")
SECTION("256..65535 (two-byte uint16_t)")
{
for (size_t i = 256; i <= 65535; ++i)
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -2124,6 +2124,20 @@ TEST_CASE("CBOR nesting does not consume the call stack")
CHECK(json::from_cbor(input, true, false, json::cbor_tag_handler_t::ignore).is_discarded());
}
SECTION("stored tags")
{
// a tag over something other than a byte string is read like for
// ignore, so a chain of them must not recurse either (#5316)
std::vector<uint8_t> input;
for (std::size_t i = 0; i < 500000; ++i)
{
input.push_back(0xD8);
input.push_back(0x18);
}
input.push_back(0x01);
CHECK(json::from_cbor(input, true, true, json::cbor_tag_handler_t::store) == 1);
}
SECTION("a well-formed deep value is read through the SAX interface")
{
std::vector<uint8_t> input(200000, 0x9F);
@@ -2529,60 +2543,34 @@ TEST_CASE("CBOR roundtrips" * doctest::skip())
{
CAPTURE(filename)
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
const auto packed = utils::read_binary_file(filename + ".cbor");
{
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse CBOR file
const auto packed = utils::read_binary_file(filename + ".cbor");
json j2;
CHECK_NOTHROW(j2 = json::from_cbor(packed));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": std::ifstream");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse CBOR file
std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
json j2;
CHECK_NOTHROW(j2 = json::from_cbor(f_cbor));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse CBOR file
const auto packed = utils::read_binary_file(filename + ".cbor");
json j2;
CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": output to output adapters");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse CBOR file
const auto packed = utils::read_binary_file(filename + ".cbor");
if (exclude_packed.count(filename) == 0u)
{
{
@@ -3080,6 +3068,77 @@ TEST_CASE("Tagged values")
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
}
SECTION("issue #5316 - cbor_tag_handler_t::store on non-binary tagged items")
{
// 55799({"a": 1}) -- CBOR self-describe magic followed by a map
const std::vector<std::uint8_t> v_map{0xD9, 0xD9, 0xF7, 0xA1, 0x61, 0x61, 0x01};
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::ignore) == json({{"a", 1}}));
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}}));
// Tag 24 over unsigned integer 5
const std::vector<std::uint8_t> v_int{0xD8, 0x18, 0x05};
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::ignore) == 5);
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::store) == 5);
// Tag 24 over text string "foo"
const std::vector<std::uint8_t> v_str{0xD8, 0x18, 0x63, 'f', 'o', 'o'};
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::ignore) == "foo");
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::store) == "foo");
// Tag 24 over array [1, 2]
const std::vector<std::uint8_t> v_arr{0xD8, 0x18, 0x82, 0x01, 0x02};
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::ignore) == json({1, 2}));
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::store) == json({1, 2}));
// Tag 24 over boolean true
const std::vector<std::uint8_t> v_bool{0xD8, 0x18, 0xF5};
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::ignore) == true);
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::store) == true);
// Tag 24 over null
const std::vector<std::uint8_t> v_null{0xD8, 0x18, 0xF6};
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::ignore) == nullptr);
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::store) == nullptr);
// Nested tags: tag 55799 over tag 24 over integer 42
const std::vector<std::uint8_t> v_nested{0xD9, 0xD9, 0xF7, 0xD8, 0x18, 0x18, 0x2A};
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::ignore) == 42);
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::store) == 42);
// Tag 24 over byte string continues to store subtype as before
const std::vector<std::uint8_t> v_bin{0xD8, 0x18, 0x42, 0xCA, 0xFE};
auto j_bin_store = json::from_cbor(v_bin, true, true, json::cbor_tag_handler_t::store);
CHECK(j_bin_store.is_binary());
CHECK(j_bin_store.get_binary().has_subtype());
CHECK(j_bin_store.get_binary().subtype() == 24);
CHECK(j_bin_store.get_binary() == json::binary({0xCA, 0xFE}, 24).get_binary());
// Tagged values inside a container under store: [24(1), 25(h'0001')]
const std::vector<std::uint8_t> v_container{0x82, 0xD8, 0x18, 0x01, 0xD8, 0x19, 0x42, 0x00, 0x01};
auto j_container_store = json::from_cbor(v_container, true, true, json::cbor_tag_handler_t::store);
CHECK(j_container_store.is_array());
CHECK(j_container_store.size() == 2);
CHECK(j_container_store[0] == 1);
CHECK(j_container_store[1].is_binary());
CHECK(j_container_store[1].get_binary().has_subtype());
CHECK(j_container_store[1].get_binary().subtype() == 25);
CHECK(j_container_store[1].get_binary() == json::binary({0x00, 0x01}, 25).get_binary());
// Tagged values as object values under store: {"a": 55799(1), "b": 24(h'01')}
const std::vector<std::uint8_t> v_object{0xA2, 0x61, 'a', 0xD9, 0xD9, 0xF7, 0x01, 0x61, 'b', 0xD8, 0x18, 0x41, 0x01};
CHECK(json::from_cbor(v_object, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}, {"b", json::binary({0x01}, 24)}}));
// two tags in a row before a byte string: the inner tag is stored
// (this uses item_read and then the byte-string path)
const std::vector<std::uint8_t> v_nested_byte_string{0xD8, 0x18, 0xD8, 0x19, 0x42, 0x00, 0x01};
CHECK(json::from_cbor(v_nested_byte_string, true, true, json::cbor_tag_handler_t::store) == json::binary({0x00, 0x01}, 25));
// errors after a stored tag are now the same as with ignore
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18, 0x1C}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
}
}
SECTION("negative integer overflow")
+127
View File
@@ -15,7 +15,13 @@
#include "doctest_compatibility.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
@@ -745,6 +751,127 @@ TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P24
}
#endif
namespace
{
// orders keys ascending or descending, as chosen when a map is created
template<class Key>
class directed_less
{
public:
directed_less() = default;
explicit directed_less(const bool descending) noexcept
: m_descending(descending)
{}
bool operator()(const Key& lhs, const Key& rhs) const
{
return m_descending ? rhs < lhs : lhs < rhs;
}
private:
bool m_descending = false;
};
// An object type that, like std::unordered_map, enumerates its entries in no
// fixed order - ascending or descending by key, depending on how the map was
// created - and whose operator== does not depend on that order.
// std::unordered_map itself cannot be used here: the standard does not
// require it to accept an incomplete mapped type such as basic_json, and
// libstdc++ 6 to 9 as well as the EDG front ends of icpc and nvc++ reject
// basic_json<std::unordered_map>. std::map, the default object type, works
// with all supported compilers.
template<class Key, class Value, class /*Compare*/, class Allocator>
struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
{
using base_type = std::map<Key, Value, directed_less<Key>, Allocator>;
using base_type::base_type;
friend bool operator==(const unordered_object_t& lhs, const unordered_object_t& rhs)
{
return lhs.size() == rhs.size() && std::all_of(lhs.begin(), lhs.end(), [&rhs](const std::pair<const Key, Value>& entry)
{
const auto it = rhs.find(entry.first);
return it != rhs.end() && it->second == entry.second;
});
}
friend bool operator!=(const unordered_object_t& lhs, const unordered_object_t& rhs)
{
return !(lhs == rhs);
}
};
using unordered_json = nlohmann::basic_json<unordered_object_t>;
// the entries "0" to "9", enumerated in ascending or in descending order
unordered_json make_unordered_object(const bool descending)
{
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
for (int i = 0; i < 10; ++i)
{
j[std::to_string(i)] = i;
}
return j;
}
template<typename Json>
Json nest(Json j, const std::size_t depth)
{
for (std::size_t i = 0; i < depth; ++i)
{
Json outer = Json::object();
outer["x"] = std::move(j);
j = std::move(outer);
}
return j;
}
} // namespace
TEST_CASE("equality of objects whose entries have no fixed order")
{
// Values nested deeper than a bound are compared without the call stack,
// entry by entry. That must agree with the object type's own operator==,
// which for unordered_object_t (as for std::unordered_map) does not
// depend on the order of the entries, and for ordered_map does.
REQUIRE(make_unordered_object(true).begin().key() == "9");
REQUIRE(make_unordered_object(false).begin().key() == "0");
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
{
CAPTURE(depth);
const unordered_json descending = nest(make_unordered_object(true), depth);
const unordered_json ascending = nest(make_unordered_object(false), depth);
CHECK(descending == ascending);
CHECK_FALSE(descending != ascending);
// a copy is equal to its original
const unordered_json copy = descending; // NOLINT(performance-unnecessary-copy-initialization)
CHECK(copy == descending);
// a different value, a different key, or another entry still count
unordered_json other_value = make_unordered_object(true);
other_value["5"] = 42;
CHECK_FALSE(nest(other_value, depth) == ascending);
unordered_json other_key = make_unordered_object(true);
other_key.erase("5");
other_key["50"] = 5;
CHECK_FALSE(nest(other_key, depth) == ascending);
unordered_json more_entries = make_unordered_object(true);
more_entries["10"] = 10;
CHECK_FALSE(nest(more_entries, depth) == ascending);
CHECK_FALSE(ascending == nest(more_entries, depth));
// ordered_json compares its entries in sequence
const nlohmann::ordered_json ab = nest(nlohmann::ordered_json({{"a", 1}, {"b", 2}}), depth);
const nlohmann::ordered_json ba = nest(nlohmann::ordered_json({{"b", 2}, {"a", 1}}), depth);
CHECK_FALSE(ab == ba);
CHECK(ab != ba);
}
}
TEST_CASE("containers are compared element by element")
{
// Containers nested deeper than a bound are compared without the call
+1 -1
View File
@@ -18,7 +18,7 @@ TEST_CASE("tests on very large JSONs")
{
SECTION("issue #1419 - Segmentation fault (stack overflow) due to unbounded recursion")
{
const auto depth = 5000000;
const auto depth = 500000;
std::string s(static_cast<std::size_t>(2 * depth), '[');
std::fill(s.begin() + depth, s.end(), ']');
+90 -37
View File
@@ -14,6 +14,7 @@ using nlohmann::json;
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#include <cstdint> // SIZE_MAX, UINT32_MAX
#include <fstream>
#include <sstream>
#include <iomanip>
@@ -255,7 +256,7 @@ TEST_CASE("MessagePack")
SECTION("256..65535 (int 16)")
{
for (size_t i = 256; i <= 65535; ++i)
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -440,7 +441,7 @@ TEST_CASE("MessagePack")
SECTION("-32768..-129 (int 16)")
{
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
{
CAPTURE(i)
@@ -646,7 +647,7 @@ TEST_CASE("MessagePack")
SECTION("256..65535 (uint 16)")
{
for (size_t i = 256; i <= 65535; ++i)
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -2042,60 +2043,34 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip())
{
CAPTURE(filename)
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
auto packed = utils::read_binary_file(filename + ".msgpack");
{
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse MessagePack file
auto packed = utils::read_binary_file(filename + ".msgpack");
json j2;
CHECK_NOTHROW(j2 = json::from_msgpack(packed));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": std::ifstream");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse MessagePack file
std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary);
json j2;
CHECK_NOTHROW(j2 = json::from_msgpack(f_msgpack));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse MessagePack file
auto packed = utils::read_binary_file(filename + ".msgpack");
json j2;
CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()}));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": output to output adapters");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse MessagePack file
auto packed = utils::read_binary_file(filename + ".msgpack");
if (exclude_packed.count(filename) == 0u)
{
{
@@ -2189,6 +2164,8 @@ TEST_CASE("MessagePack with std::byte")
}
#endif
// the fake sizes below do not fit into a 32-bit std::size_t
#if SIZE_MAX > UINT32_MAX
template<typename T, typename A = std::allocator<T>>
struct huge_array : std::vector<T, A>
{
@@ -2226,7 +2203,7 @@ TEST_CASE("MessagePack Size above uint32 for array")
CHECK_THROWS_WITH_AS(
huge_array_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack size 4294967296 exceeds maximum of 4294967295",
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
array.fake_size = false;
@@ -2279,7 +2256,7 @@ TEST_CASE("MessagePack Size above uint32 for object")
CHECK_THROWS_WITH_AS(
huge_object_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack size 4294967296 exceeds maximum of 4294967295",
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
object.fake_size = false;
@@ -2315,7 +2292,7 @@ TEST_CASE("MessagePack Size above uint32 for string")
CHECK_THROWS_WITH_AS(
huge_string_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack size 4294967296 exceeds maximum of 4294967295",
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
}
@@ -2352,7 +2329,83 @@ TEST_CASE("MessagePack Size above uint32 for binary")
CHECK_THROWS_WITH_AS(
huge_binary_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack size 4294967296 exceeds maximum of 4294967295",
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
}
#endif
namespace
{
// types that report a size beyond UINT32_MAX without allocating that much
// memory, so the MessagePack length limit can be tested cheaply; see the
// similar types in unit-bson.cpp
std::size_t beyond_uint32_size()
{
return static_cast<std::size_t>((std::numeric_limits<std::uint32_t>::max)()) + 1;
}
class beyond_uint32_binary_t : public std::vector<std::uint8_t>
{
public:
using std::vector<std::uint8_t>::vector;
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
return beyond_uint32_size();
}
};
// with clang and libstdc++ 10, the std::filesystem::path conversion that
// C++17 builds consider for every string type is ambiguous for a class
// derived from std::string, so the string case is not tested there
#if !(defined(__clang__) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11)
#define JSON_TEST_BEYOND_UINT32_STRING 1
#endif
#ifdef JSON_TEST_BEYOND_UINT32_STRING
class beyond_uint32_string_t : public std::string
{
public:
using std::string::string;
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
return beyond_uint32_size();
}
};
using beyond_uint32_string_json = nlohmann::basic_json <
std::map, std::vector, beyond_uint32_string_t, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
#endif
using beyond_uint32_binary_json = nlohmann::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, beyond_uint32_binary_t, void >;
} // namespace
TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized")
{
// MessagePack stores the length of a string, binary value, array, or
// object in at most 32 bits; a larger one used to be written without any
// length at all
#if SIZE_MAX > UINT32_MAX
{
const char* const expected = "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295";
const beyond_uint32_binary_json binary = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{});
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(binary), expected, beyond_uint32_binary_json::out_of_range&);
const beyond_uint32_binary_json ext = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}, 42);
CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(ext), expected, beyond_uint32_binary_json::out_of_range&);
#ifdef JSON_TEST_BEYOND_UINT32_STRING
// created from its type rather than from a beyond_uint32_string_t:
// that would consider the std::filesystem::path conversion, which
// libstdc++ 10 cannot decide for a class derived from std::string
const beyond_uint32_string_json string(beyond_uint32_string_json::value_t::string);
CHECK_THROWS_WITH_AS(beyond_uint32_string_json::to_msgpack(string), expected, beyond_uint32_string_json::out_of_range&);
#endif
}
#endif
}
+7 -33
View File
@@ -265,7 +265,7 @@ TEST_CASE("UBJSON")
SECTION("-32768..-129 (int16)")
{
for (int32_t i = -32768; i <= -129; ++i)
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
{
CAPTURE(i)
@@ -425,7 +425,7 @@ TEST_CASE("UBJSON")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; ++i)
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -631,7 +631,7 @@ TEST_CASE("UBJSON")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; ++i)
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
{
CAPTURE(i)
@@ -2980,60 +2980,34 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
{
CAPTURE(filename)
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
auto const packed = utils::read_binary_file(filename + ".ubjson");
{
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse UBJSON file
auto const packed = utils::read_binary_file(filename + ".ubjson");
json j2;
CHECK_NOTHROW(j2 = json::from_ubjson(packed));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": std::ifstream");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse UBJSON file
std::ifstream f_ubjson(filename + ".ubjson", std::ios::binary);
json j2;
CHECK_NOTHROW(j2 = json::from_ubjson(f_ubjson));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": uint8_t* and size");
// parse JSON file
std::ifstream f_json(filename);
const json j1 = json::parse(f_json);
// parse UBJSON file
auto const packed = utils::read_binary_file(filename + ".ubjson");
json j2;
CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()}));
// compare parsed JSON values
CHECK(j1 == j2);
}
{
INFO_WITH_TEMP(filename + ": output to output adapters");
// parse JSON file
std::ifstream f_json(filename);
json const j1 = json::parse(f_json);
// parse UBJSON file
auto const packed = utils::read_binary_file(filename + ".ubjson");
{
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
std::vector<uint8_t> vec;