mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 14:40:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b242c0bc30 |
No files matched your search
@@ -17,11 +17,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-15:
|
macos-14:
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
||||||
env:
|
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-26:
|
macos-15:
|
||||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-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: ['26.4.1', '26.5', '26.6']
|
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -71,3 +71,38 @@ jobs:
|
|||||||
run: cmake --build build --parallel 10
|
run: cmake --build build --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
|
swiftpm:
|
||||||
|
runs-on: macos-15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Check that Package.swift resolves without a deprecation warning
|
||||||
|
run: swift package dump-package
|
||||||
|
- name: Build the SwiftPM documentation example against this checkout
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/json-swiftpm-consumer/Sources/MyLibrary
|
||||||
|
cp docs/mkdocs/docs/integration/swift/example.cpp /tmp/json-swiftpm-consumer/Sources/MyLibrary/example.cpp
|
||||||
|
cat > /tmp/json-swiftpm-consumer/Package.swift << EOF
|
||||||
|
// swift-tools-version: 5.9
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "MyPackage",
|
||||||
|
dependencies: [
|
||||||
|
.package(path: "${{ github.workspace }}")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "MyLibrary",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "json", package: "json")
|
||||||
|
],
|
||||||
|
publicHeadersPath: "."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
cd /tmp/json-swiftpm-consumer
|
||||||
|
swift build
|
||||||
@@ -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', '16', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '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
|
||||||
|
|||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: json
|
|
||||||
Upstream-Contact: Niels Lohmann <mail@nlohmann.me>
|
|
||||||
Source: https://github.com/nlohmann/json
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: include/nlohmann/thirdparty/hedley.hpp
|
|
||||||
Copyright: 2016-2021 Evan Nemerson <evan@nemerson.com>
|
|
||||||
License: CC0
|
|
||||||
|
|
||||||
Files: include/nlohmann/detail/meta/cpp_future.hpp
|
|
||||||
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors
|
|
||||||
License: MIT AND Apache-2.0
|
|
||||||
|
|
||||||
Files: tests/thirdparty/doctest/*
|
|
||||||
Copyright: 2016-2023 Viktor Kirilov
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: tests/thirdparty/fifo_map/*
|
|
||||||
Copyright: 2015-2017 Niels Lohmann
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: tests/thirdparty/imapdl/*
|
|
||||||
Copyright: 2017 Georg Sauthoff <mail@gms.tf>
|
|
||||||
License: GPL-3.0-only
|
|
||||||
|
|
||||||
Files: tools/amalgamate/*
|
|
||||||
Copyright: 2012 Erik Edlund <erik.edlund@32767.se>
|
|
||||||
License: BSD-3-Clause
|
|
||||||
|
|
||||||
Files: tools/gdb_pretty_printer/*
|
|
||||||
Copyright: 2020 Hannes Domani <https://github.com/ssbssa>
|
|
||||||
License: MIT
|
|
||||||
@@ -80,6 +80,7 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
|
"single_include/nlohmann/json_fwd.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|||||||
+1
-1
@@ -10,5 +10,5 @@ title: "JSON for Modern C++"
|
|||||||
version: 3.12.0
|
version: 3.12.0
|
||||||
date-released: 2025-04-07
|
date-released: 2025-04-07
|
||||||
license: MIT
|
license: MIT
|
||||||
repository-code: "https://github.com/nlohmann"
|
repository-code: "https://github.com/nlohmann/json"
|
||||||
url: https://json.nlohmann.me
|
url: https://json.nlohmann.me
|
||||||
+4
-1
@@ -42,8 +42,11 @@ endif()
|
|||||||
## OPTIONS
|
## OPTIONS
|
||||||
##
|
##
|
||||||
|
|
||||||
|
# Build the tests by default only for the main project and only if the tests
|
||||||
|
# directory exists (the release archive json.tar.xz does not contain it).
|
||||||
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
||||||
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13))
|
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13)
|
||||||
|
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt")
|
||||||
set(JSON_BuildTests_INIT ON)
|
set(JSON_BuildTests_INIT ON)
|
||||||
else()
|
else()
|
||||||
set(JSON_BuildTests_INIT OFF)
|
set(JSON_BuildTests_INIT OFF)
|
||||||
|
|||||||
@@ -196,19 +196,19 @@ Further documentation:
|
|||||||
|
|
||||||
## REUSE
|
## REUSE
|
||||||
|
|
||||||
### `.reuse/dep5`
|
### `REUSE.toml`
|
||||||
|
|
||||||
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
||||||
|
|
||||||
Further documentation:
|
Further documentation:
|
||||||
|
|
||||||
- [DEP5](https://reuse.software/spec-3.2/#dep5-deprecated)
|
- [REUSE.toml](https://reuse.software/spec-3.3/#reusetoml)
|
||||||
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
||||||
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
||||||
- [REUSE](http://reuse.software)
|
- [REUSE](http://reuse.software)
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The filename `.reuse/dep5` is predetermined by REUSE. Alternatively, a `REUSE.toml` file can be used.
|
> The filename `REUSE.toml` is predetermined by REUSE. Alternatively, a `.reuse/dep5` file (deprecated) can be used.
|
||||||
|
|
||||||
### `.reuse/templates`
|
### `.reuse/templates`
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# find GNU sed to use `-i` parameter
|
# find GNU sed to use `-i` parameter
|
||||||
SED:=$(shell command -v gsed || which sed)
|
SED:=$(shell command -v gsed || which sed)
|
||||||
|
|
||||||
|
# find GNU tar to use `--sort` and `--pax-option` parameters
|
||||||
|
TAR:=$(shell command -v gtar || which tar)
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# source files
|
# source files
|
||||||
@@ -174,8 +177,8 @@ ChangeLog.md:
|
|||||||
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
||||||
json.tar.xz:
|
json.tar.xz:
|
||||||
mkdir json
|
mkdir json
|
||||||
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include -type f) json
|
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include src/modules -type f) json
|
||||||
gtar --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
$(TAR) --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
||||||
rm -fr json
|
rm -fr json
|
||||||
|
|
||||||
# We use `-X` to make the resulting ZIP file reproducible, see
|
# We use `-X` to make the resulting ZIP file reproducible, see
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "nlohmann-json",
|
name: "nlohmann-json",
|
||||||
platforms: [
|
platforms: [
|
||||||
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v4), .visionOS(.v1)
|
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v9), .visionOS(.v1)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "json", targets: ["json"])
|
.library(name: "json", targets: ["json"])
|
||||||
|
|||||||
@@ -1202,15 +1202,18 @@ language bindings, format converters, and the like. See the curated [Ecosystem](
|
|||||||
|
|
||||||
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||||
|
|
||||||
- GCC 4.8 - 14.2 (and possibly later)
|
- GCC 4.8 - 16.2 (and possibly later)
|
||||||
- Clang 3.4 - 21.0 (and possibly later)
|
- Clang 3.4 - 22.1 (and possibly later)
|
||||||
- Apple Clang 9.1 - 16.0 (and possibly later)
|
- Apple Clang 15.0 - 21.0 (and possibly later)
|
||||||
- Intel C++ Compiler 17.0.2 (and possibly later)
|
- Intel C++ Compiler Classic (icpc) 2021.10
|
||||||
- Nvidia CUDA Compiler 11.0.221 (and possibly later)
|
- Intel oneAPI DPC++/C++ Compiler (icpx) 2025.3 (and possibly later)
|
||||||
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
|
- NVIDIA CUDA Compiler (nvcc) 11.8 - 12.6 (and possibly later)
|
||||||
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
|
- NVIDIA HPC SDK C++ Compiler (nvc++) 25.5 (and possibly later)
|
||||||
- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)
|
- Microsoft Visual C++ 2015 / MSVC 19.0 (and possibly later)
|
||||||
- Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0 (and possibly later)
|
- Microsoft Visual C++ 2017 / MSVC 19.16 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2019 / MSVC 19.29 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2022 / MSVC 19.44 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2026 / MSVC 19.51 (and possibly later)
|
||||||
|
|
||||||
I would be happy to learn about other compilers/versions.
|
I would be happy to learn about other compilers/versions.
|
||||||
|
|
||||||
@@ -1401,7 +1404,7 @@ The library is compliant to version 3.3 of the [**REUSE specification**](https:/
|
|||||||
|
|
||||||
- Every source file contains an SPDX copyright header.
|
- Every source file contains an SPDX copyright header.
|
||||||
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
||||||
- File `.reuse/dep5` contains an overview of all files' copyrights and licenses.
|
- File `REUSE.toml` contains an overview of all files' copyrights and licenses.
|
||||||
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
version = 1
|
||||||
|
SPDX-PackageName = "json"
|
||||||
|
SPDX-PackageSupplier = "Niels Lohmann <mail@nlohmann.me>"
|
||||||
|
SPDX-PackageDownloadLocation = "https://github.com/nlohmann/json"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me>"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "include/nlohmann/thirdparty/hedley.hpp"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2016-2021 Evan Nemerson <evan@nemerson.com>"
|
||||||
|
SPDX-License-Identifier = "CC0"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "include/nlohmann/detail/meta/cpp_future.hpp"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors"
|
||||||
|
SPDX-License-Identifier = "MIT AND Apache-2.0"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/doctest/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2016-2023 Viktor Kirilov"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/fifo_map/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2015-2017 Niels Lohmann"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/imapdl/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2017 Georg Sauthoff <mail@gms.tf>"
|
||||||
|
SPDX-License-Identifier = "GPL-3.0-only"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tools/amalgamate/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2012 Erik Edlund <erik.edlund@32767.se>"
|
||||||
|
SPDX-License-Identifier = "BSD-3-Clause"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tools/gdb_pretty_printer/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2020 Hannes Domani <https://github.com/ssbssa>"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
@@ -48,6 +48,7 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
|
"single_include/nlohmann/json_fwd.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ if(MODE STREQUAL "undef")
|
|||||||
# recipe is self-contained and its output is byte-stable across reruns.
|
# recipe is self-contained and its output is byte-stable across reruns.
|
||||||
# The embedded SPDX tags below are part of the *generated* file's
|
# The embedded SPDX tags below are part of the *generated* file's
|
||||||
# content, not a REUSE header for this .cmake script itself (which is
|
# content, not a REUSE header for this .cmake script itself (which is
|
||||||
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
|
# already covered by the blanket path = "**" rule in REUSE.toml) -- keep
|
||||||
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
||||||
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
||||||
# REUSE-IgnoreStart
|
# REUSE-IgnoreStart
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- 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
|
||||||
|
|
||||||
@@ -121,6 +119,4 @@ Linear in the size of the JSON value `j`.
|
|||||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
- 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,9 +58,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
- 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
|
||||||
|
|
||||||
@@ -113,8 +110,6 @@ 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,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- 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
|
||||||
|
|
||||||
@@ -88,5 +86,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- 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,8 +54,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
`"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
|
||||||
|
|
||||||
@@ -110,5 +108,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
- 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,8 +61,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- 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
|
||||||
|
|
||||||
@@ -114,5 +112,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- 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,18 +21,17 @@ 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 |
|
||||||
@@ -90,7 +89,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 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.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
| 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 |
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Byte string | binary | 0x59 |
|
| Byte string | binary | 0x59 |
|
||||||
| Byte string | binary | 0x5A |
|
| Byte string | binary | 0x5A |
|
||||||
| Byte string | binary | 0x5B |
|
| Byte string | binary | 0x5B |
|
||||||
| Byte string | binary | 0x5F |
|
|
||||||
| UTF-8 string | string | 0x60..0x77 |
|
| UTF-8 string | string | 0x60..0x77 |
|
||||||
| UTF-8 string | string | 0x78 |
|
| UTF-8 string | string | 0x78 |
|
||||||
| UTF-8 string | string | 0x79 |
|
| UTF-8 string | string | 0x79 |
|
||||||
@@ -157,9 +156,6 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Single-Precision Float | number_float | 0xFA |
|
| Single-Precision Float | number_float | 0xFA |
|
||||||
| Double-Precision Float | number_float | 0xFB |
|
| Double-Precision Float | number_float | 0xFB |
|
||||||
|
|
||||||
Indefinite-length UTF-8 strings (0x7F) and byte strings (0x5F) are supported. Each chunk must be a definite-length
|
|
||||||
string of the same major type, as required by [RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3).
|
|
||||||
|
|
||||||
!!! warning "Incomplete mapping"
|
!!! warning "Incomplete mapping"
|
||||||
|
|
||||||
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
||||||
|
|||||||
@@ -804,20 +804,6 @@ does not list an enumerator and it is therefore converted like the first listed
|
|||||||
[json.exception.type_error.318] duplicate object key 'red'
|
[json.exception.type_error.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.
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ automatically download a release as a dependency at configure time.
|
|||||||
|
|
||||||
### `JSON_BuildTests`
|
### `JSON_BuildTests`
|
||||||
|
|
||||||
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project. That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project and the `tests` directory exists (the release archive `json.tar.xz` does not contain it). That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
||||||
|
|
||||||
### `JSON_CI`
|
### `JSON_CI`
|
||||||
|
|
||||||
|
|||||||
@@ -1072,20 +1072,6 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief reports a nested indefinite-length CBOR string or byte array
|
|
||||||
@param[in] type_name name of the rejected string type
|
|
||||||
@param[in] context parsing context for the error message
|
|
||||||
@return whether the SAX consumer accepts the parse error
|
|
||||||
*/
|
|
||||||
bool cbor_indefinite_string_error(const char* type_name, const char* context)
|
|
||||||
{
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
|
||||||
exception_message(concat("indefinite-length ", type_name,
|
|
||||||
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -1095,13 +1081,12 @@ class binary_reader
|
|||||||
into the same string.
|
into the same string.
|
||||||
|
|
||||||
@param[out] result string the bytes are appended to
|
@param[out] result string the bytes are appended to
|
||||||
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
|
||||||
|
|
||||||
@return whether string creation completed
|
@return whether string creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_string_chunk(string_t& result, const bool inside_indefinite)
|
bool get_cbor_string_chunk(string_t& result)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -1162,7 +1147,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
|
exception_message(concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1180,9 +1165,13 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_string(string_t& result, const char* context = "string")
|
bool get_cbor_string(string_t& result, const char* context = "string")
|
||||||
{
|
{
|
||||||
// read chunks iteratively, but reject a second indefinite-length
|
// number of indefinite-length strings that have been opened and not
|
||||||
// level as required by RFC 8949, Section 3.2.3
|
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
||||||
bool indefinite = false;
|
// but this reader has always accepted it, so the open levels are
|
||||||
|
// counted instead of recursed through, which overflowed the stack for
|
||||||
|
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
||||||
|
// to the same result, so no per-level state is needed.
|
||||||
|
std::size_t open = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -1193,28 +1182,29 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
++open;
|
||||||
{
|
|
||||||
return cbor_indefinite_string_error("string", "string");
|
|
||||||
}
|
|
||||||
indefinite = true;
|
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the innermost indefinite-length string;
|
||||||
// of one it falls through to the error below
|
// outside of one it is not a string and falls through to the error
|
||||||
if (indefinite && current == 0xFF)
|
if (open != 0 && current == 0xFF)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
if (--open == 0)
|
||||||
|
{
|
||||||
|
return check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (open == 0)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return check_string_utf8(result, context);
|
||||||
}
|
}
|
||||||
@@ -1306,13 +1296,12 @@ class binary_reader
|
|||||||
read into the same byte array.
|
read into the same byte array.
|
||||||
|
|
||||||
@param[out] result byte array the bytes are appended to
|
@param[out] result byte array the bytes are appended to
|
||||||
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
|
||||||
|
|
||||||
@return whether byte array creation completed
|
@return whether byte array creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_binary_chunk(binary_t& result, const bool inside_indefinite)
|
bool get_cbor_binary_chunk(binary_t& result)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -1377,7 +1366,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
|
exception_message(concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1395,9 +1384,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_binary(binary_t& result)
|
bool get_cbor_binary(binary_t& result)
|
||||||
{
|
{
|
||||||
// read chunks iteratively, but reject a second indefinite-length
|
// the open indefinite-length byte arrays are counted rather than
|
||||||
// level as required by RFC 8949, Section 3.2.3
|
// recursed through, for the reason given in @ref get_cbor_string
|
||||||
bool indefinite = false;
|
std::size_t open = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -1408,28 +1397,29 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x5F) // Binary data (indefinite length)
|
if (current == 0x5F) // Binary data (indefinite length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
++open;
|
||||||
{
|
|
||||||
return cbor_indefinite_string_error("binary array", "binary");
|
|
||||||
}
|
|
||||||
indefinite = true;
|
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the innermost indefinite-length byte
|
||||||
// of one it falls through to the error below
|
// array; outside of one it falls through to the error below
|
||||||
if (indefinite && current == 0xFF)
|
if (open != 0 && current == 0xFF)
|
||||||
{
|
{
|
||||||
return true;
|
if (--open == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result, indefinite)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (open == 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -159,7 +158,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -324,7 +322,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "CBOR");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +382,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -658,7 +655,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "MessagePack");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,7 +668,6 @@ class binary_writer
|
|||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@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,
|
||||||
@@ -905,7 +901,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -925,15 +921,6 @@ 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 //
|
||||||
//////////
|
//////////
|
||||||
@@ -1185,7 +1172,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -1212,12 +1198,10 @@ 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;
|
||||||
@@ -1254,12 +1238,10 @@ 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;
|
||||||
@@ -1326,8 +1308,6 @@ 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)
|
||||||
{
|
{
|
||||||
|
|||||||
+74
-182
@@ -612,210 +612,100 @@ 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))) {}
|
||||||
|
|
||||||
private:
|
void destroy(value_t t)
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static basic_json& last_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 v.m_data.m_value.object->rbegin()->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the last child of 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_last_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);
|
|
||||||
// erase() needs a forward iterator, so std::prev(end()) is
|
|
||||||
// used here rather than rbegin() (see last_child() above)
|
|
||||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 the "last child" chain, 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 last child slot doubles as storage for that
|
|
||||||
// parent's own parent link while we are below it, so no
|
|
||||||
// extra memory is needed. We only ever remove a child once
|
|
||||||
// it is a scalar or an empty array/object, which neither
|
|
||||||
// allocates nor recurses more than one level deep.
|
|
||||||
//
|
|
||||||
// This json_value is not itself a basic_json, so the
|
|
||||||
// top-level container is first moved into a local stand-in
|
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
basic_json cur;
|
|
||||||
cur.m_data.m_type = t;
|
|
||||||
using std::swap;
|
|
||||||
swap(cur.m_data.m_value, *this);
|
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
if (has_no_children(cur))
|
// flatten the current json_value to a heap-allocated stack
|
||||||
|
std::vector<basic_json> stack;
|
||||||
|
|
||||||
|
// move the top-level items to stack
|
||||||
|
if (t == value_t::array)
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
stack.reserve(array->size());
|
||||||
|
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stack.reserve(object->size());
|
||||||
|
for (auto&& it : *object)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
stack.push_back(std::move(it.second));
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ascend: detach the grandparent link from prev's
|
// it's now safe that current_item gets destructed
|
||||||
// last slot, drop that (now null) slot, free cur
|
// since it doesn't have any children
|
||||||
// (it is empty), then move up one level
|
|
||||||
basic_json gp;
|
|
||||||
take(gp, last_child(prev));
|
|
||||||
pop_last_child(prev);
|
|
||||||
|
|
||||||
free_container(cur);
|
|
||||||
|
|
||||||
take(cur, prev);
|
|
||||||
take(prev, gp);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_last_ref = last_child(cur);
|
|
||||||
|
|
||||||
if (has_no_children(cur_last_ref))
|
|
||||||
{
|
|
||||||
// scalar, or already-empty array/object
|
|
||||||
pop_last_child(cur);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// descend into the non-empty last child, reversing the
|
|
||||||
// link: its slot takes over prev, and the child becomes
|
|
||||||
// the new cur
|
|
||||||
basic_json tmp;
|
|
||||||
take(tmp, cur_last_ref);
|
|
||||||
take(cur_last_ref, prev);
|
|
||||||
take(prev, cur);
|
|
||||||
take(cur, tmp);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void destroy(value_t t)
|
|
||||||
{
|
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::object:
|
||||||
destroy_string();
|
{
|
||||||
|
AllocatorType<object_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
AllocatorType<array_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::string:
|
||||||
|
{
|
||||||
|
AllocatorType<string_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
destroy_binary();
|
{
|
||||||
break;
|
AllocatorType<binary_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||||
case value_t::object:
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||||
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:
|
||||||
@@ -824,7 +714,9 @@ public:
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+117
-255
@@ -14789,20 +14789,6 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief reports a nested indefinite-length CBOR string or byte array
|
|
||||||
@param[in] type_name name of the rejected string type
|
|
||||||
@param[in] context parsing context for the error message
|
|
||||||
@return whether the SAX consumer accepts the parse error
|
|
||||||
*/
|
|
||||||
bool cbor_indefinite_string_error(const char* type_name, const char* context)
|
|
||||||
{
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
|
||||||
exception_message(concat("indefinite-length ", type_name,
|
|
||||||
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -14812,13 +14798,12 @@ class binary_reader
|
|||||||
into the same string.
|
into the same string.
|
||||||
|
|
||||||
@param[out] result string the bytes are appended to
|
@param[out] result string the bytes are appended to
|
||||||
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
|
||||||
|
|
||||||
@return whether string creation completed
|
@return whether string creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_string_chunk(string_t& result, const bool inside_indefinite)
|
bool get_cbor_string_chunk(string_t& result)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -14879,7 +14864,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
|
exception_message(concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14897,9 +14882,13 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_string(string_t& result, const char* context = "string")
|
bool get_cbor_string(string_t& result, const char* context = "string")
|
||||||
{
|
{
|
||||||
// read chunks iteratively, but reject a second indefinite-length
|
// number of indefinite-length strings that have been opened and not
|
||||||
// level as required by RFC 8949, Section 3.2.3
|
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
||||||
bool indefinite = false;
|
// but this reader has always accepted it, so the open levels are
|
||||||
|
// counted instead of recursed through, which overflowed the stack for
|
||||||
|
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
||||||
|
// to the same result, so no per-level state is needed.
|
||||||
|
std::size_t open = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -14910,28 +14899,29 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
++open;
|
||||||
{
|
|
||||||
return cbor_indefinite_string_error("string", "string");
|
|
||||||
}
|
|
||||||
indefinite = true;
|
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the innermost indefinite-length string;
|
||||||
// of one it falls through to the error below
|
// outside of one it is not a string and falls through to the error
|
||||||
if (indefinite && current == 0xFF)
|
if (open != 0 && current == 0xFF)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
if (--open == 0)
|
||||||
|
{
|
||||||
|
return check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (open == 0)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return check_string_utf8(result, context);
|
||||||
}
|
}
|
||||||
@@ -15023,13 +15013,12 @@ class binary_reader
|
|||||||
read into the same byte array.
|
read into the same byte array.
|
||||||
|
|
||||||
@param[out] result byte array the bytes are appended to
|
@param[out] result byte array the bytes are appended to
|
||||||
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
|
||||||
|
|
||||||
@return whether byte array creation completed
|
@return whether byte array creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_binary_chunk(binary_t& result, const bool inside_indefinite)
|
bool get_cbor_binary_chunk(binary_t& result)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -15094,7 +15083,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
|
exception_message(concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15112,9 +15101,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_binary(binary_t& result)
|
bool get_cbor_binary(binary_t& result)
|
||||||
{
|
{
|
||||||
// read chunks iteratively, but reject a second indefinite-length
|
// the open indefinite-length byte arrays are counted rather than
|
||||||
// level as required by RFC 8949, Section 3.2.3
|
// recursed through, for the reason given in @ref get_cbor_string
|
||||||
bool indefinite = false;
|
std::size_t open = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -15125,28 +15114,29 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x5F) // Binary data (indefinite length)
|
if (current == 0x5F) // Binary data (indefinite length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
++open;
|
||||||
{
|
|
||||||
return cbor_indefinite_string_error("binary array", "binary");
|
|
||||||
}
|
|
||||||
indefinite = true;
|
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a break marker closes the indefinite-length string; outside
|
// a break marker closes the innermost indefinite-length byte
|
||||||
// of one it falls through to the error below
|
// array; outside of one it falls through to the error below
|
||||||
if (indefinite && current == 0xFF)
|
if (open != 0 && current == 0xFF)
|
||||||
{
|
{
|
||||||
return true;
|
if (--open == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
get();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result, indefinite)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!indefinite)
|
if (open == 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -21597,7 +21587,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -21629,7 +21618,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -21794,7 +21782,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "CBOR");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21854,7 +21842,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -22128,7 +22115,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "MessagePack");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22141,7 +22128,6 @@ 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,
|
||||||
@@ -22375,7 +22361,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22395,15 +22381,6 @@ 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 //
|
||||||
//////////
|
//////////
|
||||||
@@ -22655,7 +22632,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -22682,12 +22658,10 @@ 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;
|
||||||
@@ -22724,12 +22698,10 @@ 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;
|
||||||
@@ -22796,8 +22768,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -27822,210 +27792,100 @@ 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))) {}
|
||||||
|
|
||||||
private:
|
void destroy(value_t t)
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static basic_json& last_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 v.m_data.m_value.object->rbegin()->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the last child of 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_last_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);
|
|
||||||
// erase() needs a forward iterator, so std::prev(end()) is
|
|
||||||
// used here rather than rbegin() (see last_child() above)
|
|
||||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 the "last child" chain, 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 last child slot doubles as storage for that
|
|
||||||
// parent's own parent link while we are below it, so no
|
|
||||||
// extra memory is needed. We only ever remove a child once
|
|
||||||
// it is a scalar or an empty array/object, which neither
|
|
||||||
// allocates nor recurses more than one level deep.
|
|
||||||
//
|
|
||||||
// This json_value is not itself a basic_json, so the
|
|
||||||
// top-level container is first moved into a local stand-in
|
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
basic_json cur;
|
|
||||||
cur.m_data.m_type = t;
|
|
||||||
using std::swap;
|
|
||||||
swap(cur.m_data.m_value, *this);
|
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
if (has_no_children(cur))
|
// flatten the current json_value to a heap-allocated stack
|
||||||
|
std::vector<basic_json> stack;
|
||||||
|
|
||||||
|
// move the top-level items to stack
|
||||||
|
if (t == value_t::array)
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
stack.reserve(array->size());
|
||||||
|
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stack.reserve(object->size());
|
||||||
|
for (auto&& it : *object)
|
||||||
{
|
{
|
||||||
free_container(cur);
|
stack.push_back(std::move(it.second));
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ascend: detach the grandparent link from prev's
|
// it's now safe that current_item gets destructed
|
||||||
// last slot, drop that (now null) slot, free cur
|
// since it doesn't have any children
|
||||||
// (it is empty), then move up one level
|
|
||||||
basic_json gp;
|
|
||||||
take(gp, last_child(prev));
|
|
||||||
pop_last_child(prev);
|
|
||||||
|
|
||||||
free_container(cur);
|
|
||||||
|
|
||||||
take(cur, prev);
|
|
||||||
take(prev, gp);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_last_ref = last_child(cur);
|
|
||||||
|
|
||||||
if (has_no_children(cur_last_ref))
|
|
||||||
{
|
|
||||||
// scalar, or already-empty array/object
|
|
||||||
pop_last_child(cur);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// descend into the non-empty last child, reversing the
|
|
||||||
// link: its slot takes over prev, and the child becomes
|
|
||||||
// the new cur
|
|
||||||
basic_json tmp;
|
|
||||||
take(tmp, cur_last_ref);
|
|
||||||
take(cur_last_ref, prev);
|
|
||||||
take(prev, cur);
|
|
||||||
take(cur, tmp);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void destroy(value_t t)
|
|
||||||
{
|
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::object:
|
||||||
destroy_string();
|
{
|
||||||
|
AllocatorType<object_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
AllocatorType<array_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::string:
|
||||||
|
{
|
||||||
|
AllocatorType<string_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
destroy_binary();
|
{
|
||||||
break;
|
AllocatorType<binary_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||||
case value_t::object:
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||||
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:
|
||||||
@@ -28034,7 +27894,9 @@ public:
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -607,88 +607,3 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,59 +114,10 @@ TEST_CASE("BJData")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// a discarded value cannot be serialized to BJData
|
// discarded values are not serialized
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
const auto result = json::to_bjdata(j);
|
||||||
}
|
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")
|
||||||
|
|||||||
@@ -149,54 +149,6 @@ 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")
|
||||||
|
|||||||
+19
-65
@@ -38,49 +38,10 @@ TEST_CASE("CBOR")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// a discarded value cannot be serialized to CBOR
|
// discarded values are not serialized
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
const auto result = json::to_cbor(j);
|
||||||
}
|
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")
|
||||||
@@ -1699,7 +1660,7 @@ TEST_CASE("CBOR")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
|
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
|
||||||
@@ -2305,21 +2266,22 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
{
|
{
|
||||||
// Reading an indefinite-length string or byte array used to call itself
|
// Reading an indefinite-length string or byte array used to call itself
|
||||||
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
||||||
// the call stack before any of the input was rejected. Nested indefinite
|
// the call stack before any of the input was rejected. The open levels are
|
||||||
// chunks are now rejected at the second byte, without recursing.
|
// counted now, and the levels below prove the reader still reads the same
|
||||||
|
// values and reports the same errors at the same byte offsets.
|
||||||
json _;
|
json _;
|
||||||
|
|
||||||
SECTION("nested levels are rejected, not crashed on")
|
SECTION("many open levels are reported, not crashed on")
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> input(200000, 0x7F);
|
const std::vector<uint8_t> input(200000, 0x7F);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("nested levels are rejected, not crashed on (binary)")
|
SECTION("many open levels are reported, not crashed on (binary)")
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> input(200000, 0x5F);
|
const std::vector<uint8_t> input(200000, 0x5F);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2327,22 +2289,22 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
{
|
{
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
||||||
// empty and nonempty definite-length chunks concatenate in order
|
// nested indefinite-length strings are concatenated across levels
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 'a', 0x60, 0x61, 'b', 0x61, 'c', 0xFF})) == json("abc"));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0x61, 0x62, 0xFF})) == json("ab"));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x7F, 0x61, 0x7A, 0xFF, 0xFF, 0xFF})) == json("z"));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("chunks are still concatenated (binary)")
|
SECTION("chunks are still concatenated (binary)")
|
||||||
{
|
{
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0xFF})) == json::binary({}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0x41, 0x62, 0xFF})) == json::binary({0x61, 0x62}));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0x40, 0x41, 0x62, 0x41, 0x63, 0xFF})) == json::binary({0x61, 0x62, 0x63}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a chunk that is not a string is still rejected")
|
SECTION("a chunk that is not a string is still rejected")
|
||||||
{
|
{
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x00", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||||
@@ -2895,17 +2857,9 @@ TEST_CASE("examples from RFC 8949 Appendix A")
|
|||||||
{
|
{
|
||||||
const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
|
const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
|
||||||
json j;
|
json j;
|
||||||
// the fixture's tail contains nested indefinite-length byte strings.
|
CHECK_NOTHROW(j = json::from_cbor(packed));
|
||||||
CHECK_THROWS_WITH_AS(j = json::from_cbor(packed), "[json.exception.parse_error.113] parse error at byte 513: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
|
||||||
|
|
||||||
// keep the byte-for-byte decoding check for its valid prefix: the first
|
const auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
|
||||||
// 512 encoded bytes contain 468 payload bytes in definite-length chunks.
|
|
||||||
auto valid_prefix = packed;
|
|
||||||
valid_prefix.resize(512);
|
|
||||||
valid_prefix.push_back(0xFF);
|
|
||||||
auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
|
|
||||||
expected.resize(468);
|
|
||||||
CHECK_NOTHROW(j = json::from_cbor(valid_prefix));
|
|
||||||
CHECK(j == json::binary(expected));
|
CHECK(j == json::binary(expected));
|
||||||
|
|
||||||
// 0xd8
|
// 0xd8
|
||||||
|
|||||||
@@ -41,49 +41,10 @@ TEST_CASE("MessagePack")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// a discarded value cannot be serialized to MessagePack
|
// discarded values are not serialized
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
const auto result = json::to_msgpack(j);
|
||||||
}
|
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,9 +40,7 @@ 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>
|
||||||
@@ -109,84 +107,6 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|||||||
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
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
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -1020,211 +940,4 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
|||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
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
|
||||||
@@ -920,12 +920,4 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("issue #5317 - nested indefinite-length CBOR string chunks are rejected")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0xFF})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0xFF})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0x7F, 0x7F, 0xFF, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -35,59 +35,10 @@ TEST_CASE("UBJSON")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// a discarded value cannot be serialized to UBJSON
|
// discarded values are not serialized
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
const auto result = json::to_ubjson(j);
|
||||||
}
|
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")
|
||||||
@@ -2148,14 +2099,9 @@ 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};
|
||||||
#if JSON_DIAGNOSTICS
|
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
||||||
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&);
|
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||||
#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