Compare commits

..
Author SHA1 Message Date
Niels Lohmann 90078597c7 Test the remaining to_json overloads with a failing allocation
The test only reached 5 of the 14 external_constructor overloads that
were reordered: reverting the fix in any of the other 9 (const string_t&,
compatible strings, binary_t&&, array_t const&/&&, std::valarray, ranges
views, object_t const&/&&) left it green. Each of them is now called with
a failing allocation, and reverting any single hunk of the fix makes the
test fail under ASan.

binary_t&& is only reached by to_json for a binary container type other
than std::vector<std::uint8_t>, so the test calls the constructor
directly.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 18:29:37 +02:00
Niels Lohmann fa28fff7d6 Skip the vector<bool> failed-allocation check for VS 2015 with iterator debugging
AppVeyor's VS 2015 Debug build terminated in "converting into an
existing value": to_json for std::vector<bool> default-constructs an
array_t first, and VS 2015's std::vector default constructor is noexcept
but, with iterator debugging, constructs its proxy through the test's
allocator, which throws.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 13:30:57 +02:00
Niels Lohmann e9c84befa1 Keep the created pointer rather than an uninitialized json_value
clang-tidy reported the json_value unions declared in the string, array,
and object constructors of to_json.hpp as uninitialized
(cppcoreguidelines-pro-type-member-init). Assign the created pointer to
the member directly once the old value is destroyed.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 10:07:22 +02:00
Niels Lohmann 8a26f2dc8f Skip the failed-allocation test when exceptions are disabled
The no-exceptions CI job runs doctest with --no-throw, which skips every
CHECK_THROWS_AS. The test then left next_construct_fails set, and the
next allocation outside a check threw std::bad_alloc.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 07:41:42 +02:00
Niels Lohmann 2c108d0b56 Create a value before giving it its type
Several functions set the type of a value before creating the string,
array, object, or binary value it stands for. When that creation threw -
std::bad_alloc from the allocator, say - the value was left behind with
the new type but nothing behind it:

- json::binary() returned no value, but its destructor asserted that a
  binary value has a binary array, aborting debug builds.
- operator[], push_back, emplace_back, emplace, and update turned a null
  value into an array or object that did not exist; any later access
  dereferenced a null pointer.
- The to_json conversions destroyed the old value first. A failed
  creation of the new one left a pointer to the destroyed old value,
  which was then used and freed again (heap-use-after-free).

