mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 14:40:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
decbb6e758 |
@@ -17,11 +17,11 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
macos-14:
|
||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
macos-26:
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['26.4.1', '26.5', '26.6']
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -68,8 +68,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to BJData"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -121,6 +119,4 @@ Linear in the size of the JSON value `j`.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
@@ -58,9 +58,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
|
||||
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
|
||||
`"cannot serialize discarded value to BSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -113,8 +110,6 @@ pass before anything is written.
|
||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
|
||||
skipped, producing a document whose declared size did not match what was actually written.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
|
||||
@@ -49,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to CBOR"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -88,5 +86,3 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
|
||||
@@ -54,8 +54,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict`
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to MessagePack"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -110,5 +108,3 @@ Linear in the size of the JSON value `j`.
|
||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
|
||||
@@ -61,8 +61,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
discarded; example: `"cannot serialize discarded value to UBJSON"`
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -114,5 +112,3 @@ Linear in the size of the JSON value `j`.
|
||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid UBJSON.
|
||||
|
||||
@@ -21,18 +21,17 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
|
||||
| Compiler | Architecture | Operating System | CI |
|
||||
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
||||
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
@@ -90,7 +89,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||
|
||||
@@ -804,20 +804,6 @@ does not list an enumerator and it is therefore converted like the first listed
|
||||
[json.exception.type_error.318] duplicate object key 'red'
|
||||
```
|
||||
|
||||
### json.exception.type_error.321
|
||||
|
||||
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
||||
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
|
||||
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
|
||||
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
|
||||
```
|
||||
[json.exception.type_error.321] cannot serialize discarded value to CBOR
|
||||
```
|
||||
|
||||
## Out of range
|
||||
|
||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||
|
||||
@@ -2021,39 +2021,6 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -2133,6 +2100,7 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -423,7 +423,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
if (get_token() == token_type::value_separator)
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -463,7 +463,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
if (get_token() == token_type::value_separator)
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -484,7 +484,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -528,13 +528,6 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
@@ -127,7 +127,6 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -159,7 +158,6 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -324,7 +322,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "CBOR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +382,6 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -658,7 +655,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +668,6 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -905,7 +901,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,15 +921,6 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -1185,7 +1172,6 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -1212,12 +1198,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -1254,12 +1238,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -1326,8 +1308,6 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
|
||||
+74
-211
@@ -612,239 +612,100 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
// container in any order, as long as it picks the same child again
|
||||
// while that container is not modified in between. Arrays and
|
||||
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||
// use their last child, which a vector-based container can remove
|
||||
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||
// std::unordered_map), so other objects use their first child.
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||
{
|
||||
return std::prev(o.end());
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||
{
|
||||
return o.begin();
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||
{
|
||||
JSON_ASSERT(!o.empty());
|
||||
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||
}
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// destroy_container() continues with (see walk_child_it() above)
|
||||
static basic_json& walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// removes walk_child(v) from a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
void destroy(value_t t)
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr)
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down a chain of children (always the one
|
||||
// walk_child() picks), reversing links as we go: cur is the
|
||||
// container currently being emptied, and prev is its parent
|
||||
// (value_t::null when there is none). Each parent's
|
||||
// walk_child() slot doubles as storage for that parent's own
|
||||
// parent link while we are below it, so no extra memory is
|
||||
// needed; the parent is not modified meanwhile, so
|
||||
// walk_child() finds that same slot again on the way up. We
|
||||
// only ever remove a child once it is a scalar or an empty
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
{
|
||||
if (has_no_children(cur))
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
{
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
{
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
}
|
||||
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
}
|
||||
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_walk_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::string:
|
||||
destroy_string();
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -853,7 +714,9 @@ public:
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+22572
File diff suppressed because it is too large
Load Diff
@@ -12313,39 +12313,6 @@ scan_number_done:
|
||||
// read the next character and ignore whitespace
|
||||
skip_whitespace();
|
||||
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan the next token when the caller expects a separator (':' or
|
||||
',') most of the time
|
||||
|
||||
After an object key the next token is almost always ':', after a value
|
||||
inside an object or array almost always ','. Testing for that character
|
||||
first is a compare and a well-predicted branch, where the switch in
|
||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||
goes through the switch, so the result is the same as scan()'s.
|
||||
|
||||
May only be called after scan() has run once (the BOM check is skipped).
|
||||
*/
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
{
|
||||
return expected_type;
|
||||
}
|
||||
return scan_after_whitespace();
|
||||
}
|
||||
|
||||
private:
|
||||
/// the part of scan() after the leading whitespace: skip comments and
|
||||
/// scan the token that starts with current
|
||||
token_type scan_after_whitespace()
|
||||
{
|
||||
// ignore comments
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -12425,6 +12392,7 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
@@ -18454,7 +18422,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18617,7 +18585,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// or end of array (ignore_trailing_commas = true)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
if (get_token() == token_type::value_separator)
|
||||
{
|
||||
// parse a new value
|
||||
get_token();
|
||||
@@ -18657,7 +18625,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// or end of object (ignore_trailing_commas = true)
|
||||
if (get_token_expecting(token_type::value_separator))
|
||||
if (get_token() == token_type::value_separator)
|
||||
{
|
||||
get_token();
|
||||
|
||||
@@ -18678,7 +18646,7 @@ class parser
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -18722,13 +18690,6 @@ class parser
|
||||
return last_token = m_lexer.scan();
|
||||
}
|
||||
|
||||
/// get next token from lexer; true if it is the separator @a expected_type
|
||||
/// (name_separator or value_separator), which it usually is
|
||||
bool get_token_expecting(token_type expected_type)
|
||||
{
|
||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
@@ -21626,7 +21587,6 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.317 if @a j is not an object
|
||||
@throw type_error.321 if a value nested in @a j is discarded
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21658,7 +21618,6 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21823,7 +21782,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "CBOR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21883,7 +21842,6 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22157,7 +22115,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22170,7 +22128,6 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -22404,7 +22361,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22424,15 +22381,6 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -22684,7 +22632,6 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22711,12 +22658,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -22753,12 +22698,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -22825,8 +22768,6 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@throw type_error.321 if a value nested in @a document is discarded,
|
||||
before anything is written
|
||||
*/
|
||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
@@ -27851,239 +27792,100 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The walk in destroy_container() may take the children of a
|
||||
// container in any order, as long as it picks the same child again
|
||||
// while that container is not modified in between. Arrays and
|
||||
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||
// use their last child, which a vector-based container can remove
|
||||
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||
// std::unordered_map), so other objects use their first child.
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||
{
|
||||
return std::prev(o.end());
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||
{
|
||||
return o.begin();
|
||||
}
|
||||
|
||||
template<typename ObjectType_>
|
||||
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||
{
|
||||
JSON_ASSERT(!o.empty());
|
||||
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||
}
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// destroy_container() continues with (see walk_child_it() above)
|
||||
static basic_json& walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// removes walk_child(v) from a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
void destroy(value_t t)
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr)
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down a chain of children (always the one
|
||||
// walk_child() picks), reversing links as we go: cur is the
|
||||
// container currently being emptied, and prev is its parent
|
||||
// (value_t::null when there is none). Each parent's
|
||||
// walk_child() slot doubles as storage for that parent's own
|
||||
// parent link while we are below it, so no extra memory is
|
||||
// needed; the parent is not modified meanwhile, so
|
||||
// walk_child() finds that same slot again on the way up. We
|
||||
// only ever remove a child once it is a scalar or an empty
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
{
|
||||
if (has_no_children(cur))
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
{
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
{
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
}
|
||||
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
}
|
||||
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_walk_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::string:
|
||||
destroy_string();
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -28092,7 +27894,9 @@ public:
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -607,88 +607,3 @@ TEST_CASE("bad my_allocator::construct")
|
||||
j["test"].push_back("should not leak");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t counting_allocator_allocations = 0;
|
||||
std::size_t counting_allocator_deallocations = 0;
|
||||
|
||||
template<class T>
|
||||
struct counting_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
++counting_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++counting_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = counting_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("destructor performs no allocation, only deallocation")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/issues/4842 and
|
||||
// https://github.com/nlohmann/json/issues/5135: destroying nested
|
||||
// arrays/objects used to allocate a temporary stack (first with
|
||||
// std::allocator, later - after #4842 - with the provided allocator).
|
||||
// Since that stack could itself throw bad_alloc from inside the
|
||||
// noexcept destructor (#5135), destroy() no longer allocates anything:
|
||||
// it only ever frees what is already there.
|
||||
using counting_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
counting_allocator>;
|
||||
|
||||
SECTION("array")
|
||||
{
|
||||
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("object")
|
||||
{
|
||||
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
|
||||
SECTION("mixed tree of empty/non-empty arrays and objects")
|
||||
{
|
||||
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
|
||||
{
|
||||
{"empty_obj", counting_json::object()},
|
||||
{"empty_arr", counting_json::array()},
|
||||
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
|
||||
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
|
||||
});
|
||||
const auto allocations_before = counting_allocator_allocations;
|
||||
const auto deallocations_before = counting_allocator_deallocations;
|
||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
CHECK(counting_allocator_allocations == allocations_before);
|
||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,59 +114,10 @@ TEST_CASE("BJData")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to BJData
|
||||
// discarded values are not serialized
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
const auto result = json::to_bjdata(j);
|
||||
CHECK(result.empty());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -149,54 +149,6 @@ TEST_CASE("BSON")
|
||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container cannot be serialized to BSON")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("in an array that is an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = json::array({1, discarded, 2});
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in object)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json j;
|
||||
j["outer"] = middle_object;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||
|
||||
+3
-42
@@ -38,49 +38,10 @@ TEST_CASE("CBOR")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to CBOR
|
||||
// discarded values are not serialized
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
const auto result = json::to_cbor(j);
|
||||
CHECK(result.empty());
|
||||
}
|
||||
|
||||
SECTION("NaN")
|
||||
|
||||
@@ -2317,58 +2317,6 @@ TEST_CASE("parser class")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("comments before separators")
|
||||
{
|
||||
// The parser first checks for the expected ':' or ',' and only then
|
||||
// falls back to the full token switch, which skips comments. A comment
|
||||
// directly before a separator takes that fallback.
|
||||
json _;
|
||||
|
||||
SECTION("ignored")
|
||||
{
|
||||
const std::vector<std::pair<std::string, json>> inputs =
|
||||
{
|
||||
{"{\"a\" /* c */ : 1}", {{"a", 1}}},
|
||||
{"{\"a\" // c\n: 1}", {{"a", 1}}},
|
||||
{R"({"a": 1, "b" /* c */ : 2})", {{"a", 1}, {"b", 2}}},
|
||||
{R"({"a": 1 /* c */ , "b": 2})", {{"a", 1}, {"b", 2}}},
|
||||
{"{\"a\": 1 // c\n, \"b\": 2}", {{"a", 1}, {"b", 2}}},
|
||||
{"[1 /* c */ , 2]", {1, 2}},
|
||||
{"[1 // c\n, 2]", {1, 2}},
|
||||
{"{\"a\" /* c */ /* d */ : [1 // c\n , 2 /**/ ] /**/ , \"b\" : 3}", {{"a", {1, 2}}, {"b", 3}}}
|
||||
};
|
||||
for (const auto& input : inputs)
|
||||
{
|
||||
CAPTURE(input.first)
|
||||
CHECK(json::parse(input.first, nullptr, true, true) == input.second);
|
||||
CHECK(json::accept(input.first, true));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ignored, with trailing commas")
|
||||
{
|
||||
CHECK(json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true, true) == json({1}));
|
||||
CHECK(json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true, true) == json({{"a", 1}}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error);
|
||||
}
|
||||
|
||||
SECTION("not ignored")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\" /* c */ : 1}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing object separator - invalid literal; last read: '\"a\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1, \"b\" /* c */ : 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing object separator - invalid literal; last read: '\"b\" /'; expected ':'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , \"b\": 2}")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing object - invalid literal; last read: '1 /'; expected '}'", json::parse_error);
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , 2]")),
|
||||
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing array - invalid literal; last read: '1 /'; expected ']'", json::parse_error);
|
||||
CHECK(!json::accept(std::string("[1 /* c */ , 2]")));
|
||||
}
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Macro for all test cases for start_pos and end_pos
|
||||
#define SETUP_TESTCASES() \
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -198,198 +196,6 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
||||
|
||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||
|
||||
// wraps an iterator, but only offers the LegacyForwardIterator operations,
|
||||
// like the iterators of std::unordered_map and other hash maps
|
||||
template<class BaseIterator>
|
||||
class forward_only_iterator
|
||||
{
|
||||
BaseIterator m_it{};
|
||||
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<BaseIterator>::value_type;
|
||||
using difference_type = typename std::iterator_traits<BaseIterator>::difference_type;
|
||||
using pointer = typename std::iterator_traits<BaseIterator>::pointer;
|
||||
using reference = typename std::iterator_traits<BaseIterator>::reference;
|
||||
|
||||
forward_only_iterator() = default;
|
||||
explicit forward_only_iterator(BaseIterator it) : m_it(it) {}
|
||||
|
||||
BaseIterator base() const
|
||||
{
|
||||
return m_it;
|
||||
}
|
||||
|
||||
reference operator*() const
|
||||
{
|
||||
return *m_it;
|
||||
}
|
||||
pointer operator->() const
|
||||
{
|
||||
return &*m_it;
|
||||
}
|
||||
forward_only_iterator& operator++()
|
||||
{
|
||||
++m_it;
|
||||
return *this;
|
||||
}
|
||||
forward_only_iterator operator++(int)
|
||||
{
|
||||
auto result = *this;
|
||||
++m_it;
|
||||
return result;
|
||||
}
|
||||
|
||||
friend bool operator==(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||
{
|
||||
return lhs.m_it == rhs.m_it;
|
||||
}
|
||||
friend bool operator!=(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||
{
|
||||
return lhs.m_it != rhs.m_it;
|
||||
}
|
||||
};
|
||||
|
||||
// An ObjectType whose iterators are forward-only, as those of hash maps are;
|
||||
// it has no rbegin() and its iterators no operator--. A hash map is not used
|
||||
// directly for the same reason as in no_key_compare_map above.
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
class forward_only_map
|
||||
{
|
||||
using map_t = std::map<Key, T, Compare, Allocator>;
|
||||
map_t data;
|
||||
|
||||
public:
|
||||
using key_type = typename map_t::key_type;
|
||||
using mapped_type = typename map_t::mapped_type;
|
||||
using value_type = typename map_t::value_type;
|
||||
using size_type = typename map_t::size_type;
|
||||
using allocator_type = typename map_t::allocator_type;
|
||||
using iterator = forward_only_iterator<typename map_t::iterator>;
|
||||
using const_iterator = forward_only_iterator<typename map_t::const_iterator>;
|
||||
|
||||
forward_only_map() noexcept(std::is_nothrow_default_constructible<map_t>::value) : data() {}
|
||||
|
||||
template<class InputIt>
|
||||
forward_only_map(InputIt first, InputIt last) : data(first, last) {}
|
||||
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return iterator(data.begin());
|
||||
}
|
||||
iterator end() noexcept
|
||||
{
|
||||
return iterator(data.end());
|
||||
}
|
||||
const_iterator begin() const noexcept
|
||||
{
|
||||
return const_iterator(data.begin());
|
||||
}
|
||||
const_iterator end() const noexcept
|
||||
{
|
||||
return const_iterator(data.end());
|
||||
}
|
||||
const_iterator cbegin() const noexcept
|
||||
{
|
||||
return const_iterator(data.cbegin());
|
||||
}
|
||||
const_iterator cend() const noexcept
|
||||
{
|
||||
return const_iterator(data.cend());
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return data.empty();
|
||||
}
|
||||
size_type size() const noexcept
|
||||
{
|
||||
return data.size();
|
||||
}
|
||||
size_type max_size() const noexcept
|
||||
{
|
||||
return data.max_size();
|
||||
}
|
||||
void clear() noexcept
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
|
||||
iterator find(const key_type& key)
|
||||
{
|
||||
return iterator(data.find(key));
|
||||
}
|
||||
const_iterator find(const key_type& key) const
|
||||
{
|
||||
return const_iterator(data.find(key));
|
||||
}
|
||||
size_type count(const key_type& key) const
|
||||
{
|
||||
return data.count(key);
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
|
||||
{
|
||||
const auto result = data.emplace(key, value);
|
||||
return {iterator(result.first), result.second};
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const value_type& value)
|
||||
{
|
||||
const auto result = data.insert(value);
|
||||
return {iterator(result.first), result.second};
|
||||
}
|
||||
|
||||
template<class InputIt>
|
||||
void insert(InputIt first, InputIt last)
|
||||
{
|
||||
data.insert(first, last);
|
||||
}
|
||||
|
||||
mapped_type& operator[](const key_type& key)
|
||||
{
|
||||
return data[key];
|
||||
}
|
||||
|
||||
mapped_type& at(const key_type& key)
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
const mapped_type& at(const key_type& key) const
|
||||
{
|
||||
return data.at(key);
|
||||
}
|
||||
|
||||
iterator erase(iterator pos)
|
||||
{
|
||||
return iterator(data.erase(pos.base()));
|
||||
}
|
||||
iterator erase(iterator first, iterator last)
|
||||
{
|
||||
return iterator(data.erase(first.base(), last.base()));
|
||||
}
|
||||
size_type erase(const key_type& key)
|
||||
{
|
||||
return data.erase(key);
|
||||
}
|
||||
|
||||
void swap(forward_only_map& other) noexcept(noexcept(data.swap(other.data)))
|
||||
{
|
||||
data.swap(other.data);
|
||||
}
|
||||
|
||||
friend bool operator==(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||
{
|
||||
return lhs.data == rhs.data;
|
||||
}
|
||||
friend bool operator<(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||
{
|
||||
return lhs.data < rhs.data;
|
||||
}
|
||||
};
|
||||
|
||||
using forward_only_json = nlohmann::basic_json<forward_only_map>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("object type whose erase() returns void")
|
||||
@@ -516,46 +322,3 @@ TEST_CASE("object type without key_compare")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("object type with forward-only iterators")
|
||||
{
|
||||
CHECK(std::is_same<std::iterator_traits<forward_only_json::object_t::iterator>::iterator_category,
|
||||
std::forward_iterator_tag>::value);
|
||||
|
||||
SECTION("destroying nested objects and arrays")
|
||||
{
|
||||
forward_only_json j;
|
||||
j["a"] = 1;
|
||||
j["b"]["c"] = "x";
|
||||
j["b"]["d"] = forward_only_json::array();
|
||||
j["b"]["d"].push_back(forward_only_json::object());
|
||||
j["b"]["d"].push_back(true);
|
||||
j["b"]["e"]["f"]["g"] = nullptr;
|
||||
j["h"] = forward_only_json::object();
|
||||
j["i"]["j"] = 2;
|
||||
|
||||
CHECK(j.size() == 4);
|
||||
CHECK(j["b"].size() == 3);
|
||||
CHECK(j["b"]["d"].size() == 2);
|
||||
CHECK(j["b"]["e"]["f"]["g"].is_null());
|
||||
|
||||
CHECK(j.erase("b") == 1);
|
||||
CHECK(j.size() == 3);
|
||||
j = 42;
|
||||
CHECK(j == 42);
|
||||
}
|
||||
|
||||
SECTION("destroying a deeply nested object")
|
||||
{
|
||||
constexpr std::size_t depth = 100000;
|
||||
forward_only_json j;
|
||||
forward_only_json* cur = &j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
(*cur)["s"] = i;
|
||||
cur = &(*cur)["o"];
|
||||
}
|
||||
CHECK(j["o"]["o"]["s"] == 2);
|
||||
// destroyed at the end of scope without recursing per level
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,49 +41,10 @@ TEST_CASE("MessagePack")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to MessagePack
|
||||
// discarded values are not serialized
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
const auto result = json::to_msgpack(j);
|
||||
CHECK(result.empty());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
|
||||
@@ -40,9 +40,7 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <list>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -109,84 +107,6 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||
|
||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// An allocator whose allocate() can be told to fail on demand, so tests can
|
||||
// check that ~basic_json() tolerates - in fact, after #5135, never even
|
||||
// triggers - an allocation failure. This replaces an earlier version of
|
||||
// this test that overrode the process-wide ::operator new/::operator
|
||||
// delete, which affected every allocation in the whole unit-regression2
|
||||
// binary rather than just the values under test.
|
||||
std::size_t failing_allocator_allocations = 0;
|
||||
std::size_t failing_allocator_deallocations = 0;
|
||||
bool fail_next_allocation = false;
|
||||
|
||||
template<class T>
|
||||
struct failing_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
failing_allocator() noexcept = default;
|
||||
template<class U>
|
||||
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (fail_next_allocation)
|
||||
{
|
||||
fail_next_allocation = false;
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
++failing_allocator_allocations;
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
++failing_allocator_deallocations;
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = failing_allocator<U>;
|
||||
};
|
||||
};
|
||||
|
||||
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
|
||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||
|
||||
// builds `depth` levels of nesting around a scalar, iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value, which
|
||||
// is O(1)), each level an array or an object depending on `nest_objects`
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
|
||||
{
|
||||
BasicJsonType v = 0;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (nest_objects)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::object();
|
||||
wrapper["x"] = std::move(v);
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #1647
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -1020,211 +940,4 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||
}
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
|
||||
{
|
||||
// Before the fix, ~basic_json() flattened a nested array/object into a
|
||||
// heap-allocated std::vector to avoid recursing; that allocation could
|
||||
// itself throw bad_alloc, which escapes a noexcept destructor and
|
||||
// terminates the program. destroy() no longer allocates anything, so
|
||||
// none of the sections below ever observe fail_next_allocation being
|
||||
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
|
||||
|
||||
SECTION("the original report: a small, mixed array/object nest")
|
||||
{
|
||||
failing_allocator_allocations = 0;
|
||||
failing_allocator_deallocations = 0;
|
||||
{
|
||||
failing_json j = failing_json::array(
|
||||
{
|
||||
failing_json::array({1, 2}),
|
||||
failing_json::object({{"key", failing_json::array({3})}})
|
||||
});
|
||||
fail_next_allocation = true;
|
||||
} // j is destroyed here, with every further allocation set to fail
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_deallocations > 0);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested array")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, false);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json j = make_deep_nest<failing_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("100000-deep nested ordered_json")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
|
||||
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
|
||||
{
|
||||
std::size_t allocations_before = 0;
|
||||
{
|
||||
failing_json wide = failing_json::array();
|
||||
for (std::size_t i = 0; i < 1000; ++i)
|
||||
{
|
||||
failing_json inner = failing_json::array();
|
||||
for (std::size_t k = 0; k < 1000; ++k)
|
||||
{
|
||||
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
|
||||
}
|
||||
wide.push_back(std::move(inner));
|
||||
}
|
||||
|
||||
allocations_before = failing_allocator_allocations;
|
||||
fail_next_allocation = true;
|
||||
}
|
||||
|
||||
CHECK(fail_next_allocation);
|
||||
fail_next_allocation = false;
|
||||
CHECK(failing_allocator_allocations == allocations_before);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// a single-element chain of `depth` arrays, built iteratively (never
|
||||
// recursing: each wrap only moves the previous, already-built value)
|
||||
template<class BasicJsonType>
|
||||
BasicJsonType make_single_chain(std::size_t depth)
|
||||
{
|
||||
BasicJsonType v = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
BasicJsonType wrapper = BasicJsonType::array();
|
||||
wrapper.push_back(std::move(v));
|
||||
v = std::move(wrapper);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// copies value first, to make sure nothing was corrupted by building it,
|
||||
// then lets both the copy and the original destruct via normal scope exit
|
||||
template<class BasicJsonType>
|
||||
void check_destroy_edge_case(const BasicJsonType& value)
|
||||
{
|
||||
const BasicJsonType copy = value;
|
||||
CHECK(copy == value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
|
||||
{
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(BasicJsonType::object());
|
||||
root.push_back(BasicJsonType::array());
|
||||
root.push_back(BasicJsonType::object({{"k", 1}}));
|
||||
root.push_back(BasicJsonType::array({1, 2, 3}));
|
||||
root.push_back(nullptr);
|
||||
root.push_back(true);
|
||||
root.push_back(42);
|
||||
root.push_back(3.14);
|
||||
root.push_back("a string");
|
||||
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in first position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container child in last position only")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("container children in first and last position")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("single-element chain, 1000 levels deep")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty array")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("top-level empty object")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::object();
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("object whose last child is a non-empty array whose last child is an empty object")
|
||||
{
|
||||
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
|
||||
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
|
||||
check_destroy_edge_case(root);
|
||||
}
|
||||
|
||||
SECTION("destruction via erase() on a deeply nested child")
|
||||
{
|
||||
BasicJsonType root = BasicJsonType::array();
|
||||
root.push_back(make_single_chain<BasicJsonType>(500));
|
||||
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
|
||||
// erase() must destroy the removed subtree without recursing or
|
||||
// allocating beyond what erase() itself needs
|
||||
root.erase(0);
|
||||
CAPTURE(root.size())
|
||||
CHECK(root.size() == 1);
|
||||
}
|
||||
|
||||
SECTION("destruction via assignment on a deep tree")
|
||||
{
|
||||
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
|
||||
// assigning a new value destroys the old one in place
|
||||
root = nullptr;
|
||||
CHECK(root.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
|
||||
@@ -35,59 +35,10 @@ TEST_CASE("UBJSON")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to UBJSON
|
||||
// discarded values are not serialized
|
||||
json const j = json::value_t::discarded;
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("discarded values nested in a container")
|
||||
{
|
||||
json const discarded = json::value_t::discarded;
|
||||
|
||||
SECTION("in an array")
|
||||
{
|
||||
json const j = {1, discarded, 2};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("as an object value")
|
||||
{
|
||||
json j;
|
||||
j["a"] = 1;
|
||||
j["b"] = discarded;
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("nested deeper (array in object in array)")
|
||||
{
|
||||
json inner_array = {1, discarded};
|
||||
json middle_object;
|
||||
middle_object["x"] = inner_array;
|
||||
json const j = {middle_object};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("optimized array of all-discarded elements")
|
||||
{
|
||||
json const j = {discarded, discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
}
|
||||
const auto result = json::to_ubjson(j);
|
||||
CHECK(result.empty());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
@@ -2148,14 +2099,9 @@ TEST_CASE("UBJSON")
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
// a discarded value cannot be serialized to UBJSON, even as part
|
||||
// of an optimized array of a single (here: valueless) type
|
||||
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#else
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||
#endif
|
||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,5 +36,31 @@
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user