mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 14:40:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37316992ed | ||
|
|
5ecb704f6b | ||
|
|
0490778fc3 | ||
|
|
0a365865f9 | ||
|
|
5379e04ce4 |
No files matched your search
@@ -17,11 +17,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-14:
|
macos-15:
|
||||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
macos-15:
|
macos-26:
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
xcode: ['26.4.1', '26.5', '26.6']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# older GCC docker images (4, 5, 6) fail to check out code
|
||||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||||
container: gcc:${{ matrix.compiler }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ 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
|
- 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
|
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)
|
[`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
|
## Complexity
|
||||||
|
|
||||||
@@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`.
|
|||||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
- 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
|
- 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
|
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`.
|
[`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.
|
||||||
@@ -58,6 +58,9 @@ 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
|
- 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
|
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)
|
[`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
|
## Complexity
|
||||||
|
|
||||||
@@ -110,6 +113,8 @@ pass before anything is written.
|
|||||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
- 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.
|
- 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.
|
- `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
|
- 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
|
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
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ 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
|
- 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
|
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)
|
[`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
|
## Complexity
|
||||||
|
|
||||||
@@ -86,3 +88,5 @@ 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
|
- 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
|
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`.
|
[`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,6 +54,8 @@ 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)"`
|
`"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
|
- 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`
|
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
|
## Complexity
|
||||||
|
|
||||||
@@ -108,3 +110,5 @@ 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;
|
- 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
|
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||||
`number_unsigned_t`.
|
`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,6 +61,8 @@ 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
|
- 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
|
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)
|
[`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
|
## Complexity
|
||||||
|
|
||||||
@@ -112,3 +114,5 @@ 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
|
- 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
|
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`.
|
[`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,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
|
|
||||||
| Compiler | Architecture | Operating System | CI |
|
| 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 | 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.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | 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.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.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.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.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.5.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 |
|
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
@@ -89,7 +90,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 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 14.2.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 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.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 | arm64 | Ubuntu 24.04 | GitHub |
|
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | 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 |
|
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||||
|
|||||||
@@ -141,14 +141,8 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
parsed back as a regular array,
|
parsed back as a regular array,
|
||||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
||||||
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, a
|
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||||
value that fits the named width; for `double`, any value; for `single`, a value that survives narrowing to
|
`single` and `double`, an integer otherwise).
|
||||||
`float` and back without change (for instance, `0.1` does not, since it is not exactly representable as
|
|
||||||
`float`).
|
|
||||||
|
|
||||||
An annotated object is always read back with its keys in the order shown above, `"_ArrayType_"`, `"_ArraySize_"`,
|
|
||||||
`"_ArrayData_"`, regardless of the order the ND-array's header stores them in on the wire. This matters for
|
|
||||||
`ordered_json`, whose comparison takes key order into account.
|
|
||||||
|
|
||||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||||
array input to a BJData ND-array.
|
array input to a BJData ND-array.
|
||||||
|
|||||||
@@ -804,6 +804,20 @@ 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.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
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -2706,15 +2706,10 @@ class binary_reader
|
|||||||
is_ndarray can only return `true` when its initial value
|
is_ndarray can only return `true` when its initial value
|
||||||
is `false`
|
is `false`
|
||||||
@param[in] prefix type marker if already read, otherwise set to 0
|
@param[in] prefix type marker if already read, otherwise set to 0
|
||||||
@param[in] ndarray_dtype the element type marker of the enclosing bjdata ndarray if
|
|
||||||
already known (it precedes the dimension vector read here),
|
|
||||||
otherwise 0; used to emit the "_ArrayType_" annotation key
|
|
||||||
before "_ArraySize_" if a dimension vector turns out to
|
|
||||||
describe an ndarray
|
|
||||||
|
|
||||||
@return whether size determination completed
|
@return whether size determination completed
|
||||||
*/
|
*/
|
||||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0, char_int_type ndarray_dtype = 0)
|
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)
|
||||||
{
|
{
|
||||||
if (prefix == 0)
|
if (prefix == 0)
|
||||||
{
|
{
|
||||||
@@ -2827,34 +2822,8 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the element type precedes the dimension vector (see get_ubjson_size_type)
|
|
||||||
// and is passed down as ndarray_dtype; emit it here so the annotation keys
|
|
||||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
|
||||||
if (ndarray_dtype != 0)
|
|
||||||
{
|
|
||||||
const char* type_name = bjd_type_name(ndarray_dtype);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
|
||||||
{
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
|
||||||
exception_message("invalid byte: 0x" + last_token, "type"), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
string_t type_key = "_ArrayType_";
|
|
||||||
string_t type = type_name; // sax->string() takes a reference
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(type_key) || !sax->string(type)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string_t key = "_ArraySize_";
|
string_t key = "_ArraySize_";
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(dim.size())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2962,7 +2931,7 @@ class binary_reader
|
|||||||
exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
|
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||||
// an ndarray was read here only if the flag flipped; when it was
|
// an ndarray was read here only if the flag flipped; when it was
|
||||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||||
// dimension vector
|
// dimension vector
|
||||||
@@ -3155,17 +3124,27 @@ class binary_reader
|
|||||||
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||||
|
const char* type_name = bjd_type_name(size_and_type.second);
|
||||||
|
string_t key = "_ArrayType_";
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
||||||
|
{
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||||
|
exception_message("invalid byte: 0x" + last_token, "type"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
string_t type = type_name; // sax->string() takes a reference
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// the "_ArrayType_" and "_ArraySize_" annotation keys were already emitted by
|
|
||||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
|
||||||
// that determines size_and_type.first is read, so it is emitted first there to
|
|
||||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
|
||||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
size_and_type.second = 'U';
|
size_and_type.second = 'U';
|
||||||
}
|
}
|
||||||
|
|
||||||
string_t key = "_ArrayData_";
|
key = "_ArrayData_";
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2021,6 +2021,39 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_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
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -2100,7 +2133,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -423,7 +423,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -463,7 +463,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// or end of object (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -528,6 +528,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
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 exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ class binary_writer
|
|||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@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)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -158,6 +159,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -322,7 +324,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,6 +384,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@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)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -655,7 +658,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "MessagePack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,6 +671,7 @@ class binary_writer
|
|||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
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,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -901,7 +905,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,6 +925,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1172,6 +1185,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -1198,10 +1212,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -1238,10 +1254,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -1308,6 +1326,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
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)
|
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
@@ -1817,31 +1837,16 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||||
@return whether @a el's value survives narrowing to float and back without any change
|
@return whether @a el's value fits a float without overflow; always true when @a dry_run is false
|
||||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
|
||||||
@a dry_run is false
|
|
||||||
*/
|
*/
|
||||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||||
{
|
{
|
||||||
const auto dval = el.template get<double>();
|
const auto dval = el.template get<double>();
|
||||||
if (dry_run)
|
if (dry_run)
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
return !std::isfinite(dval) ||
|
||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||||
#endif
|
|
||||||
// a value that would be rounded (rather than exactly represented) by the
|
|
||||||
// narrowing to float is treated like an out-of-range integer element; this
|
|
||||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
|
||||||
const bool in_range = std::isnan(dval) ||
|
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
|
||||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
|
||||||
std::isinf(dval);
|
|
||||||
#ifdef __GNUC__
|
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
|
||||||
#endif
|
|
||||||
return in_range;
|
|
||||||
}
|
}
|
||||||
write_number(static_cast<float>(dval), true);
|
write_number(static_cast<float>(dval), true);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+210
-73
@@ -612,100 +612,239 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// constructor for rvalue binary arrays (internal type)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||||
|
|
||||||
void destroy(value_t t)
|
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
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(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)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
// flatten the current json_value to a heap-allocated stack
|
if (has_no_children(cur))
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
|
||||||
if (t == value_t::array)
|
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (prev.m_data.m_type == value_t::null)
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stack.reserve(object->size());
|
|
||||||
for (auto&& it : *object)
|
|
||||||
{
|
{
|
||||||
stack.push_back(std::move(it.second));
|
free_container(cur);
|
||||||
}
|
return; // back at the top with nothing left to do
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
// ascend: detach the grandparent link from prev's
|
||||||
// since it doesn't have any children
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
switch (t)
|
||||||
{
|
{
|
||||||
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:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -714,9 +853,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+301
-141
@@ -12313,6 +12313,39 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_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
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -12392,7 +12425,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
@@ -16423,15 +16455,10 @@ class binary_reader
|
|||||||
is_ndarray can only return `true` when its initial value
|
is_ndarray can only return `true` when its initial value
|
||||||
is `false`
|
is `false`
|
||||||
@param[in] prefix type marker if already read, otherwise set to 0
|
@param[in] prefix type marker if already read, otherwise set to 0
|
||||||
@param[in] ndarray_dtype the element type marker of the enclosing bjdata ndarray if
|
|
||||||
already known (it precedes the dimension vector read here),
|
|
||||||
otherwise 0; used to emit the "_ArrayType_" annotation key
|
|
||||||
before "_ArraySize_" if a dimension vector turns out to
|
|
||||||
describe an ndarray
|
|
||||||
|
|
||||||
@return whether size determination completed
|
@return whether size determination completed
|
||||||
*/
|
*/
|
||||||
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0, char_int_type ndarray_dtype = 0)
|
bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)
|
||||||
{
|
{
|
||||||
if (prefix == 0)
|
if (prefix == 0)
|
||||||
{
|
{
|
||||||
@@ -16544,34 +16571,8 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the element type precedes the dimension vector (see get_ubjson_size_type)
|
|
||||||
// and is passed down as ndarray_dtype; emit it here so the annotation keys
|
|
||||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
|
||||||
if (ndarray_dtype != 0)
|
|
||||||
{
|
|
||||||
const char* type_name = bjd_type_name(ndarray_dtype);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
|
||||||
{
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
|
||||||
exception_message("invalid byte: 0x" + last_token, "type"), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
string_t type_key = "_ArrayType_";
|
|
||||||
string_t type = type_name; // sax->string() takes a reference
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(type_key) || !sax->string(type)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string_t key = "_ArraySize_";
|
string_t key = "_ArraySize_";
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(dim.size())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -16679,7 +16680,7 @@ class binary_reader
|
|||||||
exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
exception_message(concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
|
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||||
// an ndarray was read here only if the flag flipped; when it was
|
// an ndarray was read here only if the flag flipped; when it was
|
||||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||||
// dimension vector
|
// dimension vector
|
||||||
@@ -16872,17 +16873,27 @@ class binary_reader
|
|||||||
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||||
|
const char* type_name = bjd_type_name(size_and_type.second);
|
||||||
|
string_t key = "_ArrayType_";
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
||||||
|
{
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||||
|
exception_message("invalid byte: 0x" + last_token, "type"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
string_t type = type_name; // sax->string() takes a reference
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// the "_ArrayType_" and "_ArraySize_" annotation keys were already emitted by
|
|
||||||
// get_ubjson_size_value() (the type marker is known before the dimension vector
|
|
||||||
// that determines size_and_type.first is read, so it is emitted first there to
|
|
||||||
// match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order)
|
|
||||||
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
if (size_and_type.second == 'C' || size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
size_and_type.second = 'U';
|
size_and_type.second = 'U';
|
||||||
}
|
}
|
||||||
|
|
||||||
string_t key = "_ArrayData_";
|
key = "_ArrayData_";
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -18443,7 +18454,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18606,7 +18617,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -18646,7 +18657,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// or end of object (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -18667,7 +18678,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18711,6 +18722,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
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 exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
@@ -21608,6 +21626,7 @@ class binary_writer
|
|||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@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)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21639,6 +21658,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21803,7 +21823,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21863,6 +21883,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@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)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22136,7 +22157,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "MessagePack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22149,6 +22170,7 @@ class binary_writer
|
|||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@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
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
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,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -22382,7 +22404,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22402,6 +22424,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -22653,6 +22684,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22679,10 +22711,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -22719,10 +22753,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -22789,6 +22825,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
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)
|
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
@@ -23298,31 +23336,16 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||||
@return whether @a el's value survives narrowing to float and back without any change
|
@return whether @a el's value fits a float without overflow; always true when @a dry_run is false
|
||||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
|
||||||
@a dry_run is false
|
|
||||||
*/
|
*/
|
||||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||||
{
|
{
|
||||||
const auto dval = el.template get<double>();
|
const auto dval = el.template get<double>();
|
||||||
if (dry_run)
|
if (dry_run)
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
return !std::isfinite(dval) ||
|
||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||||
#endif
|
|
||||||
// a value that would be rounded (rather than exactly represented) by the
|
|
||||||
// narrowing to float is treated like an out-of-range integer element; this
|
|
||||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
|
||||||
const bool in_range = std::isnan(dval) ||
|
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
|
||||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
|
||||||
std::isinf(dval);
|
|
||||||
#ifdef __GNUC__
|
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
|
||||||
#endif
|
|
||||||
return in_range;
|
|
||||||
}
|
}
|
||||||
write_number(static_cast<float>(dval), true);
|
write_number(static_cast<float>(dval), true);
|
||||||
return true;
|
return true;
|
||||||
@@ -27828,100 +27851,239 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// constructor for rvalue binary arrays (internal type)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||||
|
|
||||||
void destroy(value_t t)
|
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
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(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)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
// flatten the current json_value to a heap-allocated stack
|
if (has_no_children(cur))
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
|
||||||
if (t == value_t::array)
|
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (prev.m_data.m_type == value_t::null)
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stack.reserve(object->size());
|
|
||||||
for (auto&& it : *object)
|
|
||||||
{
|
{
|
||||||
stack.push_back(std::move(it.second));
|
free_container(cur);
|
||||||
}
|
return; // back at the top with nothing left to do
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
// ascend: detach the grandparent link from prev's
|
||||||
// since it doesn't have any children
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
switch (t)
|
||||||
{
|
{
|
||||||
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:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -27930,9 +28092,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -607,3 +607,88 @@ TEST_CASE("bad my_allocator::construct")
|
|||||||
j["test"].push_back("should not leak");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
-45
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using ordered_json = nlohmann::ordered_json;
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
@@ -115,10 +114,59 @@ TEST_CASE("BJData")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to BJData
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_bjdata(j);
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
@@ -2184,33 +2232,29 @@ TEST_CASE("BJData")
|
|||||||
|
|
||||||
SECTION("start_array() in ndarray _ArraySize_")
|
SECTION("start_array() in ndarray _ArraySize_")
|
||||||
{
|
{
|
||||||
// _ArrayType_ (2 events: key + string) is now emitted before
|
|
||||||
// _ArraySize_ (see GitHub issue #5661), which shifts the events
|
|
||||||
// below later by the same 2 events
|
|
||||||
std::vector<uint8_t> const v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
std::vector<uint8_t> const v = {'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
||||||
SaxCountdown scp(4);
|
SaxCountdown scp(2);
|
||||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("number_integer() in ndarray _ArraySize_")
|
SECTION("number_integer() in ndarray _ArraySize_")
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'i', '#', 'i', 2, 2, 1, 1, 2};
|
||||||
SaxCountdown scp(5);
|
SaxCountdown scp(3);
|
||||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("key() in ndarray _ArrayType_")
|
SECTION("key() in ndarray _ArrayType_")
|
||||||
{
|
{
|
||||||
// _ArrayType_ is emitted right after start_object(), before _ArraySize_
|
|
||||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
||||||
SaxCountdown scp(1);
|
SaxCountdown scp(6);
|
||||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("string() in ndarray _ArrayType_")
|
SECTION("string() in ndarray _ArrayType_")
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', '$', 'U', '#', 'i', 2, 2, 2, 1, 2, 3, 4};
|
||||||
SaxCountdown scp(2);
|
SaxCountdown scp(7);
|
||||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2813,22 +2857,6 @@ TEST_CASE("BJData")
|
|||||||
CHECK(out_single.at(0) == '{');
|
CHECK(out_single.at(0) == '{');
|
||||||
CHECK(json::from_bjdata(out_single) == j_single);
|
CHECK(json::from_bjdata(out_single) == j_single);
|
||||||
|
|
||||||
// a double element that is finite and within the range of "single"
|
|
||||||
// but is not exactly representable as a float, so narrowing it would
|
|
||||||
// silently round it (0.1 is read back as 0.10000000149011612); this,
|
|
||||||
// like the overflow case above, falls back to a plain object (see
|
|
||||||
// GitHub issue #5661)
|
|
||||||
json const j_single_rounded = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 0.1}}});
|
|
||||||
const auto out_single_rounded = json::to_bjdata(j_single_rounded);
|
|
||||||
CHECK(out_single_rounded.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_single_rounded) == j_single_rounded);
|
|
||||||
|
|
||||||
// a double element that underflows to 0 when narrowed to "single"
|
|
||||||
json const j_single_underflow = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 1e-300}}});
|
|
||||||
const auto out_single_underflow = json::to_bjdata(j_single_underflow);
|
|
||||||
CHECK(out_single_underflow.at(0) == '{');
|
|
||||||
CHECK(json::from_bjdata(out_single_underflow) == j_single_underflow);
|
|
||||||
|
|
||||||
// in-range boundary values still use the compact ndarray encoding
|
// in-range boundary values still use the compact ndarray encoding
|
||||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
|
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
|
||||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
|
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
|
||||||
@@ -2842,23 +2870,6 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("ndarray annotation keys are read back in the documented order")
|
|
||||||
{
|
|
||||||
// from_bjdata() must emit the annotation object's keys in the order
|
|
||||||
// used throughout the documentation, _ArrayType_, _ArraySize_,
|
|
||||||
// _ArrayData_: the type marker precedes the dimension vector on the
|
|
||||||
// wire (see get_ubjson_size_type()), so it is known, and emitted,
|
|
||||||
// before _ArraySize_. For a plain json this key order is invisible
|
|
||||||
// (its comparison ignores it), but for an ordered_json it is not (see
|
|
||||||
// GitHub issue #5661).
|
|
||||||
const ordered_json o = ordered_json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,4]})");
|
|
||||||
const auto packed = ordered_json::to_bjdata(o);
|
|
||||||
CHECK(packed.at(0) == '[');
|
|
||||||
const ordered_json o_back = ordered_json::from_bjdata(packed);
|
|
||||||
CHECK(o_back == o);
|
|
||||||
CHECK(o_back.dump() == o.dump());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ndarray that would not be read back as an annotated object stays as object")
|
SECTION("ndarray that would not be read back as an annotated object stays as object")
|
||||||
{
|
{
|
||||||
// the reader only restores an annotated object from an ND-array
|
// the reader only restores an annotated object from an ND-array
|
||||||
|
|||||||
@@ -149,6 +149,54 @@ TEST_CASE("BSON")
|
|||||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
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&);
|
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")
|
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||||
|
|||||||
+42
-3
@@ -38,10 +38,49 @@ TEST_CASE("CBOR")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to CBOR
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_cbor(j);
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("NaN")
|
SECTION("NaN")
|
||||||
|
|||||||
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
|
|||||||
#endif
|
#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
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
// Macro for all test cases for start_pos and end_pos
|
// Macro for all test cases for start_pos and end_pos
|
||||||
#define SETUP_TESTCASES() \
|
#define SETUP_TESTCASES() \
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -196,6 +198,198 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
|||||||
|
|
||||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
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
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("object type whose erase() returns void")
|
TEST_CASE("object type whose erase() returns void")
|
||||||
@@ -322,3 +516,46 @@ 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,10 +41,49 @@ TEST_CASE("MessagePack")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to MessagePack
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_msgpack(j);
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <new>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -107,6 +109,84 @@ 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>;
|
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
|
// for #1647
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -940,4 +1020,211 @@ 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());
|
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
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -35,10 +35,59 @@ TEST_CASE("UBJSON")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to UBJSON
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_ubjson(j);
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
@@ -2099,9 +2148,14 @@ TEST_CASE("UBJSON")
|
|||||||
|
|
||||||
SECTION("discarded")
|
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};
|
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
#if JSON_DIAGNOSTICS
|
||||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user