The value is now created first and the type set after it, and to_json
destroys the old value only once the new one exists, so a failed
allocation leaves the value unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 22:19:39 +02:00
33 changed files with 919 additions and 7509 deletions
@@ -18,7 +18,7 @@ ignore
: ignore tags
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. If several tags precede a byte string, only the innermost one is stored.
: store tagged values as binary container with subtype (for bytes 0xd8..0xdb)
## Examples
@@ -34,15 +34,6 @@ 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`.
@@ -74,4 +65,3 @@ 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. 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.
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.
??? example
@@ -65,8 +65,6 @@ 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),
+4 -10
View File
@@ -932,25 +932,19 @@ 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, 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.
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.
!!! failure "Example messages"
!!! failure "Example message"
```
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 BSON length was silently truncated, and
This exception was added in version 3.13.0. Before that, the 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; [`to_msgpack`](../api/basic_json/to_msgpack.md) wrote such
a value without any length, producing output that could not be read back.
[`from_bson`](../api/basic_json/from_bson.md) rejected.
### json.exception.out_of_range.413
+45 -25
View File
@@ -42,6 +42,10 @@ namespace detail
* 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
* 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;
@@ -65,18 +69,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.assert_invariant();
}
@@ -85,9 +91,10 @@ struct external_constructor<value_t::string>
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
auto* created = j.template create<typename BasicJsonType::string_t>(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.m_data.m_value.string = created;
j.assert_invariant();
}
};
@@ -98,18 +105,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.assert_invariant();
}
};
@@ -159,9 +168,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -169,9 +179,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -187,9 +198,10 @@ struct external_constructor<value_t::array>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::array_t>(begin(arr), end(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.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.array = created;
j.set_parents();
j.assert_invariant();
}
@@ -197,15 +209,17 @@ struct external_constructor<value_t::array>
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());
typename BasicJsonType::array_t elements;
elements.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());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
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;
j.set_parents();
j.assert_invariant();
}
@@ -213,11 +227,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
typename BasicJsonType::array_t elements(arr.size());
std::copy(std::begin(arr), std::end(arr), elements.begin());
const typename BasicJsonType::json_value value(std::move(elements));
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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -229,14 +244,16 @@ 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;
typename BasicJsonType::array_t elements;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
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;
j.set_parents();
j.assert_invariant();
}
#endif
@@ -248,9 +265,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -258,9 +276,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -272,9 +291,10 @@ struct external_constructor<value_t::object>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::object_t>(begin(obj), end(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.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.object = created;
j.set_parents();
j.assert_invariant();
}
@@ -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 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
store ///< store tags as binary type
};
/*!
@@ -592,18 +592,14 @@ 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& item_read)
bool& tag_pending)
{
tag_pending = false;
item_read = false;
switch (get_char ? get() : current)
{
@@ -1025,17 +1021,7 @@ class binary_reader
}
}
get();
// 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;
return get_cbor_binary(b) && sax->binary(b);
}
default: // LCOV_EXCL_LINE
@@ -1517,14 +1503,13 @@ 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, item_read)))
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
{
return false;
}
fetch = !item_read;
fetch = true;
}
while (tag_pending);
+227 -135
View File
@@ -168,20 +168,92 @@ class binary_writer
if (j.m_data.m_value.number_integer >= 0)
{
// CBOR does not differentiate between positive signed
// integers and unsigned integers
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
// integers and unsigned integers. Therefore, we used the
// code from the value_t::number_unsigned case here.
if (j.m_data.m_value.number_integer <= 0x17)
{
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x18));
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x19));
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x1A));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
{
oa.write_character(to_char_type(0x1B));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
}
else
{
// a negative integer n is encoded as -1 - n
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
// The conversions below encode the sign in the first
// byte, and the value is converted to a positive number.
const auto positive_number = -1 - j.m_data.m_value.number_integer;
if (j.m_data.m_value.number_integer >= -24)
{
write_number(static_cast<std::uint8_t>(0x20 + positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x38));
write_number(static_cast<std::uint8_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x39));
write_number(static_cast<std::uint16_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x3A));
write_number(static_cast<std::uint32_t>(positive_number));
}
else
{
oa.write_character(to_char_type(0x3B));
write_number(static_cast<std::uint64_t>(positive_number));
}
}
break;
}
case value_t::number_unsigned:
{
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
if (j.m_data.m_value.number_unsigned <= 0x17)
{
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x18));
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x19));
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x1A));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
}
else
{
oa.write_character(to_char_type(0x1B));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
}
break;
}
@@ -211,7 +283,33 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
write_cbor_head(0x60, j.m_data.m_value.string->size());
const auto N = j.m_data.m_value.string->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x60 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x78));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x79));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x7A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x7B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write the string
oa.write_characters(
@@ -223,7 +321,33 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
write_cbor_head(0x80, j.m_data.m_value.array->size());
const auto N = j.m_data.m_value.array->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x80 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x98));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x99));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x9A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x9B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_data.m_value.array)
@@ -252,7 +376,7 @@ class binary_writer
write_number(static_cast<std::uint8_t>(0xda));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
}
else
else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xdb));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
@@ -261,7 +385,32 @@ class binary_writer
// step 1: write control byte and the binary array size
const auto N = j.m_data.m_value.binary->size();
write_cbor_head(0x40, N);
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x40 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x58));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x59));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x5A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x5B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
oa.write_characters(
@@ -274,7 +423,33 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
write_cbor_head(0xA0, j.m_data.m_value.object->size());
const auto N = j.m_data.m_value.object->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0xA0 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0xB8));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0xB9));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0xBA));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0xBB));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
@@ -291,23 +466,6 @@ 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
*/
@@ -359,7 +517,7 @@ class binary_writer
oa.write_character(to_char_type(0xCE));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
// uint 64
oa.write_character(to_char_type(0xCF));
@@ -394,7 +552,8 @@ class binary_writer
oa.write_character(to_char_type(0xD2));
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
// int 64
oa.write_character(to_char_type(0xD3));
@@ -429,7 +588,7 @@ class binary_writer
oa.write_character(to_char_type(0xCE));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
// uint 64
oa.write_character(to_char_type(0xCF));
@@ -447,7 +606,7 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
const auto N = j.m_data.m_value.string->size();
if (N <= 31)
{
// fixstr
@@ -465,7 +624,7 @@ class binary_writer
oa.write_character(to_char_type(0xDA));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// str 32
oa.write_character(to_char_type(0xDB));
@@ -482,7 +641,7 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
const auto N = j.m_data.m_value.array->size();
if (N <= 15)
{
// fixarray
@@ -494,7 +653,7 @@ class binary_writer
oa.write_character(to_char_type(0xDC));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// array 32
oa.write_character(to_char_type(0xDD));
@@ -516,7 +675,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 = to_msgpack_length(j.m_data.m_value.binary->size(), j);
const auto N = j.m_data.m_value.binary->size();
if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
std::uint8_t output_type{};
@@ -568,7 +727,7 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
const std::uint8_t output_type = use_ext
? 0xC9 // ext 32
@@ -600,7 +759,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
const auto N = j.m_data.m_value.object->size();
if (N <= 15)
{
// fixmap
@@ -612,7 +771,7 @@ class binary_writer
oa.write_character(to_char_type(0xDE));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// map 32
oa.write_character(to_char_type(0xDF));
@@ -1367,46 +1526,6 @@ class binary_writer
// CBOR //
//////////
/*!
@brief write the head of a CBOR data item
The head is the major type in the upper three bits of the first byte and
an argument - an unsigned integer, the length of a string, the number of
elements of a container - in the shortest of its encodings: in the lower
five bits of the first byte itself if it is at most 23, otherwise in the
1, 2, 4, or 8 bytes that follow (RFC 8949, section 3).
@param[in] major_type the major type, shifted into the upper three bits
@param[in] argument the argument of the data item
*/
void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument)
{
if (argument <= 0x17)
{
write_number(static_cast<std::uint8_t>(major_type + argument));
}
else if (argument <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x18)));
write_number(static_cast<std::uint8_t>(argument));
}
else if (argument <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x19)));
write_number(static_cast<std::uint16_t>(argument));
}
else if (argument <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1A)));
write_number(static_cast<std::uint32_t>(argument));
}
else
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1B)));
write_number(argument);
}
}
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
{
return to_char_type(0xFA); // Single-Precision Float
@@ -1512,7 +1631,7 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
else if (use_bjdata)
else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())
{
if (add_prefix)
{
@@ -1592,59 +1711,30 @@ class binary_writer
}
write_number(static_cast<uint32_t>(n), use_bjdata);
}
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
if (add_prefix)
{
oa.write_character(to_char_type('L')); // int64
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
// LCOV_EXCL_START
else
{
// every value of an integer type of at most 64 bits fits into an
// int64; only a wider type needs a range check
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
if (add_prefix)
{
oa.write_character(to_char_type('H')); // high-precision number
}
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
}
template<typename NumberType>
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
{
if (add_prefix)
{
oa.write_character(to_char_type('L')); // int64
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
template<typename NumberType>
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
{
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
return;
}
if (add_prefix)
{
oa.write_character(to_char_type('H')); // high-precision number
}
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
template<typename NumberType>
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
{
return 'L';
}
template<typename NumberType>
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
{
// anything outside of the range of an int64 is treated as a
// high-precision number
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
// LCOV_EXCL_STOP
}
/*!
@@ -1686,10 +1776,12 @@ class binary_writer
{
return 'm';
}
// every value of an integer type of at most 64 bits fits into
// an int64; only a wider type needs a range check
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
return 'L';
}
// anything else is treated as a high-precision number
return 'H'; // LCOV_EXCL_LINE
}
case value_t::number_unsigned:
@@ -1722,12 +1814,12 @@ class binary_writer
{
return 'L';
}
if (use_bjdata)
if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
return 'M';
}
// anything else is treated as a high-precision number
return 'H';
return 'H'; // LCOV_EXCL_LINE
}
case value_t::number_float:
+29 -33
View File
@@ -1492,28 +1492,13 @@ 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> {});
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);
return key_result;
}
left = &(current.lhs_object_it->second);
right = &(current.rhs_object_it->second);
++current.lhs_object_it;
++current.rhs_object_it;
}
@@ -1702,8 +1687,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object)
{
// the initializer list is a list of pairs -> create an object
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init)
{
@@ -1725,8 +1710,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
#endif
// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
}
set_parents();
@@ -1739,8 +1724,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1750,8 +1735,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1761,8 +1746,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -1772,8 +1757,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -2172,6 +2157,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// value access //
//////////////////
/// get a boolean (explicit)
boolean_t get_impl(boolean_t* /*unused*/) const
{
if (JSON_HEDLEY_LIKELY(is_boolean()))
{
return m_data.m_value.boolean;
}
JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -2819,8 +2815,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>();
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -2879,8 +2875,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -2932,8 +2928,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -3868,8 +3864,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -3901,8 +3897,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -3933,8 +3929,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -3989,8 +3985,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -4014,8 +4010,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -4196,8 +4192,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
-5676
View File
File diff suppressed because it is too large Load Diff
+306 -213
View File
@@ -6571,6 +6571,10 @@ namespace detail
* 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
* 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;
@@ -6594,18 +6598,20 @@ struct external_constructor<value_t::string>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.assert_invariant();
}
@@ -6614,9 +6620,10 @@ struct external_constructor<value_t::string>
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
auto* created = j.template create<typename BasicJsonType::string_t>(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.m_data.m_value.string = created;
j.assert_invariant();
}
};
@@ -6627,18 +6634,20 @@ struct external_constructor<value_t::binary>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.assert_invariant();
}
};
@@ -6688,9 +6697,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6698,9 +6708,10 @@ struct external_constructor<value_t::array>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6716,9 +6727,10 @@ struct external_constructor<value_t::array>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::array_t>(begin(arr), end(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.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.m_data.m_value.array = created;
j.set_parents();
j.assert_invariant();
}
@@ -6726,15 +6738,17 @@ struct external_constructor<value_t::array>
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());
typename BasicJsonType::array_t elements;
elements.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());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
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;
j.set_parents();
j.assert_invariant();
}
@@ -6742,11 +6756,12 @@ struct external_constructor<value_t::array>
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
typename BasicJsonType::array_t elements(arr.size());
std::copy(std::begin(arr), std::end(arr), elements.begin());
const typename BasicJsonType::json_value value(std::move(elements));
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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6758,14 +6773,16 @@ 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;
typename BasicJsonType::array_t elements;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
elements.push_back(x);
}
const typename BasicJsonType::json_value value(std::move(elements));
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;
j.set_parents();
j.assert_invariant();
}
#endif
@@ -6777,9 +6794,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
const typename BasicJsonType::json_value value(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6787,9 +6805,10 @@ struct external_constructor<value_t::object>
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
const typename BasicJsonType::json_value value(std::move(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.m_data.m_value = value;
j.set_parents();
j.assert_invariant();
}
@@ -6801,9 +6820,10 @@ struct external_constructor<value_t::object>
using std::begin;
using std::end;
auto* created = j.template create<typename BasicJsonType::object_t>(begin(obj), end(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.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.m_data.m_value.object = created;
j.set_parents();
j.assert_invariant();
}
@@ -12741,7 +12761,7 @@ enum class cbor_tag_handler_t
{
error, ///< throw a parse_error exception in case of a tag
ignore, ///< ignore tags
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
store ///< store tags as binary type
};
/*!
@@ -13289,18 +13309,14 @@ 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& item_read)
bool& tag_pending)
{
tag_pending = false;
item_read = false;
switch (get_char ? get() : current)
{
@@ -13722,17 +13738,7 @@ class binary_reader
}
}
get();
// 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;
return get_cbor_binary(b) && sax->binary(b);
}
default: // LCOV_EXCL_LINE
@@ -14214,14 +14220,13 @@ 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, item_read)))
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
{
return false;
}
fetch = !item_read;
fetch = true;
}
while (tag_pending);
@@ -19648,20 +19653,92 @@ class binary_writer
if (j.m_data.m_value.number_integer >= 0)
{
// CBOR does not differentiate between positive signed
// integers and unsigned integers
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
// integers and unsigned integers. Therefore, we used the
// code from the value_t::number_unsigned case here.
if (j.m_data.m_value.number_integer <= 0x17)
{
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x18));
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x19));
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x1A));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
{
oa.write_character(to_char_type(0x1B));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
}
else
{
// a negative integer n is encoded as -1 - n
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
// The conversions below encode the sign in the first
// byte, and the value is converted to a positive number.
const auto positive_number = -1 - j.m_data.m_value.number_integer;
if (j.m_data.m_value.number_integer >= -24)
{
write_number(static_cast<std::uint8_t>(0x20 + positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x38));
write_number(static_cast<std::uint8_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x39));
write_number(static_cast<std::uint16_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x3A));
write_number(static_cast<std::uint32_t>(positive_number));
}
else
{
oa.write_character(to_char_type(0x3B));
write_number(static_cast<std::uint64_t>(positive_number));
}
}
break;
}
case value_t::number_unsigned:
{
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
if (j.m_data.m_value.number_unsigned <= 0x17)
{
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x18));
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x19));
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
}
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x1A));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
}
else
{
oa.write_character(to_char_type(0x1B));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
}
break;
}
@@ -19691,7 +19768,33 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
write_cbor_head(0x60, j.m_data.m_value.string->size());
const auto N = j.m_data.m_value.string->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x60 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x78));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x79));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x7A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x7B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write the string
oa.write_characters(
@@ -19703,7 +19806,33 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
write_cbor_head(0x80, j.m_data.m_value.array->size());
const auto N = j.m_data.m_value.array->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x80 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x98));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x99));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x9A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x9B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_data.m_value.array)
@@ -19732,7 +19861,7 @@ class binary_writer
write_number(static_cast<std::uint8_t>(0xda));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
}
else
else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xdb));
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
@@ -19741,7 +19870,32 @@ class binary_writer
// step 1: write control byte and the binary array size
const auto N = j.m_data.m_value.binary->size();
write_cbor_head(0x40, N);
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0x40 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0x58));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0x59));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0x5A));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0x5B));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
oa.write_characters(
@@ -19754,7 +19908,33 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
write_cbor_head(0xA0, j.m_data.m_value.object->size());
const auto N = j.m_data.m_value.object->size();
if (N <= 0x17)
{
write_number(static_cast<std::uint8_t>(0xA0 + N));
}
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(0xB8));
write_number(static_cast<std::uint8_t>(N));
}
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(0xB9));
write_number(static_cast<std::uint16_t>(N));
}
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(0xBA));
write_number(static_cast<std::uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
{
oa.write_character(to_char_type(0xBB));
write_number(static_cast<std::uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
@@ -19771,23 +19951,6 @@ 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
*/
@@ -19839,7 +20002,7 @@ class binary_writer
oa.write_character(to_char_type(0xCE));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
// uint 64
oa.write_character(to_char_type(0xCF));
@@ -19874,7 +20037,8 @@ class binary_writer
oa.write_character(to_char_type(0xD2));
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
// int 64
oa.write_character(to_char_type(0xD3));
@@ -19909,7 +20073,7 @@ class binary_writer
oa.write_character(to_char_type(0xCE));
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
}
else
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
// uint 64
oa.write_character(to_char_type(0xCF));
@@ -19927,7 +20091,7 @@ class binary_writer
case value_t::string:
{
// step 1: write control byte and the string length
const auto N = to_msgpack_length(j.m_data.m_value.string->size(), j);
const auto N = j.m_data.m_value.string->size();
if (N <= 31)
{
// fixstr
@@ -19945,7 +20109,7 @@ class binary_writer
oa.write_character(to_char_type(0xDA));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// str 32
oa.write_character(to_char_type(0xDB));
@@ -19962,7 +20126,7 @@ class binary_writer
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
const auto N = j.m_data.m_value.array->size();
if (N <= 15)
{
// fixarray
@@ -19974,7 +20138,7 @@ class binary_writer
oa.write_character(to_char_type(0xDC));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// array 32
oa.write_character(to_char_type(0xDD));
@@ -19996,7 +20160,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 = to_msgpack_length(j.m_data.m_value.binary->size(), j);
const auto N = j.m_data.m_value.binary->size();
if (N <= (std::numeric_limits<std::uint8_t>::max)())
{
std::uint8_t output_type{};
@@ -20048,7 +20212,7 @@ class binary_writer
oa.write_character(to_char_type(output_type));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
const std::uint8_t output_type = use_ext
? 0xC9 // ext 32
@@ -20080,7 +20244,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
const auto N = j.m_data.m_value.object->size();
if (N <= 15)
{
// fixmap
@@ -20092,7 +20256,7 @@ class binary_writer
oa.write_character(to_char_type(0xDE));
write_number(static_cast<std::uint16_t>(N));
}
else
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
{
// map 32
oa.write_character(to_char_type(0xDF));
@@ -20847,46 +21011,6 @@ class binary_writer
// CBOR //
//////////
/*!
@brief write the head of a CBOR data item
The head is the major type in the upper three bits of the first byte and
an argument - an unsigned integer, the length of a string, the number of
elements of a container - in the shortest of its encodings: in the lower
five bits of the first byte itself if it is at most 23, otherwise in the
1, 2, 4, or 8 bytes that follow (RFC 8949, section 3).
@param[in] major_type the major type, shifted into the upper three bits
@param[in] argument the argument of the data item
*/
void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument)
{
if (argument <= 0x17)
{
write_number(static_cast<std::uint8_t>(major_type + argument));
}
else if (argument <= (std::numeric_limits<std::uint8_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x18)));
write_number(static_cast<std::uint8_t>(argument));
}
else if (argument <= (std::numeric_limits<std::uint16_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x19)));
write_number(static_cast<std::uint16_t>(argument));
}
else if (argument <= (std::numeric_limits<std::uint32_t>::max)())
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1A)));
write_number(static_cast<std::uint32_t>(argument));
}
else
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1B)));
write_number(argument);
}
}
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
{
return to_char_type(0xFA); // Single-Precision Float
@@ -20992,7 +21116,7 @@ class binary_writer
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
else if (use_bjdata)
else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())
{
if (add_prefix)
{
@@ -21072,59 +21196,30 @@ class binary_writer
}
write_number(static_cast<uint32_t>(n), use_bjdata);
}
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
if (add_prefix)
{
oa.write_character(to_char_type('L')); // int64
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
// LCOV_EXCL_START
else
{
// every value of an integer type of at most 64 bits fits into an
// int64; only a wider type needs a range check
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
if (add_prefix)
{
oa.write_character(to_char_type('H')); // high-precision number
}
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
}
template<typename NumberType>
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
{
if (add_prefix)
{
oa.write_character(to_char_type('L')); // int64
}
write_number(static_cast<std::int64_t>(n), use_bjdata);
}
template<typename NumberType>
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
{
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
{
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
return;
}
if (add_prefix)
{
oa.write_character(to_char_type('H')); // high-precision number
}
const auto number = BasicJsonType(n).dump();
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
for (std::size_t i = 0; i < number.size(); ++i)
{
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
}
}
template<typename NumberType>
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
{
return 'L';
}
template<typename NumberType>
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
{
// anything outside of the range of an int64 is treated as a
// high-precision number
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
// LCOV_EXCL_STOP
}
/*!
@@ -21166,10 +21261,12 @@ class binary_writer
{
return 'm';
}
// every value of an integer type of at most 64 bits fits into
// an int64; only a wider type needs a range check
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
{
return 'L';
}
// anything else is treated as a high-precision number
return 'H'; // LCOV_EXCL_LINE
}
case value_t::number_unsigned:
@@ -21202,12 +21299,12 @@ class binary_writer
{
return 'L';
}
if (use_bjdata)
if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
{
return 'M';
}
// anything else is treated as a high-precision number
return 'H';
return 'H'; // LCOV_EXCL_LINE
}
case value_t::number_float:
@@ -26373,28 +26470,13 @@ 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> {});
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);
return key_result;
}
left = &(current.lhs_object_it->second);
right = &(current.rhs_object_it->second);
++current.lhs_object_it;
++current.rhs_object_it;
}
@@ -26583,8 +26665,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (is_an_object)
{
// the initializer list is a list of pairs -> create an object
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
for (auto& element_ref : init)
{
@@ -26606,8 +26688,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
#endif
// the initializer list describes an array -> create an array
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>(init.begin(), init.end());
m_data.m_type = value_t::array;
}
set_parents();
@@ -26620,8 +26702,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = init;
res.m_data.m_type = value_t::binary;
return res;
}
@@ -26631,8 +26713,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(init, subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -26642,8 +26724,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = std::move(init);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -26653,8 +26735,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
{
auto res = basic_json();
res.m_data.m_type = value_t::binary;
res.m_data.m_value = binary_t(std::move(init), subtype);
res.m_data.m_type = value_t::binary;
return res;
}
@@ -27053,6 +27135,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// value access //
//////////////////
/// get a boolean (explicit)
boolean_t get_impl(boolean_t* /*unused*/) const
{
if (JSON_HEDLEY_LIKELY(is_boolean()))
{
return m_data.m_value.boolean;
}
JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
}
/// get a pointer to the value (object)
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
{
@@ -27700,8 +27793,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value.array = create<array_t>();
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -27760,8 +27853,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -27813,8 +27906,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -28749,8 +28842,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -28782,8 +28875,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -28814,8 +28907,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -28870,8 +28963,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an array
if (is_null())
{
m_data.m_type = value_t::array;
m_data.m_value = value_t::array;
m_data.m_type = value_t::array;
assert_invariant();
}
@@ -28895,8 +28988,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// transform a null object into an object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value = value_t::object;
m_data.m_type = value_t::object;
assert_invariant();
}
@@ -29077,8 +29170,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// implicitly convert a null value to an empty object
if (is_null())
{
m_data.m_type = value_t::object;
m_data.m_value.object = create<object_t>();
m_data.m_type = value_t::object;
assert_invariant();
}
-18
View File
@@ -9,7 +9,6 @@
#pragma once
#include <cstdint> // uint8_t
#include <cstddef> // size_t
#include <fstream> // ifstream, istreambuf_iterator, ios
#include <vector> // vector
@@ -25,23 +24,6 @@ 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);
+175 -6
View File
@@ -12,6 +12,11 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <valarray>
#if JSON_HAS_RANGES
#include <ranges>
#endif
namespace
{
// special test case to check if memory is leaked if constructor throws
@@ -37,12 +42,6 @@ struct bad_allocator : std::allocator<T>
};
} // namespace
TEST_CASE("get_allocator")
{
const auto alloc = nlohmann::json::get_allocator();
CHECK(alloc == std::allocator<nlohmann::json>());
}
TEST_CASE("bad_alloc")
{
SECTION("bad_alloc")
@@ -394,3 +393,173 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak");
}
}
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
// next_construct_fails set for the next allocation outside a check
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("a failed allocation leaves the value unchanged")
{
// create JSON type using the throwing allocator
using my_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
my_allocator>;
// Each of these creates a string, array, object, or binary value. The
// value must be created before the type is changed: otherwise, a failed
// creation left a value of the new type without anything behind it (an
// assertion in its destructor, a null pointer everywhere else) or, when
// an old value was destroyed first, with a pointer to that destroyed one.
SECTION("creating a binary value")
{
const std::vector<std::uint8_t> bytes = {1, 2, 3};
my_json _;
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes)), std::bad_alloc&);
next_construct_fails = true;
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes), 42), std::bad_alloc&);
next_construct_fails = false;
}
SECTION("turning a null value into an array or object")
{
my_json j;
next_construct_fails = true;
CHECK_THROWS_AS(j[0], std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
CHECK(j.is_null());
#ifdef JSON_HAS_CPP_17
next_construct_fails = true;
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
CHECK(j.is_null());
#endif
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
CHECK(j.is_null());
const my_json one = 1;
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = true;
CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&);
CHECK(j.is_null());
const my_json object = {{"key", 1}};
next_construct_fails = true;
CHECK_THROWS_AS(j.update(object), std::bad_alloc&);
CHECK(j.is_null());
next_construct_fails = false;
}
SECTION("converting into an existing value")
{
// to_json replaces the value it is given; the old one must survive a
// failed creation of the new one
my_json j = "old";
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<int> {1, 2}), std::bad_alloc&);
CHECK(j == "old");
// with iterator debugging, the default constructor of VS 2015's
// std::vector is noexcept but constructs a proxy with the allocator,
// which terminates when this allocator throws
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&);
CHECK(j == "old");
#endif
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::map<std::string, int> {{"a", 1}}), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&);
CHECK(j == "old");
// the overloads for lvalues of the value types, for the value types
// themselves, and for the remaining compatible types
const std::string string = "new";
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&);
CHECK(j == "old");
// to_json only moves a binary value that it converted from another
// container type, which my_json's std::vector<std::uint8_t> is not
using binary_constructor = nlohmann::detail::external_constructor<nlohmann::detail::value_t::binary>;
next_construct_fails = true;
CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&);
CHECK(j == "old");
my_json::array_t array = {1, 2};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&);
CHECK(j == "old");
my_json::object_t object = {{"a", 1}};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&);
CHECK(j == "old");
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
CHECK(j == "old");
#if JSON_HAS_RANGES && !defined(__MINGW32__)
const std::vector<int> numbers = {1, 2};
next_construct_fails = true;
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
{
return true;
})), std::bad_alloc&);
CHECK(j == "old");
#endif
next_construct_fails = false;
nlohmann::to_json(j, std::vector<int> {1, 2});
CHECK(j == my_json({1, 2}));
}
}
#endif
+25 -120
View File
@@ -418,7 +418,7 @@ TEST_CASE("BJData")
SECTION("-32768..-129 (int16)")
{
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
for (int32_t i = -32768; i <= -129; ++i)
{
CAPTURE(i)
@@ -578,7 +578,7 @@ TEST_CASE("BJData")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
for (size_t i = 256; i <= 32767; ++i)
{
CAPTURE(i)
@@ -911,7 +911,7 @@ TEST_CASE("BJData")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
for (size_t i = 256; i <= 32767; ++i)
{
CAPTURE(i)
@@ -3763,49 +3763,6 @@ TEST_CASE("BJData")
}
}
TEST_CASE("BJData input that cannot be read is discarded by every overload")
{
std::vector<std::uint8_t> input = json::to_bjdata(json({{"a", {1, 2}}}));
input.pop_back();
json _;
CHECK_THROWS_AS(_ = json::from_bjdata(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_bjdata(input, true, false).is_discarded());
CHECK(json::from_bjdata(input.begin(), input.end(), true, false).is_discarded());
}
TEST_CASE("BJData SAX parsing stops at every event")
{
// Containers are opened and closed by the loop that reads them; a SAX
// handler that rejects any event - including the end of a nested
// container - must stop the parse right there.
const auto count_events = [](const std::vector<std::uint8_t>& input)
{
int events = 0;
while (true)
{
SaxCountdown scp(events);
if (json::sax_parse(input, &scp, json::input_format_t::bjdata))
{
return events;
}
++events;
REQUIRE(events < 1000);
}
};
// 20 events: every container kind closes inside another one
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
CHECK(count_events(json::to_bjdata(j)) == 20);
CHECK(count_events(json::to_bjdata(j, true)) == 20);
CHECK(count_events(json::to_bjdata(j, true, true)) == 20);
// an ND-array is announced as an annotated object: start_object, then
// _ArrayType_, _ArraySize_ and _ArrayData_ with its elements
const json ndarray = json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})");
CHECK(count_events(json::to_bjdata(ndarray, true, true)) == 16);
}
TEST_CASE("issue #5405 - array reserve for definite-length BJData arrays")
{
#if !defined(JSON_NOEXCEPTION)
@@ -4290,65 +4247,6 @@ TEST_CASE("all BJData first bytes")
}
#endif
TEST_CASE("BJData and UBJSON can be written to a string")
{
const std::vector<json> values =
{
{{"a", {1, 2.5, "x", nullptr}}, {"b", json::binary({1, 2})}},
// an annotated ND-array, and objects that only look like one
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
json::parse(R"({"_ArrayType_": 1, "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3, 4]})"),
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": 4, "_ArrayData_": [1, 2, 3, 4]})"),
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, -2], "_ArrayData_": [1, 2, 3, 4]})"),
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": [1, 2, 3]})"),
json::parse(R"({"_ArrayType_": "uint8", "_ArraySize_": [2, 2], "_ArrayData_": 1})"),
};
// compared byte by byte: building a std::string from the bytes would
// convert them implicitly, which -fsanitize=integer reports for bytes of
// 0x80 and above
const auto same_bytes = [](const std::vector<std::uint8_t>& bytes, const std::string & text)
{
return bytes.size() == text.size() && std::equal(bytes.begin(), bytes.end(), text.begin(), [](std::uint8_t byte, char c)
{
return byte == static_cast<std::uint8_t>(c);
});
};
for (const auto& j : values)
{
CAPTURE(j.dump());
for (const bool use_size :
{
false, true
})
{
for (const bool use_type :
{
false, true
})
{
if (use_type && !use_size)
{
continue;
}
CAPTURE(use_size);
CAPTURE(use_type);
const auto bjdata = json::to_bjdata(j, use_size, use_type);
std::string bjdata_string;
json::to_bjdata(j, bjdata_string, use_size, use_type);
CHECK(same_bytes(bjdata, bjdata_string));
const auto ubjson = json::to_ubjson(j, use_size, use_type);
std::string ubjson_string;
json::to_ubjson(j, ubjson_string, use_size, use_type);
CHECK(same_bytes(ubjson, ubjson_string));
}
}
}
}
TEST_CASE("BJData use_type requires use_size")
{
SECTION("non-empty object throws other_error.502")
@@ -4367,17 +4265,6 @@ TEST_CASE("BJData use_type requires use_size")
json::other_error&);
}
SECTION("non-empty binary value throws other_error.502")
{
const json j = json::binary({1, 2, 3});
CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true),
"[json.exception.other_error.502] use_type requires use_size = true",
json::other_error&);
CHECK_THROWS_WITH_AS(json::to_ubjson(j, false, true),
"[json.exception.other_error.502] use_type requires use_size = true",
json::other_error&);
}
SECTION("scalars do not throw with use_type=true, use_count=false")
{
CHECK_NOTHROW(json::to_bjdata(42, false, true));
@@ -4541,27 +4428,45 @@ 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;
-38
View File
@@ -1244,44 +1244,6 @@ TEST_CASE("BSON nesting does not consume the call stack")
}
}
TEST_CASE("BSON input that cannot be read is discarded by every overload")
{
std::vector<std::uint8_t> input = json::to_bson(json({{"a", {1, 2}}}));
input.pop_back();
json _;
CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_bson(input, true, false).is_discarded());
CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded());
CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_bson({input.data(), input.size()}, true, false).is_discarded());
}
TEST_CASE("BSON SAX parsing stops at every event")
{
// Containers are opened and closed by the loop that reads them; a SAX
// handler that rejects any event - including the end of a nested
// container - must stop the parse right there.
const auto count_events = [](const std::vector<std::uint8_t>& input)
{
int events = 0;
while (true)
{
SaxCountdown scp(events);
if (json::sax_parse(input, &scp, json::input_format_t::bson))
{
return events;
}
++events;
REQUIRE(events < 1000);
}
};
// 20 events: every container kind closes inside another one
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
CHECK(count_events(json::to_bson(j)) == 20);
}
TEST_CASE("BSON numerical data")
{
SECTION("number")
+34 -140
View File
@@ -15,7 +15,6 @@ using nlohmann::json;
#include <sstream>
#include <iomanip>
#include <limits>
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
@@ -291,7 +290,7 @@ TEST_CASE("CBOR")
SECTION("-65536..-257")
{
for (int32_t i = -65536; i <= -257; i = utils::next_integer_sample(i, -257, 7))
for (int32_t i = -65536; i <= -257; ++i)
{
CAPTURE(i)
@@ -479,7 +478,7 @@ TEST_CASE("CBOR")
SECTION("256..65535")
{
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
for (size_t i = 256; i <= 65535; ++i)
{
CAPTURE(i)
@@ -614,7 +613,7 @@ TEST_CASE("CBOR")
SECTION("-32768..-129 (int 16)")
{
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)))
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
{
CAPTURE(i)
@@ -719,7 +718,7 @@ TEST_CASE("CBOR")
SECTION("256..65535 (two-byte uint16_t)")
{
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
for (size_t i = 256; i <= 65535; ++i)
{
CAPTURE(i)
@@ -2124,20 +2123,6 @@ 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);
@@ -2190,52 +2175,6 @@ TEST_CASE("CBOR nesting does not consume the call stack")
}
}
TEST_CASE("CBOR input that cannot be read is discarded by every overload")
{
std::vector<std::uint8_t> input = json::to_cbor(json({{"a", {1, 2}}}));
input.pop_back();
json _;
CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_cbor(input, true, false).is_discarded());
CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded());
CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded());
// a string that ends early, read through iterators that are not
// contiguous and have to be copied from one element at a time
const std::list<std::uint8_t> truncated_string = {0x63, 'a', 'b'};
CHECK(json::from_cbor(truncated_string.begin(), truncated_string.end(), true, false).is_discarded());
const std::list<std::uint8_t> complete_string = {0x63, 'a', 'b', 'c'};
CHECK(json::from_cbor(complete_string.begin(), complete_string.end()) == "abc");
}
TEST_CASE("CBOR SAX parsing stops at every event")
{
// Containers are opened and closed by the loop that reads them; a SAX
// handler that rejects any event - including the end of a nested
// container - must stop the parse right there.
const auto count_events = [](const std::vector<std::uint8_t>& input)
{
int events = 0;
while (true)
{
SaxCountdown scp(events);
if (json::sax_parse(input, &scp, json::input_format_t::cbor))
{
return events;
}
++events;
REQUIRE(events < 1000);
}
};
// 20 events: every container kind closes inside another one
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
CHECK(count_events(json::to_cbor(j)) == 20);
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
}
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
{
// Reading an indefinite-length string or byte array used to call itself
@@ -2543,34 +2482,60 @@ 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)
{
{
@@ -3068,77 +3033,6 @@ 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")
-7
View File
@@ -43,13 +43,6 @@ TEST_CASE("const_iterator class")
json::const_iterator const it(&j);
json::const_iterator it2(&j);
it2 = it;
// assigning an iterator to itself leaves it unchanged
json const a = {1, 2, 3};
json::const_iterator it3 = a.cbegin() + 1;
const json::const_iterator& same = it3;
it3 = same;
CHECK(*it3 == 2);
}
SECTION("copy constructor from non-const iterator")
-43
View File
@@ -12,7 +12,6 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cfloat> // FLT_EVAL_METHOD
#include <cstdlib> // strtod
#include <sstream> // stringstream
#include <string> // string
@@ -658,45 +657,3 @@ TEST_CASE("lexer string fast path")
}
}
}
TEST_CASE("parse_float_fast declines what it cannot convert exactly")
{
// The lexer only hands well-formed numbers to parse_float_fast, so the
// malformed ones below can only be passed to it directly. Declining is
// always safe: the caller then falls back to a slower, exact conversion.
const auto fast = [](const std::string & s, double & out)
{
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
};
double out = 0;
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
// without true double precision, the fast path declines everything
CHECK_FALSE(fast("1.5", out));
#else
CHECK(fast("1.5", out));
CHECK(out == 1.5);
CHECK(fast("+2.5e1", out));
CHECK(out == 25.0);
CHECK(fast("-25E-1", out));
CHECK(out == -2.5);
CHECK(fast("1e", out));
CHECK(out == 1.0);
#endif
// not a number
CHECK_FALSE(fast("", out));
CHECK_FALSE(fast("-", out));
CHECK_FALSE(fast(".", out));
CHECK_FALSE(fast("1.2.3", out));
CHECK_FALSE(fast("1x", out));
CHECK_FALSE(fast("1e+", out));
CHECK_FALSE(fast("1e1x", out));
// numbers that are not represented exactly on the fast path
CHECK_FALSE(fast("12345678901234567890", out));
CHECK_FALSE(fast("1e10000", out));
CHECK_FALSE(fast("9007199254740993", out));
CHECK_FALSE(fast("1e23", out));
CHECK_FALSE(fast("1e-23", out));
}
-217
View File
@@ -15,13 +15,7 @@
#include "doctest_compatibility.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
@@ -365,15 +359,6 @@ TEST_CASE("lexicographical comparison operators")
CHECK(json(1) < json(1.5));
CHECK(json(1.5) < json(2));
CHECK(json(2) > json(1.5));
CHECK(json(-1) > json(-1.5));
CHECK(json(-1.5) < json(-1));
CHECK(json(-2) < json(-1.5));
// a float below the range of the integer type
CHECK(json(0) > json(-1e30));
CHECK(json(-1e30) < json(0));
CHECK(json(0u) > json(-0.5));
CHECK(json(-0.5) < json(0u));
// a NaN operand stays unordered against either integer kind
CHECK_FALSE(json(1) == json(nan));
@@ -750,205 +735,3 @@ 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
// stack, by code of their own; every relation is checked both at the top
// level and below that bound.
const auto deep = [](const json & j, const std::size_t depth)
{
json result = j;
for (std::size_t i = 0; i < depth; ++i)
{
result = json::array({std::move(result)});
}
return result;
};
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
{
CAPTURE(depth);
// objects with different keys
{
const json a = deep({{"a", 1}}, depth);
const json b = deep({{"b", 1}}, depth);
CHECK_FALSE(a == b);
CHECK(a != b);
CHECK(a < b);
CHECK(b > a);
CHECK_FALSE(b < a);
#if JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
#endif
}
// a container that is a prefix of the other one
{
// the one that runs out of elements first is the smaller one
const json shorter = deep({1}, depth);
const json longer = deep({1, 2}, depth);
CHECK(shorter < longer);
CHECK(longer > shorter);
CHECK_FALSE(longer < shorter);
CHECK_FALSE(shorter == longer);
const json smaller_object = deep({{"a", 1}}, depth);
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
CHECK(smaller_object < larger_object);
CHECK(larger_object > smaller_object);
CHECK_FALSE(smaller_object == larger_object);
#if JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
#endif
}
// elements that cannot be ordered
{
const double nan = std::numeric_limits<double>::quiet_NaN();
const json lhs = deep({nan, 1}, depth);
const json rhs = deep({nan, 2}, depth);
CHECK_FALSE(lhs == lhs);
CHECK_FALSE(rhs < lhs);
#if JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
// operator<=> stops there, as std::lexicographical_compare_three_way
// does, and operator< is derived from it
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
CHECK_FALSE(lhs < rhs);
#else
// operator< skips a pair of elements that cannot be ordered, as
// std::lexicographical_compare does, and the next pair decides
CHECK(lhs < rhs);
#endif
}
}
}
-10
View File
@@ -49,16 +49,6 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
CHECK(char_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
}
SECTION("a value is converted to the binary type if it is binary or an array")
{
const std::vector<char> chars{'\0', '\x01', '\x7F'};
CHECK(char_binary_json::binary(chars).get<std::vector<char>>() == chars);
CHECK(char_binary_json({0, 1, 127}).get<std::vector<char>>() == chars);
CHECK_THROWS_WITH_AS(char_binary_json(1).get<std::vector<char>>(),
"[json.exception.type_error.302] type must be binary or array, but is number",
char_binary_json::type_error&);
}
SECTION("the default binary type is unchanged")
{
CHECK(nlohmann::json::binary({0, 1, 255}, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
-35
View File
@@ -156,38 +156,3 @@ TEST_CASE("Better diagnostics with positions")
#endif
}
}
TEST_CASE("values read from a binary format have no positions")
{
// only the JSON lexer knows where a value started and ended
const json source = {{"a", {1, "x", json::binary({1})}}, {"b", {{"c", true}}}, {"d", nullptr}, {"e", 1.5}};
const std::vector<std::uint8_t> cbor = json::to_cbor(source);
const auto check_no_positions = [](const json & j)
{
CHECK(j.start_pos() == std::string::npos);
CHECK(j.end_pos() == std::string::npos);
CHECK(j.at("a").start_pos() == std::string::npos);
CHECK(j.at("a").at(1).end_pos() == std::string::npos);
CHECK(j.at("b").at("c").start_pos() == std::string::npos);
};
SECTION("DOM parser")
{
const json j = json::from_cbor(cbor);
CHECK(j == source);
check_no_positions(j);
}
SECTION("DOM parser with a callback")
{
json j;
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(cbor))> sdp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
{
return true;
});
CHECK(json::sax_parse(cbor, &sdp, json::input_format_t::cbor));
CHECK(j == source);
check_no_positions(j);
}
}
-10
View File
@@ -1517,16 +1517,6 @@ TEST_CASE_TEMPLATE("element access 2 (throwing tests)", Json, nlohmann::json, nl
CHECK(j.value("/not/existing"_json_pointer, Json({{"foo", "bar"}})) == Json({{"foo", "bar"}}));
CHECK(j.value("/not/existing"_json_pointer, Json({10, 100})) == Json({10, 100}));
// an array index that is out of range, too large to be
// represented, or "-", and a token below a scalar
CHECK(j.value("/array/3"_json_pointer, 2) == 2);
CHECK(j.value("/array/-"_json_pointer, 2) == 2);
CHECK(j.value("/array/99999999999999999999999999"_json_pointer, 2) == 2);
CHECK(j.value("/integer/0"_json_pointer, 2) == 2);
CHECK(j.value("/string/x"_json_pointer, 2) == 2);
CHECK(j.value("/null/x"_json_pointer, 2) == 2);
CHECK(j.value("/array/0"_json_pointer, 2) == 1);
CHECK(j_const.value("/not/existing"_json_pointer, 2) == 2);
CHECK(j_const.value("/not/existing"_json_pointer, 2u) == 2u);
CHECK(j_const.value("/not/existing"_json_pointer, false) == false);
-95
View File
@@ -1751,98 +1751,3 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
CHECK(source.patch(patch) == target);
}
}
TEST_CASE("JSON patch - every operation on ordered_json")
{
using nlohmann::ordered_json;
const ordered_json doc = {{"foo", "bar"}, {"arr", {1, 2, 3}}, {"obj", {{"a", 1}}}};
SECTION("successful operations")
{
const ordered_json patch = ordered_json::parse(R"([
{"op": "add", "path": "/obj/b", "value": 2},
{"op": "add", "path": "/arr/1", "value": 9},
{"op": "add", "path": "/arr/-", "value": 4},
{"op": "remove", "path": "/arr/0"},
{"op": "remove", "path": "/obj/a"},
{"op": "replace", "path": "/foo", "value": "baz"},
{"op": "move", "from": "/foo", "path": "/moved"},
{"op": "copy", "from": "/obj", "path": "/copied"},
{"op": "test", "path": "/copied/b", "value": 2}
])");
const ordered_json expected = ordered_json::parse(R"({
"arr": [9, 2, 3, 4], "obj": {"b": 2}, "moved": "baz", "copied": {"b": 2}
})");
CHECK(doc.patch(patch) == expected);
// adding to the root replaces the document
CHECK(doc.patch(ordered_json::parse(R"([{"op": "add", "path": "", "value": [1]}])")) == ordered_json({1}));
}
SECTION("failing operations")
{
ordered_json _;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
"[json.exception.out_of_range.401] (/arr) array index 4 is out of range", ordered_json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/arr/4", "value": 1}])")),
"[json.exception.out_of_range.401] array index 4 is out of range", ordered_json::out_of_range&);
#endif
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/nope/x", "value": 1}])")),
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/obj/nope"}])")),
"[json.exception.out_of_range.403] key 'nope' not found", ordered_json::out_of_range&);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
"[json.exception.out_of_range.401] (/arr) array index 3 is out of range", ordered_json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "remove", "path": "/arr/3"}])")),
"[json.exception.out_of_range.401] array index 3 is out of range", ordered_json::out_of_range&);
#endif
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
"[json.exception.other_error.501] (/0) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
#elif JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
"[json.exception.other_error.501] (bytes 1-47) unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
#else
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "test", "path": "/foo", "value": "qux"}])")),
"[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/foo\",\"value\":\"qux\"}", ordered_json::other_error&);
#endif
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
"[json.exception.parse_error.105] parse error: (/0) operation 'add' must have member 'value'", ordered_json::parse_error&);
#elif JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
"[json.exception.parse_error.105] parse error: (bytes 1-30) operation 'add' must have member 'value'", ordered_json::parse_error&);
#else
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "add", "path": "/foo"}])")),
"[json.exception.parse_error.105] parse error: operation 'add' must have member 'value'", ordered_json::parse_error&);
#endif
CHECK_THROWS_WITH_AS(_ = doc.patch(ordered_json::parse(R"([{"op": "move", "from": "/obj", "path": "/obj/a/b"}])")),
"[json.exception.out_of_range.414] cannot move value: 'from' path '/obj' is a proper prefix of 'path' '/obj/a/b'", ordered_json::out_of_range&);
}
SECTION("diff reproduces the target")
{
const ordered_json source = {{"a", 1}, {"b", 2}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}};
const std::vector<ordered_json> targets =
{
// a key removed, a key added, a nested change, a shorter array
{{"a", 1}, {"c", {{"x", 2}}}, {"l", {1}}, {"d", 4}},
// the same keys in another order
{{"c", {{"x", 1}}}, {"a", 1}, {"b", 2}, {"l", {1, 2, 3}}},
// new keys ahead of the common ones
{{"new", true}, {"a", 1}, {"b", 3}, {"c", {{"x", 1}}}, {"l", {1, 2, 3}}},
};
for (const auto& target : targets)
{
CAPTURE(target.dump());
CHECK(source.patch(ordered_json::diff(source, target)) == target);
}
}
}
-13
View File
@@ -872,16 +872,3 @@ TEST_CASE("JSON pointers")
}
#endif
}
TEST_CASE("unescaping keeps a '~' that does not start an escape sequence")
{
// the parser of a JSON pointer rejects such reference tokens before it
// unescapes them, so this is only reachable by calling unescape directly
std::string s = "a~2b~";
nlohmann::detail::unescape(s);
CHECK(s == "a~2b~");
s = "~0~1~";
nlohmann::detail::unescape(s);
CHECK(s == "~/~");
}
+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 = 500000;
const auto depth = 5000000;
std::string s(static_cast<std::size_t>(2 * depth), '[');
std::fill(s.begin() + depth, s.end(), ']');
-11
View File
@@ -158,17 +158,6 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
json::sax_parse("12.34", &sax);
CHECK(sax.float_string_copy == "12.34");
}
SECTION("serializing a long double")
{
// a floating-point type that is not a float or a double is written
// with snprintf, whose locale-specific decimal point and thousands
// separator are undone afterwards
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
CHECK(long_double_json(12345.5L).dump() == "12345.5");
CHECK(long_double_json(1.0L).dump() == "1.0");
CHECK(long_double_json(-0.25L).dump() == "-0.25");
}
}
else
{
-29
View File
@@ -345,32 +345,3 @@ TEST_CASE("JSON Merge Patch on deeply nested values")
CHECK(p->at("x") == 1);
}
}
TEST_CASE("JSON Merge Patch and update on ordered_json")
{
using nlohmann::ordered_json;
SECTION("merge_patch")
{
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": 2}, "d": 3, "e": [1]})");
target.merge_patch(ordered_json::parse(R"({"a": {"b": null, "f": 4}, "d": {"x": {"y": null}}, "e": null, "g": {"h": 5}})"));
CHECK(target == ordered_json::parse(R"({"a": {"c": 2, "f": 4}, "d": {"x": {}}, "g": {"h": 5}})"));
// a patch that is not an object replaces the target
target.merge_patch(ordered_json({1, 2}));
CHECK(target == ordered_json({1, 2}));
// an object patch turns a target that is not an object into one
target.merge_patch(ordered_json::parse(R"({"k": {"l": null}})"));
CHECK(target == ordered_json::parse(R"({"k": {}})"));
}
SECTION("update with merge_objects")
{
ordered_json target = ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2}}, "e": 3})");
target.update(ordered_json::parse(R"({"a": {"c": {"x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"), true);
CHECK(target == ordered_json::parse(R"({"a": {"b": 1, "c": {"d": 2, "x": 1}, "f": 4}, "e": {"y": 5}, "g": 6})"));
target.update(ordered_json::parse(R"({"a": 1})"), false);
CHECK(target == ordered_json::parse(R"({"a": 1, "e": {"y": 5}, "g": 6})"));
}
}
+33 -292
View File
@@ -14,7 +14,6 @@ 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>
@@ -256,7 +255,7 @@ TEST_CASE("MessagePack")
SECTION("256..65535 (int 16)")
{
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
for (size_t i = 256; i <= 65535; ++i)
{
CAPTURE(i)
@@ -441,7 +440,7 @@ TEST_CASE("MessagePack")
SECTION("-32768..-129 (int 16)")
{
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)))
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); ++i)
{
CAPTURE(i)
@@ -647,7 +646,7 @@ TEST_CASE("MessagePack")
SECTION("256..65535 (uint 16)")
{
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
for (size_t i = 256; i <= 65535; ++i)
{
CAPTURE(i)
@@ -1781,44 +1780,6 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
}
}
TEST_CASE("MessagePack input that cannot be read is discarded by every overload")
{
std::vector<std::uint8_t> input = json::to_msgpack(json({{"a", {1, 2}}}));
input.pop_back();
json _;
CHECK_THROWS_AS(_ = json::from_msgpack(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_msgpack(input, true, false).is_discarded());
CHECK(json::from_msgpack(input.begin(), input.end(), true, false).is_discarded());
CHECK(json::from_msgpack(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_msgpack({input.data(), input.size()}, true, false).is_discarded());
}
TEST_CASE("MessagePack SAX parsing stops at every event")
{
// Containers are opened and closed by the loop that reads them; a SAX
// handler that rejects any event - including the end of a nested
// container - must stop the parse right there.
const auto count_events = [](const std::vector<std::uint8_t>& input)
{
int events = 0;
while (true)
{
SaxCountdown scp(events);
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
{
return events;
}
++events;
REQUIRE(events < 1000);
}
};
// 20 events: every container kind closes inside another one
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
CHECK(count_events(json::to_msgpack(j)) == 20);
}
TEST_CASE("single MessagePack roundtrip")
{
SECTION("sample.json")
@@ -2043,34 +2004,60 @@ 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)
{
{
@@ -2163,249 +2150,3 @@ 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>
{
using base = std::vector<T, A>;
using base::base;
bool fake_size = false;
std::size_t size() const noexcept
{
if (fake_size)
{
return (std::numeric_limits<std::uint32_t>::max)() + 1ULL;
}
return base::size();
}
};
using huge_array_json = nlohmann::basic_json <
std::map, huge_array, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer,
std::vector<std::uint8_t>, void >;
TEST_CASE("MessagePack Size above uint32 for array")
{
huge_array_json j = huge_array_json::array();
j.push_back(1);
j.push_back(2);
j.push_back(3);
auto& array = j.get_ref<huge_array_json::array_t&>();
array.fake_size = true;
CHECK_THROWS_WITH_AS(
huge_array_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
array.fake_size = false;
}
template<typename K, typename V,
typename C = std::less<K>,
typename A = std::allocator<std::pair<const K, V>>>
struct huge_map : std::map<K, V, C, A>
{
using base = std::map<K, V, C, A>;
using base::base;
bool fake_size = false;
std::size_t size() const noexcept
{
if (fake_size)
{
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
}
return base::size();
}
};
using huge_object_json = nlohmann::basic_json <
huge_map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
void >;
TEST_CASE("MessagePack Size above uint32 for object")
{
huge_object_json j = huge_object_json::object();
j["one"] = 1;
j["two"] = 2;
auto& object = j.get_ref<huge_object_json::object_t&>();
object.fake_size = true;
CHECK_THROWS_WITH_AS(
huge_object_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
object.fake_size = false;
}
struct huge_string : std::string
{
using std::string::string;
std::size_t size() const noexcept
{
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
}
};
using huge_string_json = nlohmann::basic_json <
std::map,
std::vector,
huge_string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
void >;
TEST_CASE("MessagePack Size above uint32 for string")
{
huge_string_json j = "hello";
CHECK_THROWS_WITH_AS(
huge_string_json::to_msgpack(j),
"[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295",
json::out_of_range&);
}
struct huge_binary : std::vector<std::uint8_t>
{
using std::vector<std::uint8_t>::vector;
std::size_t size() const noexcept
{
return static_cast<std::size_t>(UINT32_MAX) + 1ULL;
}
};
using huge_binary_json = nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
huge_binary,
void >;
TEST_CASE("MessagePack Size above uint32 for binary")
{
huge_binary_json j = huge_binary_json::binary(huge_binary{});
j.get_binary().push_back(0x01);
j.get_binary().push_back(0x02);
CHECK_THROWS_WITH_AS(
huge_binary_json::to_msgpack(j),
"[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
}
-160
View File
@@ -639,163 +639,3 @@ TEST_CASE("serialization of deeply nested values")
}
}
}
namespace
{
// wraps @a inner into @a depth single-element arrays
json wrap_in_arrays(const json& inner, const std::size_t depth)
{
json j = inner;
for (std::size_t i = 0; i < depth; ++i)
{
j = json::array({std::move(j)});
}
return j;
}
// what wrap_in_arrays(inner, depth).dump(2) is expected to be: the arrays
// around inner.dump(2), with inner's own lines indented by the depth
std::string expected_pretty_in_arrays(const json& inner, const std::size_t depth)
{
std::string expected;
for (std::size_t i = 0; i < depth; ++i)
{
expected += std::string(2 * i, ' ') + "[\n";
}
const std::string indent(2 * depth, ' ');
expected += indent;
for (const char c : inner.dump(2))
{
expected += c;
if (c == '\n')
{
expected += indent;
}
}
for (std::size_t i = depth; i > 0; --i)
{
expected += '\n' + std::string(2 * (i - 1), ' ') + ']';
}
return expected;
}
} // namespace
TEST_CASE("serialization of every kind of value below the bound of the descent")
{
// Values nested deeper than the bound are written without the call stack,
// by code of their own; each kind of value must come out the same there as
// it does at the top level, compact and pretty-printed.
std::vector<json> values =
{
json::parse(R"({"a": 1, "b": [1, 2, {"c": "x"}], "d": {}, "e": []})"),
json::parse(R"([1, [2, 3], {"k": null}, "s"])"),
json::object(),
json::array(),
json::binary({1, 2, 3}, 42),
json::binary({1, 2, 3}),
json::binary({}, 7),
json::binary({}),
"a string with \"escapes\"\n",
true,
false,
-42,
42u,
1.5,
nullptr,
json(json::value_t::discarded),
};
// a pretty-printed object whose members are themselves deep
values.push_back({{"x", wrap_in_arrays(1, 5)}, {"y", {{"z", 2}}}});
for (const std::size_t depth : std::vector<std::size_t> {1, 200})
{
CAPTURE(depth);
for (const auto& inner : values)
{
CAPTURE(inner.dump());
const json j = wrap_in_arrays(inner, depth);
CHECK(j.dump() == std::string(depth, '[') + inner.dump() + std::string(depth, ']'));
CHECK(j.dump(2) == expected_pretty_in_arrays(inner, depth));
}
}
SECTION("pretty-printed objects across the bound")
{
for (std::size_t d = 120; d <= 140; ++d)
{
CAPTURE(d);
// built from the inside out: {"k": <level below>, "n": <level>}
json j = 7;
std::string expected = "7";
for (std::size_t i = d; i > 0; --i)
{
j = json({{"k", std::move(j)}, {"n", i}});
const std::string indent(2 * i, ' ');
const std::string outer_indent(2 * (i - 1), ' ');
std::string next = "{\n";
next += indent;
next += "\"k\": ";
next += expected;
next += ",\n";
next += indent;
next += "\"n\": ";
next += std::to_string(i);
next += '\n';
next += outer_indent;
next += '}';
expected = std::move(next);
}
CHECK(j.dump(2) == expected);
CHECK(json::parse(j.dump(2)) == j);
CHECK(json::parse(j.dump()) == j);
}
}
}
TEST_CASE("serializer buffers are flushed mid-string and mid-binary")
{
SECTION("a long run of escaped characters")
{
// each character is escaped on its own, so the escape buffer fills up
const json newlines = std::string(600, '\n');
std::string expected = "\"";
for (int i = 0; i < 600; ++i)
{
expected += "\\n";
}
expected += '"';
CHECK(newlines.dump() == expected);
// every character is \u-escaped under ensure_ascii
std::string umlauts;
std::string escaped_umlauts = "\"";
for (int i = 0; i < 300; ++i)
{
umlauts += "\xC3\xA4";
escaped_umlauts += "\\u00e4";
}
escaped_umlauts += '"';
CHECK(json(umlauts).dump(-1, ' ', true) == escaped_umlauts);
}
SECTION("a large binary value")
{
std::vector<std::uint8_t> bytes(3000);
std::string expected_bytes;
std::string expected_pretty_bytes;
for (std::size_t i = 0; i < bytes.size(); ++i)
{
bytes[i] = static_cast<std::uint8_t>(i % 256);
expected_bytes += (i == 0 ? "" : ",") + std::to_string(i % 256);
expected_pretty_bytes += (i == 0 ? "" : ", ") + std::to_string(i % 256);
}
const json j = json::binary(bytes);
CHECK(j.dump() == "{\"bytes\":[" + expected_bytes + "],\"subtype\":null}");
CHECK(j.dump(2) == "{\n \"bytes\": [" + expected_pretty_bytes + "],\n \"subtype\": null\n}");
}
}
-23
View File
@@ -102,29 +102,6 @@ TEST_CASE("std::formatter<nlohmann::json>")
CHECK_THROWS_AS(std::vformat("{:{}}", std::make_format_args(j, dynamic_width)), std::format_error); // dynamic width
}
SECTION("a format spec may run to the end of the parse context")
{
// std::format always hands parse() a range that still holds the closing
// '}', but a parse context may also end right after the spec
const auto parse = [](const char* spec)
{
std::format_parse_context ctx(spec);
std::formatter<json> f;
CHECK(f.parse(ctx) == ctx.end());
return f;
};
CHECK(parse("").indent == -1);
CHECK(parse(">").indent == -1);
CHECK(parse("#").indent == 4);
CHECK(parse("3").indent == 3);
CHECK(parse("#12").indent == 12);
const auto f = parse(".>");
CHECK(f.indent == -1);
CHECK(f.indent_char == '.');
}
SECTION("std::format_to writes through an arbitrary output iterator")
{
const json j = {{"foo", 1}, {"bar", {1, 2, 3}}};
+33 -85
View File
@@ -265,7 +265,7 @@ TEST_CASE("UBJSON")
SECTION("-32768..-129 (int16)")
{
for (int32_t i = -32768; i <= -129; i = utils::next_integer_sample(i, -129, 7))
for (int32_t i = -32768; i <= -129; ++i)
{
CAPTURE(i)
@@ -425,7 +425,7 @@ TEST_CASE("UBJSON")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
for (size_t i = 256; i <= 32767; ++i)
{
CAPTURE(i)
@@ -631,7 +631,7 @@ TEST_CASE("UBJSON")
SECTION("256..32767 (int16)")
{
for (size_t i = 256; i <= 32767; i = utils::next_integer_sample(i, static_cast<size_t>(32767), static_cast<size_t>(7)))
for (size_t i = 256; i <= 32767; ++i)
{
CAPTURE(i)
@@ -1640,29 +1640,6 @@ TEST_CASE("UBJSON")
});
CHECK_THROWS_AS(_ = json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson), json::out_of_range&);
}
SECTION("array with a known size, read with a callback")
{
// a sized array announces its length to start_array()
std::vector<uint8_t> const v_ubjson = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
json j;
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp(j, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
{
return true;
});
CHECK(json::sax_parse(v_ubjson, &scp, json::input_format_t::ubjson));
CHECK(j == json({1, 2}));
// the readers reject a size this large before they announce
// it, so it can only reach start_array() directly (the largest
// value stands for an unknown size and is never checked)
json k;
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp2(k, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
{
return true;
});
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
}
}
}
@@ -2278,46 +2255,6 @@ TEST_CASE("UBJSON nesting does not consume the call stack")
}
}
TEST_CASE("UBJSON input that cannot be read is discarded by every overload")
{
std::vector<std::uint8_t> input = json::to_ubjson(json({{"a", {1, 2}}}));
input.pop_back();
json _;
CHECK_THROWS_AS(_ = json::from_ubjson(input.begin(), input.end()), json::parse_error&);
CHECK(json::from_ubjson(input, true, false).is_discarded());
CHECK(json::from_ubjson(input.begin(), input.end(), true, false).is_discarded());
CHECK(json::from_ubjson(input.data(), input.size(), true, false).is_discarded());
CHECK(json::from_ubjson({input.data(), input.size()}, true, false).is_discarded());
}
TEST_CASE("UBJSON SAX parsing stops at every event")
{
// Containers are opened and closed by the loop that reads them; a SAX
// handler that rejects any event - including the end of a nested
// container - must stop the parse right there.
const auto count_events = [](const std::vector<std::uint8_t>& input)
{
int events = 0;
while (true)
{
SaxCountdown scp(events);
if (json::sax_parse(input, &scp, json::input_format_t::ubjson))
{
return events;
}
++events;
REQUIRE(events < 1000);
}
};
// 20 events: every container kind closes inside another one
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
CHECK(count_events(json::to_ubjson(j)) == 20);
CHECK(count_events(json::to_ubjson(j, true)) == 20);
CHECK(count_events(json::to_ubjson(j, true, true)) == 20);
}
TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
{
// An element of type 'Z', 'T' or 'F' is encoded by its marker alone, so an
@@ -2980,34 +2917,60 @@ 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;
@@ -3018,18 +2981,3 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
}
}
}
TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
{
// UBJSON has no unsigned 64-bit type, so such values are written as
// high-precision numbers - also as the type of an optimized container
const json j = {18446744073709551615ULL, 9223372036854775808ULL};
const std::vector<std::uint8_t> expected =
{
'[', '$', 'H', '#', 'i', 2,
'i', 20, '1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', '5',
'i', 19, '9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5', '4', '7', '7', '5', '8', '0', '8'
};
CHECK(json::to_ubjson(j, true, true) == expected);
CHECK(json::from_ubjson(expected) == j);
}
-4
View File
@@ -70,8 +70,6 @@ TEST_CASE("wide strings")
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), static_cast<wchar_t>(0xE000), L'"'}), error_high_surrogate, json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
@@ -101,8 +99,6 @@ TEST_CASE("wide strings")
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// ... also when the unit is above the low surrogates
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, 0xE000, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
@@ -36,31 +36,5 @@
</Expand>
</Type>
<!-- Fallback for when the basic_json entry above does not match: json_default_base is the (empty) default
base class of basic_json, and base class visualizers are inherited by derived types and evaluated
against the derived object, so m_data is accessible here. The class lives in {{ ns }} after
3.12.0 (#5238) and in {{ ns }}::detail up to 3.12.0, so both names are listed. -->
{% for default_base in ['json_default_base', 'detail::json_default_base'] %}
<Type Name="{{ ns }}::{{ default_base }}">
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
{% endfor %}
{% endfor %}
</AutoVisualizer>