mirror of
https://github.com/nlohmann/json.git
synced 2026-09-10 18:27:59 +00:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd33ec95de | ||
|
|
b7e364be9b | ||
|
|
0750e141d9 | ||
|
|
f0fb1d735f | ||
|
|
344057d420 | ||
|
|
407b875e82 | ||
|
|
bb1fc5eb9a | ||
|
|
b17c272f46 | ||
|
|
8e26b9d4b3 | ||
|
|
8c8d14cb4e | ||
|
|
d22c5c7641 | ||
|
|
c9f1d2d646 | ||
|
|
a316738cfa | ||
|
|
8c38e270b5 | ||
|
|
c9477ccf91 | ||
|
|
d6660cf718 | ||
|
|
c05347dd54 | ||
|
|
6d86cc0f6b | ||
|
|
faa35cc647 | ||
|
|
7b2d73cf2e | ||
|
|
0c2cdebe31 | ||
|
|
6e9212c444 | ||
|
|
a207a03ed0 | ||
|
|
c7df1f7b69 | ||
|
|
4570abf1f6 | ||
|
|
e28d9cfee9 | ||
|
|
1e4f31639a | ||
|
|
3f5b08230e | ||
|
|
1d671db1fe | ||
|
|
bf629a9e51 | ||
|
|
7844f8e0d3 | ||
|
|
676fec6939 | ||
|
|
b82717c8a4 | ||
|
|
d133e01db9 | ||
|
|
346a73873d |
@@ -108,7 +108,9 @@ The tests are located in [`tests/src/unit-*.cpp`](https://github.com/nlohmann/js
|
|||||||
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
||||||
context which existing file needs to be extended, and only very few cases require creating new test files.
|
context which existing file needs to be extended, and only very few cases require creating new test files.
|
||||||
|
|
||||||
When fixing a bug, edit `unit-regression2.cpp` and add a section referencing the fixed issue.
|
When fixing a bug, edit `unit-regression3.cpp` and add a test case referencing the fixed issue. Its predecessors
|
||||||
|
`unit-regression1.cpp` and `unit-regression2.cpp` stay as they are: the MinGW linker fails on the object a file this
|
||||||
|
size produces, which is why the tests are spread over several files in the first place.
|
||||||
|
|
||||||
#### Exceptions
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
container: ubuntu:focal
|
container: ubuntu:focal
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls]
|
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_no_thread_local]
|
||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||||
@@ -118,7 +118,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ jobs:
|
|||||||
target: [ci_test_examples, ci_test_build_documentation]
|
target: [ci_test_examples, ci_test_build_documentation]
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ jobs:
|
|||||||
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
||||||
# MinGW linker cannot relocate the debug sections this test produces.
|
# MinGW linker cannot relocate the debug sections this test produces.
|
||||||
# The tests are only built and run here, so the debug info is not used.
|
# The tests are only built and run here, so the debug info is not used.
|
||||||
|
# Do not add -O1 here to shrink the objects further: it does make them
|
||||||
|
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
||||||
|
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
||||||
|
# Keep the objects small by splitting the test files instead.
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build ^
|
run: cmake -S . -B build ^
|
||||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: pretty clean ChangeLog.md release
|
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# configuration
|
# configuration
|
||||||
@@ -41,6 +41,8 @@ all:
|
|||||||
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
||||||
@echo "pretty - beautify code with Artistic Style"
|
@echo "pretty - beautify code with Artistic Style"
|
||||||
@echo "run_benchmarks - build and run benchmarks"
|
@echo "run_benchmarks - build and run benchmarks"
|
||||||
|
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
|
||||||
|
@echo "update_hedley_undef - rebuild hedley_undef.hpp from the JSON_HEDLEY_* #define names in hedley.hpp"
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -241,11 +243,24 @@ update_hedley:
|
|||||||
rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
||||||
curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp
|
curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
$(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp
|
$(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
grep "[[:blank:]]*#[[:blank:]]*undef" include/nlohmann/thirdparty/hedley/hedley.hpp | grep -v "__" | sort | uniq | $(SED) 's/ //g' | $(SED) 's/undef/undef /g' > include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
|
||||||
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp
|
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
$(MAKE) update_hedley_undef
|
||||||
$(MAKE) amalgamate
|
$(MAKE) amalgamate
|
||||||
|
|
||||||
|
# Rebuild hedley_undef.hpp from every JSON_HEDLEY_* name that hedley.hpp
|
||||||
|
# #defines. Hedley does not #undef all of its public macros internally (see
|
||||||
|
# #5408), so grepping those #undef lines misses names such as
|
||||||
|
# JSON_HEDLEY_PRAGMA. cmake/scripts/gen_hedley_undef_check.cmake is the
|
||||||
|
# single source of truth for this extraction (tests/CMakeLists.txt uses the
|
||||||
|
# same script, in MODE=checks, to generate the matching leak-check test), so
|
||||||
|
# the vendored header, the generated #undef list, and the regression test
|
||||||
|
# cannot drift apart.
|
||||||
|
update_hedley_undef:
|
||||||
|
cmake -DHEDLEY_HPP=include/nlohmann/thirdparty/hedley/hedley.hpp \
|
||||||
|
-DOUTPUT=include/nlohmann/thirdparty/hedley/hedley_undef.hpp \
|
||||||
|
-DMODE=undef \
|
||||||
|
-P cmake/scripts/gen_hedley_undef_check.cmake
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# serve_header.py
|
# serve_header.py
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|||||||
@@ -242,6 +242,25 @@ add_custom_target(ci_test_noglobaludls
|
|||||||
COMMENT "Compile and test with global UDLs disabled"
|
COMMENT "Compile and test with global UDLs disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Disable thread-local storage.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Without thread-local storage, copying and comparing cannot bound their
|
||||||
|
# descent and handle every object and array without the call stack. Those paths
|
||||||
|
# are otherwise only reached by values nested deeper than the bound, so this
|
||||||
|
# target is what runs the whole test suite through them.
|
||||||
|
add_custom_target(ci_test_no_thread_local
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
|
-DJSON_BuildTests=ON
|
||||||
|
-DCMAKE_CXX_FLAGS=-DJSON_NO_THREAD_LOCAL
|
||||||
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||||
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_no_thread_local && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||||
|
COMMENT "Compile and test without thread-local storage"
|
||||||
|
)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Coverage.
|
# Coverage.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Shared extractor for the JSON_HEDLEY_* macro names defined in hedley.hpp.
|
||||||
|
#
|
||||||
|
# Every macro that hedley.hpp #defines must be #undef-ed again once json.hpp
|
||||||
|
# has been fully processed (see include/nlohmann/detail/macro_unscope.hpp
|
||||||
|
# and https://github.com/nlohmann/json/issues/5408). Deriving the macro list
|
||||||
|
# straight from hedley.hpp here -- instead of hand-maintaining it in two
|
||||||
|
# places -- means hedley_undef.hpp and the regression test that checks for
|
||||||
|
# leaked macros can never drift apart, even after a future `make
|
||||||
|
# update_hedley` pulls in new macros from upstream Hedley.
|
||||||
|
#
|
||||||
|
# MODE=undef (default): write hedley_undef.hpp (SPDX header, #pragma once,
|
||||||
|
# one #undef per macro name) -- used by `make update_hedley_undef`
|
||||||
|
# MODE=checks: write one #ifdef/FAIL_CHECK/#endif per macro name,
|
||||||
|
# meant to be #include-d inside a TEST_CASE -- used by
|
||||||
|
# tests/CMakeLists.txt to (re)generate the include for
|
||||||
|
# tests/src/unit-no-macro-leak.cpp
|
||||||
|
#
|
||||||
|
# Required variables:
|
||||||
|
# HEDLEY_HPP path to include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
|
# OUTPUT path of the file to (over)write
|
||||||
|
# Optional:
|
||||||
|
# MODE "undef" (default) or "checks"
|
||||||
|
|
||||||
|
if(NOT DEFINED HEDLEY_HPP OR NOT DEFINED OUTPUT)
|
||||||
|
message(FATAL_ERROR "HEDLEY_HPP and OUTPUT must be set")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${HEDLEY_HPP}")
|
||||||
|
message(FATAL_ERROR "Hedley header not found: ${HEDLEY_HPP}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED MODE)
|
||||||
|
set(MODE undef)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT MODE STREQUAL "undef" AND NOT MODE STREQUAL "checks")
|
||||||
|
message(FATAL_ERROR "MODE must be undef or checks, got: ${MODE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Line-anchored, like `grep -oE "^[[:blank:]]*#[[:blank:]]*define[[:blank:]]+JSON_HEDLEY_[A-Za-z0-9_]+"`.
|
||||||
|
# Unanchored matching would also pick up JSON_HEDLEY_* mentions inside
|
||||||
|
# comments or string literals elsewhere in the file, which must not turn
|
||||||
|
# into #undef lines.
|
||||||
|
file(STRINGS "${HEDLEY_HPP}" hedley_lines)
|
||||||
|
set(macro_names)
|
||||||
|
foreach(line IN LISTS hedley_lines)
|
||||||
|
if("${line}" MATCHES "^[ \t]*#[ \t]*define[ \t]+(JSON_HEDLEY_[A-Za-z0-9_]+)")
|
||||||
|
list(APPEND macro_names "${CMAKE_MATCH_1}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(NOT macro_names)
|
||||||
|
message(FATAL_ERROR "No JSON_HEDLEY_* macros found in ${HEDLEY_HPP}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(REMOVE_DUPLICATES macro_names)
|
||||||
|
# Lexicographic, locale-independent (ASCII-only names) -- matches `LC_ALL=C sort`.
|
||||||
|
list(SORT macro_names COMPARE STRING)
|
||||||
|
list(LENGTH macro_names macro_count)
|
||||||
|
|
||||||
|
set(generated "")
|
||||||
|
if(MODE STREQUAL "undef")
|
||||||
|
# Same banner `make update_hedley_undef` would stamp by hand, so the
|
||||||
|
# recipe is self-contained and its output is byte-stable across reruns.
|
||||||
|
# The embedded SPDX tags below are part of the *generated* file's
|
||||||
|
# content, not a REUSE header for this .cmake script itself (which is
|
||||||
|
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
|
||||||
|
# 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.
|
||||||
|
# REUSE-IgnoreStart
|
||||||
|
string(APPEND generated "// __ _____ _____ _____\n")
|
||||||
|
string(APPEND generated "// __| | __| | | | JSON for Modern C++\n")
|
||||||
|
string(APPEND generated "// | | |__ | | | | | | version 3.12.0\n")
|
||||||
|
string(APPEND generated "// |_____|_____|_____|_|___| https://github.com/nlohmann/json\n")
|
||||||
|
string(APPEND generated "//\n")
|
||||||
|
string(APPEND generated "// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>\n")
|
||||||
|
string(APPEND generated "// SPDX-License-Identifier: MIT\n")
|
||||||
|
# REUSE-IgnoreEnd
|
||||||
|
string(APPEND generated "\n")
|
||||||
|
string(APPEND generated "#pragma once\n")
|
||||||
|
string(APPEND generated "\n")
|
||||||
|
foreach(name IN LISTS macro_names)
|
||||||
|
string(APPEND generated "#undef ${name}\n")
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
string(APPEND generated "// This file is generated by cmake/scripts/gen_hedley_undef_check.cmake\n")
|
||||||
|
string(APPEND generated "// from include/nlohmann/thirdparty/hedley/hedley.hpp. Do not edit it by\n")
|
||||||
|
string(APPEND generated "// hand -- it is regenerated on every build. ${macro_count} macros checked.\n\n")
|
||||||
|
foreach(name IN LISTS macro_names)
|
||||||
|
string(APPEND generated "#ifdef ${name}\n")
|
||||||
|
string(APPEND generated " FAIL_CHECK(\"${name} leaked after including nlohmann/json.hpp\");\n")
|
||||||
|
string(APPEND generated "#endif\n")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(output_dir "${OUTPUT}" DIRECTORY)
|
||||||
|
if(output_dir)
|
||||||
|
file(MAKE_DIRECTORY "${output_dir}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Avoid rewriting the file (and busting downstream incremental rebuilds)
|
||||||
|
# when the content has not actually changed.
|
||||||
|
set(write_output TRUE)
|
||||||
|
if(EXISTS "${OUTPUT}")
|
||||||
|
file(READ "${OUTPUT}" existing_content)
|
||||||
|
if(existing_content STREQUAL generated)
|
||||||
|
set(write_output FALSE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(write_output)
|
||||||
|
file(WRITE "${OUTPUT}" "${generated}")
|
||||||
|
endif()
|
||||||
@@ -34,6 +34,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
("add", "remove", "move")
|
("add", "remove", "move")
|
||||||
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
||||||
location has a parent that is neither an object nor an array.
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
|
||||||
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
|
||||||
|
location is a proper prefix of its "path" location.
|
||||||
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
||||||
unsuccessful.
|
unsuccessful.
|
||||||
|
|
||||||
@@ -75,3 +79,7 @@ is thrown. In any case, the original value is not changed: the patch is applied
|
|||||||
- Added in version 2.0.0.
|
- Added in version 2.0.0.
|
||||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||||
target location has a non-object/non-array parent in version 3.13.0.
|
target location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||||
|
location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||||
|
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ No guarantees, value may be corrupted by an unsuccessful patch operation.
|
|||||||
("add", "remove", "move")
|
("add", "remove", "move")
|
||||||
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
||||||
location has a parent that is neither an object nor an array.
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
|
||||||
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
|
||||||
|
location is a proper prefix of its "path" location.
|
||||||
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
||||||
unsuccessful.
|
unsuccessful.
|
||||||
|
|
||||||
@@ -72,3 +76,7 @@ function throws an exception.
|
|||||||
- Added in version 3.11.0.
|
- Added in version 3.11.0.
|
||||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||||
target location has a non-object/non-array parent in version 3.13.0.
|
target location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||||
|
location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||||
|
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
|
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
|
||||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
|
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# JSON_NO_THREAD_LOCAL
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_THREAD_LOCAL
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
|
||||||
|
toolchain does not support it.
|
||||||
|
|
||||||
|
Copying a value and comparing two values both descend into the first levels by letting the containers copy or compare
|
||||||
|
themselves, and finish whatever is nested deeper than that without the call stack, so that neither can exhaust the stack
|
||||||
|
however deeply the values are nested. Each counts the levels it has descended into in a `#!cpp thread_local` variable, as
|
||||||
|
a counter shared between threads would be raced.
|
||||||
|
|
||||||
|
Without those counters, no descent can be bounded safely, so objects and arrays are copied and compared without the call
|
||||||
|
stack right away. Both keep working exactly as they do otherwise - the same values come out, the same comparisons hold,
|
||||||
|
and deeply nested values are handled just as safely - but both are slower, because the containers no longer copy or
|
||||||
|
compare themselves. Copying the benchmark documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer, and
|
||||||
|
comparing two equal ones 10% (`citm_catalog.json`) to 90% (`canada.json`) longer.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_NO_THREAD_LOCAL` is not defined.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_NO_THREAD_LOCAL
|
||||||
|
```
|
||||||
|
|
||||||
|
The library defines it by itself for Clang targeting MinGW, which does not survive the `#!cpp thread_local` storage:
|
||||||
|
copying a value segfaults there, with both old and current Clang versions, while GCC targeting MinGW is unaffected.
|
||||||
|
Copying and comparing fall back to working without the call stack there, as they do whenever the macro is defined.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The code below forces the library not to use `#!cpp thread_local` storage.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_THREAD_LOCAL 1
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.12.1.
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
[json.exception.type_error.302] type must be boolean, but is string
|
[json.exception.type_error.302] type must be boolean, but is string
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
|||||||
|
|
||||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||||
|
|
||||||
|
## `JSON_NO_THREAD_LOCAL`
|
||||||
|
|
||||||
|
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||||
|
always avoid the call stack rather than descending into a bounded number of levels first, which is slower but yields the
|
||||||
|
same values and the same comparisons.
|
||||||
|
|
||||||
|
See [full documentation of `JSON_NO_THREAD_LOCAL`](../api/macros/json_no_thread_local.md).
|
||||||
|
|
||||||
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
|
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
|
||||||
|
|
||||||
When defined, the library will not create a compiler warning when a different version of the library was already
|
When defined, the library will not create a compiler warning when a different version of the library was already
|
||||||
|
|||||||
@@ -933,6 +933,34 @@ BSON stores the length of documents, arrays, strings, and binary values in a sig
|
|||||||
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
|
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
|
||||||
[`from_bson`](../api/basic_json/from_bson.md) rejected.
|
[`from_bson`](../api/basic_json/from_bson.md) rejected.
|
||||||
|
|
||||||
|
### json.exception.out_of_range.413
|
||||||
|
|
||||||
|
A JSON Patch `remove` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), a `remove` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) or `null` has no members or elements to remove.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, this situation was silently ignored (the `remove` operation had no effect).
|
||||||
|
|
||||||
|
### json.exception.out_of_range.414
|
||||||
|
|
||||||
|
A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"path"` location. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) (section 4.4), a location cannot be moved into one of its own children.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.
|
||||||
|
|
||||||
## Further exceptions
|
## Further exceptions
|
||||||
|
|
||||||
This exception is thrown in case of errors that cannot be classified with the
|
This exception is thrown in case of errors that cannot be classified with the
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ nav:
|
|||||||
- 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md
|
- 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md
|
||||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||||
|
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||||
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
|
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
|
||||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||||
|
|||||||
@@ -398,6 +398,17 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename ConstructibleObjectType>
|
||||||
|
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(obj.reserve(size), void())
|
||||||
|
{
|
||||||
|
obj.reserve(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ConstructibleObjectType>
|
||||||
|
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||||
|
{}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||||
@@ -409,6 +420,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
|||||||
|
|
||||||
ConstructibleObjectType ret;
|
ConstructibleObjectType ret;
|
||||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||||
|
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
||||||
for (const auto& p : *inner_object)
|
for (const auto& p : *inner_object)
|
||||||
{
|
{
|
||||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||||
|
|||||||
@@ -1075,8 +1075,8 @@ char* to_chars(char* first, const char* last, FloatType value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
if (value == 0) // +-0
|
if (value == 0) // +-0
|
||||||
{
|
{
|
||||||
@@ -1087,7 +1087,7 @@ char* to_chars(char* first, const char* last, FloatType value)
|
|||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087
|
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087
|
||||||
// for a discussion.
|
// for a discussion.
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wweak-vtables")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -287,5 +287,5 @@ class other_error : public exception
|
|||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -149,10 +149,11 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
||||||
|
, discard_number_values(discard_number_values_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -1279,6 +1280,58 @@ scan_number_done:
|
|||||||
// we are done scanning a number)
|
// we are done scanning a number)
|
||||||
unget();
|
unget();
|
||||||
|
|
||||||
|
// If the caller does not need the converted value (only whether the
|
||||||
|
// input is syntactically valid; see json_sax_acceptor/accept()), an
|
||||||
|
// unsigned/integer token can be reported without calling
|
||||||
|
// strtoull()/strtoll() at all, *provided* we can already tell from
|
||||||
|
// the digit count alone that the conversion cannot overflow 64 bits.
|
||||||
|
// Such tokens are always finite and are accepted unconditionally by
|
||||||
|
// the parser regardless of their actual value (parser::sax_parse_internal()
|
||||||
|
// never checks finiteness for value_unsigned/value_integer), so the
|
||||||
|
// classification below is all that is needed.
|
||||||
|
//
|
||||||
|
// A decimal number with up to 18 digits is always representable in
|
||||||
|
// both std::uint64_t and std::int64_t (18 nines is ~1e18, well below
|
||||||
|
// both UINT64_MAX ~1.8e19 and INT64_MAX ~9.2e18), so strtoull()/strtoll()
|
||||||
|
// could not have set errno to ERANGE for it. Numbers with more digits
|
||||||
|
// (rare in practice) fall through to the exact code below, unchanged,
|
||||||
|
// so their handling -- including reclassification to value_float when
|
||||||
|
// the value overflows 64 bits, and rejection when it is not even
|
||||||
|
// finite as a double -- is bit-for-bit identical to before this
|
||||||
|
// optimization.
|
||||||
|
//
|
||||||
|
// Note this reasons about std::uint64_t/std::int64_t, not about
|
||||||
|
// number_unsigned_t/number_integer_t (BasicJsonType's own, possibly
|
||||||
|
// narrower, template parameters -- e.g. std::uint32_t). That is fine
|
||||||
|
// *only* because discard_number_values is exclusively set by
|
||||||
|
// accept() (see json.hpp), and accept() always parses through the
|
||||||
|
// library's own json_sax_acceptor -- never a user-supplied SAX
|
||||||
|
// consumer -- whose number_unsigned()/number_integer()/number_float()
|
||||||
|
// callbacks unconditionally discard their argument and return true.
|
||||||
|
// So for every caller that can reach this branch, neither the token
|
||||||
|
// classification below nor the eventual (possibly narrowed, and on
|
||||||
|
// this fast path left stale/unset) value_unsigned/value_integer is
|
||||||
|
// ever consulted -- an unsigned/integer token is accepted outright,
|
||||||
|
// and even a >18-digit token that this fast path deliberately falls
|
||||||
|
// through for is, once reclassified to value_float, still finite
|
||||||
|
// (and thus accepted) for any digit count that fits in number_unsigned_t
|
||||||
|
// or number_integer_t regardless of that type's width. If this
|
||||||
|
// function is ever taught to run with discard_number_values true for
|
||||||
|
// a caller that *does* read the converted value, this reasoning (and
|
||||||
|
// the fast path below) would need to be revisited.
|
||||||
|
if (discard_number_values)
|
||||||
|
{
|
||||||
|
constexpr std::size_t safe_digit_count = 18;
|
||||||
|
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||||
|
{
|
||||||
|
return token_type::value_unsigned;
|
||||||
|
}
|
||||||
|
if (number_type == token_type::value_integer && token_buffer.size() - 1 <= safe_digit_count)
|
||||||
|
{
|
||||||
|
return token_type::value_integer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
|
||||||
@@ -1393,8 +1446,7 @@ scan_number_done:
|
|||||||
*/
|
*/
|
||||||
char_int_type get()
|
char_int_type get()
|
||||||
{
|
{
|
||||||
++position.chars_read_total;
|
advance_position();
|
||||||
++position.chars_read_current_line;
|
|
||||||
|
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
@@ -1406,6 +1458,23 @@ scan_number_done:
|
|||||||
current = ia.get_character();
|
current = ia.get_character();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return track_after_read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shared head of get() / get_ignoring_pending_unget(): bump the
|
||||||
|
/// per-character position counters (line-count-on-'\n' bookkeeping is
|
||||||
|
/// handled afterwards, in track_after_read(), once `current` is known)
|
||||||
|
void advance_position() noexcept
|
||||||
|
{
|
||||||
|
++position.chars_read_total;
|
||||||
|
++position.chars_read_current_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shared tail of get() / get_ignoring_pending_unget(): capture the
|
||||||
|
/// character for error messages (if needed) and update line/column
|
||||||
|
/// bookkeeping for the character now in `current`
|
||||||
|
char_int_type track_after_read()
|
||||||
|
{
|
||||||
// seekable adapters reconstruct the token lazily on error (see
|
// seekable adapters reconstruct the token lazily on error (see
|
||||||
// get_token_string), so the eager per-character copy is skipped
|
// get_token_string), so the eager per-character copy is skipped
|
||||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
@@ -1419,6 +1488,29 @@ scan_number_done:
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief like get(), but for call sites that can prove no unget() is pending
|
||||||
|
|
||||||
|
get() has to check the `next_unget` flag on every call, because a
|
||||||
|
previous token may have ended with unget() (e.g. scan_number() always
|
||||||
|
ungets the character that terminated the number, so the next call to
|
||||||
|
scan() can see it again). skip_whitespace() reads that first,
|
||||||
|
possibly-ungotten character via a plain get(), but every further
|
||||||
|
character it reads is guaranteed to be a fresh read: nothing between
|
||||||
|
those calls invokes unget(). This variant skips the (otherwise always
|
||||||
|
false) next_unget branch for those calls; it is not a general
|
||||||
|
replacement for get().
|
||||||
|
*/
|
||||||
|
char_int_type get_ignoring_pending_unget()
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!next_unget);
|
||||||
|
|
||||||
|
advance_position();
|
||||||
|
current = ia.get_character();
|
||||||
|
|
||||||
|
return track_after_read();
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
||||||
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -1612,13 +1704,37 @@ scan_number_done:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// whether `current` is one of the four JSON whitespace characters
|
||||||
|
bool current_is_whitespace() const noexcept
|
||||||
|
{
|
||||||
|
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
|
||||||
|
}
|
||||||
|
|
||||||
void skip_whitespace()
|
void skip_whitespace()
|
||||||
{
|
{
|
||||||
|
// the first character may be a pending unget() left over from the
|
||||||
|
// previous token (see get_ignoring_pending_unget()); every
|
||||||
|
// subsequent character read by this loop is guaranteed fresh, since
|
||||||
|
// nothing below calls unget()
|
||||||
|
get();
|
||||||
|
|
||||||
|
if (!current_is_whitespace())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is written as an if-guarded do-while (rather than a plain
|
||||||
|
// while loop) because that shape is what lets both GCC and Clang
|
||||||
|
// keep the input adapter's read pointer in a register across
|
||||||
|
// iterations; the equivalent while-loop measurably defeated that
|
||||||
|
// optimization in testing, turning long whitespace runs (e.g. the
|
||||||
|
// indentation of pretty-printed JSON) from a register-only loop
|
||||||
|
// into one that reloads the pointer from memory every character
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
get();
|
get_ignoring_pending_unget();
|
||||||
}
|
}
|
||||||
while (current == ' ' || current == '\t' || current == '\n' || current == '\r');
|
while (current_is_whitespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
token_type scan()
|
token_type scan()
|
||||||
@@ -1754,6 +1870,13 @@ scan_number_done:
|
|||||||
const char_int_type decimal_point_char = '.';
|
const char_int_type decimal_point_char = '.';
|
||||||
/// the position of the decimal point in the input
|
/// the position of the decimal point in the input
|
||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
|
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||||
|
/// token classification and never looks at the converted numeric value;
|
||||||
|
/// when set, scan_number() may skip strtoull()/strtoll() for
|
||||||
|
/// value_unsigned/value_integer tokens whose digit count guarantees they
|
||||||
|
/// fit into 64 bits (see scan_number())
|
||||||
|
const bool discard_number_values = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -72,9 +72,10 @@ class parser
|
|||||||
parser_callback_t<BasicJsonType> cb = nullptr,
|
parser_callback_t<BasicJsonType> cb = nullptr,
|
||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas_ = false)
|
const bool ignore_trailing_commas_ = false,
|
||||||
|
const bool discard_number_values_ = false)
|
||||||
: callback(std::move(cb))
|
: callback(std::move(cb))
|
||||||
, m_lexer(std::move(adapter), ignore_comments)
|
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
||||||
, allow_exceptions(allow_exceptions_)
|
, allow_exceptions(allow_exceptions_)
|
||||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.hpp>
|
#include <nlohmann/detail/value_t.hpp>
|
||||||
@@ -206,10 +207,10 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
namespace std
|
namespace std
|
||||||
{
|
{
|
||||||
|
|
||||||
#if defined(__clang__)
|
|
||||||
// Fix: https://github.com/nlohmann/json/issues/1401
|
// Fix: https://github.com/nlohmann/json/issues/1401
|
||||||
#pragma clang diagnostic push
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic ignored "-Wmismatched-tags"
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
|
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags")
|
||||||
#endif
|
#endif
|
||||||
template<typename IteratorType>
|
template<typename IteratorType>
|
||||||
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
|
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
|
||||||
@@ -224,7 +225,7 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
|
|||||||
::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
|
::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
|
||||||
};
|
};
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|||||||
@@ -748,6 +748,20 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the reference token consists only of digits at this point (cf. checks
|
||||||
|
// above); however, its numeric value might not be representable, in which
|
||||||
|
// case array_index() would throw out_of_range.404/410 -- contains() must
|
||||||
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
// the array index cannot be represented as size_type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,6 +186,15 @@
|
|||||||
#define JSON_NO_UNIQUE_ADDRESS
|
#define JSON_NO_UNIQUE_ADDRESS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||||
|
// constructor uses to bound its descent: every test that copies a value
|
||||||
|
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||||
|
// GCC targeting MinGW and with every other toolchain the library is tested on.
|
||||||
|
// Copying works the same way without the counter, only more slowly.
|
||||||
|
#if !defined(JSON_NO_THREAD_LOCAL) && defined(__clang__) && defined(__MINGW32__)
|
||||||
|
#define JSON_NO_THREAD_LOCAL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
// disable documentation warnings on clang
|
// disable documentation warnings on clang
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
|
|||||||
@@ -1850,8 +1850,8 @@ class binary_writer
|
|||||||
void write_compact_float(const number_float_t n, detail::input_format_t format)
|
void write_compact_float(const number_float_t n, detail::input_format_t format)
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||||
@@ -1870,7 +1870,7 @@ class binary_writer
|
|||||||
write_number(n);
|
write_number(n);
|
||||||
}
|
}
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+722
-69
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -17,7 +17,7 @@
|
|||||||
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN
|
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION
|
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_FEATURE
|
#undef JSON_HEDLEY_CLANG_HAS_FEATURE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_WARNING
|
#undef JSON_HEDLEY_CLANG_HAS_WARNING
|
||||||
@@ -108,7 +108,10 @@
|
|||||||
#undef JSON_HEDLEY_PELLES_VERSION_CHECK
|
#undef JSON_HEDLEY_PELLES_VERSION_CHECK
|
||||||
#undef JSON_HEDLEY_PGI_VERSION
|
#undef JSON_HEDLEY_PGI_VERSION
|
||||||
#undef JSON_HEDLEY_PGI_VERSION_CHECK
|
#undef JSON_HEDLEY_PGI_VERSION_CHECK
|
||||||
|
#undef JSON_HEDLEY_PRAGMA
|
||||||
#undef JSON_HEDLEY_PREDICT
|
#undef JSON_HEDLEY_PREDICT
|
||||||
|
#undef JSON_HEDLEY_PREDICT_FALSE
|
||||||
|
#undef JSON_HEDLEY_PREDICT_TRUE
|
||||||
#undef JSON_HEDLEY_PRINTF_FORMAT
|
#undef JSON_HEDLEY_PRINTF_FORMAT
|
||||||
#undef JSON_HEDLEY_PRIVATE
|
#undef JSON_HEDLEY_PRIVATE
|
||||||
#undef JSON_HEDLEY_PUBLIC
|
#undef JSON_HEDLEY_PUBLIC
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+59
-1
@@ -72,7 +72,12 @@ target_compile_options(test_main PUBLIC
|
|||||||
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
|
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
|
||||||
# makes MSVC flag the code following its call in binary_reader.hpp
|
# makes MSVC flag the code following its call in binary_reader.hpp
|
||||||
# as unreachable for that instantiation, in both Debug and Release
|
# as unreachable for that instantiation, in both Debug and Release
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702>
|
# Disable warning C4503: decorated name length exceeded, name was truncated; the deep
|
||||||
|
# copy support added for #5387 pushes the mangled name of
|
||||||
|
# std::allocator_traits<...>::construct for the custom-base-class
|
||||||
|
# test's map type past VS2015's limit. The name is only used for
|
||||||
|
# debug info, so truncation does not affect the build.
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702;/wd4503>
|
||||||
# https://github.com/nlohmann/json/issues/1114
|
# https://github.com/nlohmann/json/issues/1114
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
||||||
|
|
||||||
@@ -125,6 +130,51 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
|||||||
# add unit tests
|
# add unit tests
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
# Generate the leak checks for every JSON_HEDLEY_* macro defined in
|
||||||
|
# hedley.hpp; tests/src/unit-no-macro-leak.cpp #include-s the result after
|
||||||
|
# nlohmann/json.hpp (see issue #5408). Using the shared
|
||||||
|
# cmake/scripts/gen_hedley_undef_check.cmake script (also used by `make
|
||||||
|
# update_hedley_undef`) instead of a hand-maintained list of macro names
|
||||||
|
# means this test can never go stale after a future `make update_hedley`.
|
||||||
|
set(hedley_hpp "${PROJECT_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp")
|
||||||
|
set(hedley_undef_check_script "${PROJECT_SOURCE_DIR}/cmake/scripts/gen_hedley_undef_check.cmake")
|
||||||
|
set(hedley_undef_checks "${PROJECT_BINARY_DIR}/include/hedley_undef_checks.inc")
|
||||||
|
|
||||||
|
# Reconfigure whenever the vendored header or the generator script changes,
|
||||||
|
# so a `cmake --build` after `make update_hedley` does not silently keep a
|
||||||
|
# stale generated file around.
|
||||||
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
|
"${hedley_hpp}"
|
||||||
|
"${hedley_undef_check_script}")
|
||||||
|
|
||||||
|
# Generate once at configure time, so the very first build (before any
|
||||||
|
# custom-command build step has run) already has an up-to-date file.
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DHEDLEY_HPP=${hedley_hpp}"
|
||||||
|
"-DOUTPUT=${hedley_undef_checks}"
|
||||||
|
-DMODE=checks
|
||||||
|
-P "${hedley_undef_check_script}"
|
||||||
|
RESULT_VARIABLE hedley_undef_check_result
|
||||||
|
)
|
||||||
|
if(NOT hedley_undef_check_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to generate ${hedley_undef_checks}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Also (re)generate as a build step, so an incremental build after editing
|
||||||
|
# hedley.hpp without a full reconfigure still picks up the change.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${hedley_undef_checks}"
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DHEDLEY_HPP=${hedley_hpp}"
|
||||||
|
"-DOUTPUT=${hedley_undef_checks}"
|
||||||
|
-DMODE=checks
|
||||||
|
-P "${hedley_undef_check_script}"
|
||||||
|
DEPENDS "${hedley_hpp}" "${hedley_undef_check_script}"
|
||||||
|
COMMENT "Generating Hedley undef leak checks"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_target(generate_hedley_undef_checks DEPENDS "${hedley_undef_checks}")
|
||||||
|
|
||||||
if("${JSON_TestStandards}" STREQUAL "")
|
if("${JSON_TestStandards}" STREQUAL "")
|
||||||
set(test_cxx_standards 11 14 17 20 23)
|
set(test_cxx_standards 11 14 17 20 23)
|
||||||
unset(test_force)
|
unset(test_force)
|
||||||
@@ -163,6 +213,14 @@ foreach(file ${files})
|
|||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
||||||
|
# so its test targets must be built after generate_hedley_undef_checks.
|
||||||
|
foreach(cxx_standard ${test_cxx_standards})
|
||||||
|
if(TARGET test-no-macro-leak_cpp${cxx_standard})
|
||||||
|
add_dependencies(test-no-macro-leak_cpp${cxx_standard} generate_hedley_undef_checks)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
if(json_32bit_test_only)
|
if(json_32bit_test_only)
|
||||||
# Skip all other tests in this file
|
# Skip all other tests in this file
|
||||||
return()
|
return()
|
||||||
|
|||||||
@@ -214,323 +214,4 @@ static void BinaryToCbor(benchmark::State& state)
|
|||||||
}
|
}
|
||||||
BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12);
|
BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// parse binary formats
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Only MessagePack had a read benchmark (FromMsgpack above, left untouched so
|
|
||||||
// its numbers stay comparable across releases). The benchmarks below cover the
|
|
||||||
// other formats, and read from a contiguous buffer as well as from a FILE*:
|
|
||||||
// most callers pass a container, and the two adapters compile to different
|
|
||||||
// code. The test data repository ships JSON only, so the input for each is
|
|
||||||
// derived at setup time by serializing a parsed test file.
|
|
||||||
|
|
||||||
/// binary format to benchmark; the _optimized variants add UBJSON/BJData size
|
|
||||||
/// and type annotations, which the readers handle in a separate code path
|
|
||||||
enum class binary_format
|
|
||||||
{
|
|
||||||
cbor,
|
|
||||||
msgpack,
|
|
||||||
ubjson,
|
|
||||||
ubjson_optimized,
|
|
||||||
bjdata,
|
|
||||||
bjdata_optimized,
|
|
||||||
bson
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case binary_format::cbor:
|
|
||||||
return json::to_cbor(j);
|
|
||||||
case binary_format::msgpack:
|
|
||||||
return json::to_msgpack(j);
|
|
||||||
case binary_format::ubjson:
|
|
||||||
return json::to_ubjson(j);
|
|
||||||
case binary_format::ubjson_optimized:
|
|
||||||
return json::to_ubjson(j, true, true);
|
|
||||||
case binary_format::bjdata:
|
|
||||||
return json::to_bjdata(j);
|
|
||||||
case binary_format::bjdata_optimized:
|
|
||||||
return json::to_bjdata(j, true, true);
|
|
||||||
case binary_format::bson:
|
|
||||||
default:
|
|
||||||
return json::to_bson(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_format format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case binary_format::cbor:
|
|
||||||
return json::from_cbor(bytes);
|
|
||||||
case binary_format::msgpack:
|
|
||||||
return json::from_msgpack(bytes);
|
|
||||||
case binary_format::ubjson:
|
|
||||||
case binary_format::ubjson_optimized:
|
|
||||||
return json::from_ubjson(bytes);
|
|
||||||
case binary_format::bjdata:
|
|
||||||
case binary_format::bjdata_optimized:
|
|
||||||
return json::from_bjdata(bytes);
|
|
||||||
case binary_format::bson:
|
|
||||||
default:
|
|
||||||
return json::from_bson(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static json from_binary(std::FILE* file, const binary_format format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case binary_format::cbor:
|
|
||||||
return json::from_cbor(file);
|
|
||||||
case binary_format::msgpack:
|
|
||||||
return json::from_msgpack(file);
|
|
||||||
case binary_format::ubjson:
|
|
||||||
case binary_format::ubjson_optimized:
|
|
||||||
return json::from_ubjson(file);
|
|
||||||
case binary_format::bjdata:
|
|
||||||
case binary_format::bjdata_optimized:
|
|
||||||
return json::from_bjdata(file);
|
|
||||||
case binary_format::bson:
|
|
||||||
default:
|
|
||||||
return json::from_bson(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief serialize a parsed test file to @a format
|
|
||||||
|
|
||||||
Returns an empty vector and marks the benchmark as skipped if the file cannot
|
|
||||||
be represented in the format, rather than letting the exception escape: BSON
|
|
||||||
requires an object at the top level, and several test files are arrays.
|
|
||||||
*/
|
|
||||||
static std::vector<std::uint8_t> binary_input(benchmark::State& state, const char* filename, const binary_format format)
|
|
||||||
{
|
|
||||||
std::ifstream f(filename);
|
|
||||||
std::string const str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
|
||||||
const json j = json::parse(str);
|
|
||||||
|
|
||||||
if (format == binary_format::bson && !j.is_object())
|
|
||||||
{
|
|
||||||
state.SkipWithError("BSON requires an object at the top level");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return to_binary(j, format);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FromBinaryBuffer(benchmark::State& state, const char* filename, const binary_format format)
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
|
|
||||||
if (bytes.empty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto _ : state)
|
|
||||||
{
|
|
||||||
// the value is destroyed outside the timed section, because destroying
|
|
||||||
// a large DOM is not what this benchmark measures
|
|
||||||
state.PauseTiming();
|
|
||||||
auto* j = new json();
|
|
||||||
state.ResumeTiming();
|
|
||||||
|
|
||||||
*j = from_binary(bytes, format);
|
|
||||||
|
|
||||||
state.PauseTiming();
|
|
||||||
delete j;
|
|
||||||
state.ResumeTiming();
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetBytesProcessed(state.iterations() * bytes.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / floats, TEST_DATA_DIRECTORY "/regression/floats.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson_optimized);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson_optimized);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
|
|
||||||
// BSON requires an object at the top level, so the array-rooted test files
|
|
||||||
// (jeopardy and the regression files) cannot be captured here
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
|
||||||
|
|
||||||
static void FromBinaryFile(benchmark::State& state, const char* filename, const binary_format format)
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
|
|
||||||
if (bytes.empty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* tmp = "benchmark_input.bin";
|
|
||||||
std::ofstream o(tmp, std::ios::binary);
|
|
||||||
o.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
|
||||||
o.flush();
|
|
||||||
o.close();
|
|
||||||
|
|
||||||
for (auto _ : state)
|
|
||||||
{
|
|
||||||
state.PauseTiming();
|
|
||||||
auto* j = new json();
|
|
||||||
auto* file = std::fopen(tmp, "rb");
|
|
||||||
state.ResumeTiming();
|
|
||||||
|
|
||||||
*j = from_binary(file, format);
|
|
||||||
|
|
||||||
state.PauseTiming();
|
|
||||||
std::fclose(file);
|
|
||||||
delete j;
|
|
||||||
state.ResumeTiming();
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetBytesProcessed(state.iterations() * bytes.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
// parse binary formats: value shapes
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// The test files above are wide and shallow, but the readers' cost is per
|
|
||||||
// container, so these cover the shapes that stress the container handling
|
|
||||||
// itself. Every shape is wrapped in an object so that BSON, which requires an
|
|
||||||
// object at the top level, measures the same value as the other formats.
|
|
||||||
|
|
||||||
/// deeply nested arrays: one container per level, no other work
|
|
||||||
static json make_nested()
|
|
||||||
{
|
|
||||||
json nested = json::array();
|
|
||||||
json* p = &nested;
|
|
||||||
for (std::size_t i = 1; i < 1000; ++i)
|
|
||||||
{
|
|
||||||
p->push_back(json::array());
|
|
||||||
p = &p->operator[](0);
|
|
||||||
}
|
|
||||||
|
|
||||||
json j = json::object();
|
|
||||||
j["data"] = std::move(nested);
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// many sibling containers: maximum container churn, minimum nesting
|
|
||||||
static json make_containers()
|
|
||||||
{
|
|
||||||
json data = json::array();
|
|
||||||
for (std::size_t i = 0; i < 100000; ++i)
|
|
||||||
{
|
|
||||||
data.push_back(json::array({1, 2}));
|
|
||||||
}
|
|
||||||
|
|
||||||
json j = json::object();
|
|
||||||
j["data"] = std::move(data);
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// one flat array of numbers: the scalar decoding path, which must not move
|
|
||||||
static json make_scalars()
|
|
||||||
{
|
|
||||||
json data = json::array();
|
|
||||||
for (std::size_t i = 0; i < 1000000; ++i)
|
|
||||||
{
|
|
||||||
data.push_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
json j = json::object();
|
|
||||||
j["data"] = std::move(data);
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FromBinaryShape(benchmark::State& state, json (*build)(), const binary_format format)
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> bytes = to_binary(build(), format);
|
|
||||||
|
|
||||||
for (auto _ : state)
|
|
||||||
{
|
|
||||||
state.PauseTiming();
|
|
||||||
auto* j = new json();
|
|
||||||
state.ResumeTiming();
|
|
||||||
|
|
||||||
*j = from_binary(bytes, format);
|
|
||||||
|
|
||||||
state.PauseTiming();
|
|
||||||
delete j;
|
|
||||||
state.ResumeTiming();
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetBytesProcessed(state.iterations() * bytes.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / cbor, make_nested, binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
|
||||||
// BSON names every array element, so a large array measures key generation
|
|
||||||
// rather than scalar decoding and is left out here
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
|
|
||||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief parse an indefinite-length CBOR string
|
|
||||||
|
|
||||||
The writer never emits this form, so the input is assembled by hand: 0x7F
|
|
||||||
opens the string, each chunk is a one-character string, and 0xFF closes it.
|
|
||||||
*/
|
|
||||||
static void FromCborChunkedString(benchmark::State& state, const std::size_t chunks)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> bytes;
|
|
||||||
bytes.reserve(2 * chunks + 2);
|
|
||||||
bytes.push_back(0x7F);
|
|
||||||
for (std::size_t i = 0; i < chunks; ++i)
|
|
||||||
{
|
|
||||||
bytes.push_back(0x61); // string of length 1
|
|
||||||
bytes.push_back(0x61); // 'a'
|
|
||||||
}
|
|
||||||
bytes.push_back(0xFF);
|
|
||||||
|
|
||||||
for (auto _ : state)
|
|
||||||
{
|
|
||||||
json j = json::from_cbor(bytes);
|
|
||||||
benchmark::DoNotOptimize(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.SetBytesProcessed(state.iterations() * bytes.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
|
||||||
|
|
||||||
BENCHMARK_MAIN();
|
BENCHMARK_MAIN();
|
||||||
|
|||||||
@@ -15,6 +15,15 @@
|
|||||||
namespace utils
|
namespace utils
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Some tests intentionally discard the [[nodiscard]]/JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
|
// return value of a call they only make to exercise its side effects (e.g. checking
|
||||||
|
// that it does not throw). A plain (void) cast on the call expression does not
|
||||||
|
// suppress GCC's warning for functions using the GNU __attribute__((warn_unused_result))
|
||||||
|
// form (as opposed to the C++17 [[nodiscard]] attribute) -- passing the value into an
|
||||||
|
// ordinary function call does.
|
||||||
|
template<typename T>
|
||||||
|
inline void ignore_return_value(T&& /*unused*/) noexcept {}
|
||||||
|
|
||||||
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
||||||
{
|
{
|
||||||
std::ifstream file(filename, std::ios::binary);
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
|||||||
@@ -216,6 +216,57 @@ TEST_CASE("controlled bad_alloc")
|
|||||||
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
|
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
|
||||||
next_construct_fails = false;
|
next_construct_fails = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("basic_json(const basic_json&) of a deeply nested value (#5387)")
|
||||||
|
{
|
||||||
|
// Copying a value nested deeper than the descent bound builds the
|
||||||
|
// copy from the top down: every value whose own copy has not been
|
||||||
|
// made yet stays a null value until it is. Failing an allocation
|
||||||
|
// part-way through is what proves such a half-built copy can still
|
||||||
|
// be destroyed.
|
||||||
|
//
|
||||||
|
// Which path the failure lands in depends on the build: the first
|
||||||
|
// allocation of a copy belongs to the outermost level, so here it
|
||||||
|
// is the descending one. Built with JSON_NO_THREAD_LOCAL - as the
|
||||||
|
// ci_test_no_thread_local target builds the whole suite - no
|
||||||
|
// descent is made at all and the very same failure lands in the
|
||||||
|
// iterative path instead, part-way through its worklist.
|
||||||
|
const auto check_deep_copy = [](bool objects)
|
||||||
|
{
|
||||||
|
CAPTURE(objects);
|
||||||
|
|
||||||
|
next_construct_fails = false;
|
||||||
|
|
||||||
|
// deeper than the 128 levels the copy constructor descends into
|
||||||
|
const std::size_t depth = 300;
|
||||||
|
|
||||||
|
my_json j = 1;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
if (objects)
|
||||||
|
{
|
||||||
|
my_json wrapper = my_json::object();
|
||||||
|
wrapper["a"] = std::move(j);
|
||||||
|
j = std::move(wrapper);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
j = my_json::array({std::move(j)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
CHECK_NOTHROW(my_json(j));
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
CHECK_THROWS_AS(my_json(j), std::bad_alloc&);
|
||||||
|
next_construct_fails = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
check_deep_copy(false);
|
||||||
|
check_deep_copy(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,54 @@ class huge_binary_t : public std::vector<std::uint8_t>
|
|||||||
using huge_binary_json = nlohmann::basic_json <
|
using huge_binary_json = nlohmann::basic_json <
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||||
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
|
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
|
||||||
|
|
||||||
|
// a string type that can be made to report a size beyond INT32_MAX without
|
||||||
|
// allocating that much memory, so BSON length overflow can be tested for
|
||||||
|
// strings and (embedded) documents as well, following the same idea as
|
||||||
|
// huge_binary_t.
|
||||||
|
//
|
||||||
|
// Unlike huge_binary_t (which is only ever used as the BSON *value* type),
|
||||||
|
// this type doubles as basic_json's StringType and is therefore also used
|
||||||
|
// for *object keys* (e.g. "s" or "nested" below). Only the designated test
|
||||||
|
// value is meant to lie about its size - if every huge_string_t (including
|
||||||
|
// keys) reported a huge size, the running totals computed while walking the
|
||||||
|
// BSON document (see calc_bson_object_size & friends in binary_writer.hpp)
|
||||||
|
// would need more than 32 bits, and on platforms where std::size_t is only
|
||||||
|
// 32 bits wide that arithmetic would silently wrap around, producing wrong
|
||||||
|
// (or even unguarded) lengths. The fake size is therefore opt-in via
|
||||||
|
// as_huge(), and plain strings - in particular object keys - keep reporting
|
||||||
|
// their real, small size.
|
||||||
|
class huge_string_t : public std::string
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using std::string::string;
|
||||||
|
huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||||
|
|
||||||
|
// returns a copy of @a s whose size() pretends to be huge
|
||||||
|
static huge_string_t as_huge(const std::string& s)
|
||||||
|
{
|
||||||
|
huge_string_t result(s);
|
||||||
|
result.pretend_huge = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type size() const noexcept
|
||||||
|
{
|
||||||
|
if (pretend_huge)
|
||||||
|
{
|
||||||
|
// one byte more than the BSON length field can represent
|
||||||
|
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
|
||||||
|
}
|
||||||
|
return std::string::size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool pretend_huge = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
using huge_string_json = nlohmann::basic_json <
|
||||||
|
std::map, std::vector, huge_string_t, bool, std::int64_t, std::uint64_t,
|
||||||
|
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("BSON")
|
TEST_CASE("BSON")
|
||||||
@@ -104,6 +152,11 @@ TEST_CASE("BSON")
|
|||||||
}
|
}
|
||||||
|
|
||||||
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
||||||
|
{
|
||||||
|
// out_of_range.412 is thrown from a single shared helper
|
||||||
|
// (to_bson_length) that guards the BSON length fields of binary
|
||||||
|
// values, strings, and (embedded) documents alike
|
||||||
|
SECTION("binary")
|
||||||
{
|
{
|
||||||
huge_binary_json j;
|
huge_binary_json j;
|
||||||
j["b"] = huge_binary_json::binary(huge_binary_t{});
|
j["b"] = huge_binary_json::binary(huge_binary_t{});
|
||||||
@@ -111,6 +164,27 @@ TEST_CASE("BSON")
|
|||||||
CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
|
CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("string")
|
||||||
|
{
|
||||||
|
huge_string_json j;
|
||||||
|
j["s"] = huge_string_t::as_huge("value");
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("document")
|
||||||
|
{
|
||||||
|
// an oversized string nested one level deep makes the
|
||||||
|
// *embedded* document's own length exceed INT32_MAX as well
|
||||||
|
huge_string_json nested;
|
||||||
|
nested["s"] = huge_string_t::as_huge("value");
|
||||||
|
huge_string_json j;
|
||||||
|
j["nested"] = nested;
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483674 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("string length must be at least 1")
|
SECTION("string length must be at least 1")
|
||||||
{
|
{
|
||||||
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
||||||
@@ -193,6 +267,23 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("non-empty object with bool from a non-0/1 byte (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): any non-zero byte
|
||||||
|
// is accepted as `true`, not just 0x01
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0D, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x08, // entry: boolean
|
||||||
|
'e', 'n', 't', 'r', 'y', '\x00',
|
||||||
|
0x02, // value = 0x02 (neither 0x00 nor 0x01)
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected = { { "entry", true } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-empty object with double")
|
SECTION("non-empty object with double")
|
||||||
{
|
{
|
||||||
json const j =
|
json const j =
|
||||||
@@ -499,6 +590,29 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("array elements with non-conforming keys (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): BSON array element
|
||||||
|
// keys are not checked against the required decimal sequence
|
||||||
|
// "0", "1", "2", ... - elements are taken in encoded order
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x26, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x04, 'e', 'n', 't', 'r', 'y', '\x00', // entry: embedded array
|
||||||
|
|
||||||
|
0x1A, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x10, '5', 0x00, 0x0A, 0x00, 0x00, 0x00, // key "5" (bogus) -> 10
|
||||||
|
0x10, 'x', 0x00, 0x14, 0x00, 0x00, 0x00, // key "x" (non-numeric) -> 20
|
||||||
|
0x10, '1', 0x00, 0x1E, 0x00, 0x00, 0x00, // key "1" (out of order) -> 30
|
||||||
|
0x00, // end marker (embedded array)
|
||||||
|
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected = { { "entry", json::array({10, 20, 30}) } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-empty object with binary member")
|
SECTION("non-empty object with binary member")
|
||||||
{
|
{
|
||||||
const size_t N = 10;
|
const size_t N = 10;
|
||||||
@@ -594,6 +708,31 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): the payload for
|
||||||
|
// binary subtype 0x02 ("old binary") is returned as-is,
|
||||||
|
// including its own inner 4-byte length prefix; it is not
|
||||||
|
// stripped or reinterpreted
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x17, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
|
||||||
|
|
||||||
|
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload)
|
||||||
|
0x68, 0x69, // payload ('h', 'i')
|
||||||
|
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
// the inner length prefix is part of the (unmodified) payload
|
||||||
|
const std::vector<std::uint8_t> expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69};
|
||||||
|
const json expected = { { "entry", json::binary(expected_payload, 0x02) } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Some more complex document")
|
SECTION("Some more complex document")
|
||||||
{
|
{
|
||||||
json const j =
|
json const j =
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ using nlohmann::json;
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class SaxEventLogger
|
class SaxEventLogger
|
||||||
@@ -624,7 +626,8 @@ TEST_CASE("parser class")
|
|||||||
SECTION("overflow")
|
SECTION("overflow")
|
||||||
{
|
{
|
||||||
// overflows during parsing yield an exception
|
// overflows during parsing yield an exception
|
||||||
CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("invalid numbers")
|
SECTION("invalid numbers")
|
||||||
@@ -930,6 +933,98 @@ TEST_CASE("parser class")
|
|||||||
CHECK(accept_helper("+1") == false);
|
CHECK(accept_helper("+1") == false);
|
||||||
CHECK(accept_helper("+0") == false);
|
CHECK(accept_helper("+0") == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("issue #5411 - skip conversion when accept() does not need the numeric value")
|
||||||
|
{
|
||||||
|
// lexer::scan_number() may skip strtoull()/strtoll() for
|
||||||
|
// value_unsigned/value_integer tokens when the caller (e.g.
|
||||||
|
// json::accept()) does not need the converted value, as long
|
||||||
|
// as the digit count alone guarantees no 64-bit overflow (see
|
||||||
|
// the "safe_digit_count" fast path in scan_number()). This
|
||||||
|
// differential test checks that json::accept() (which enables
|
||||||
|
// the fast path) and json::parse() (which never does) always
|
||||||
|
// agree, over a corpus that exercises both the fast path
|
||||||
|
// (<=18 digits) and the untouched, exact fallback path (>=19
|
||||||
|
// digits) -- including reclassification of huge digit-only
|
||||||
|
// integers to a (possibly non-finite) floating-point value.
|
||||||
|
const std::vector<std::pair<std::string, bool>> cases =
|
||||||
|
{
|
||||||
|
// normal small/large integers, both signs
|
||||||
|
{"0", true}, {"1", true}, {"-1", true}, {"42", true}, {"-42", true},
|
||||||
|
{"123456789", true}, {"-123456789", true},
|
||||||
|
|
||||||
|
// digit-count boundary around the 18-digit safe cutoff (both signs)
|
||||||
|
{std::string(17, '9'), true},
|
||||||
|
{std::string(18, '9'), true},
|
||||||
|
{std::string(19, '9'), true},
|
||||||
|
{std::string(20, '9'), true},
|
||||||
|
{"-" + std::string(17, '9'), true},
|
||||||
|
{"-" + std::string(18, '9'), true},
|
||||||
|
{"-" + std::string(19, '9'), true},
|
||||||
|
{"-" + std::string(20, '9'), true},
|
||||||
|
|
||||||
|
// 64-bit boundaries
|
||||||
|
{"9223372036854775807", true}, // INT64_MAX
|
||||||
|
{"-9223372036854775808", true}, // INT64_MIN
|
||||||
|
{"18446744073709551615", true}, // UINT64_MAX
|
||||||
|
{"18446744073709551616", true}, // UINT64_MAX + 1 (overflows uint64_t, finite double)
|
||||||
|
|
||||||
|
// the 28-digit example from the issue: overflows uint64_t
|
||||||
|
// but is finite as a double, so the scanner reclassifies
|
||||||
|
// it to value_float and it is accepted
|
||||||
|
{"9999999999999999999999999999", true},
|
||||||
|
|
||||||
|
// huge digit-only integers that overflow even a double -> rejected
|
||||||
|
{std::string(309, '9'), false},
|
||||||
|
{std::string(400, '9'), false},
|
||||||
|
{"1" + std::string(400, '0'), false},
|
||||||
|
|
||||||
|
// 1e999 / 1e400 style overflow -> rejected
|
||||||
|
{"1e999", false},
|
||||||
|
{"1e400", false},
|
||||||
|
{"-1e999", false},
|
||||||
|
{"1E999", false},
|
||||||
|
|
||||||
|
// values straddling DBL_MAX
|
||||||
|
{"1.7976931348623157e308", true}, // <= DBL_MAX, finite
|
||||||
|
{"1.7976931348623159e308", false}, // > DBL_MAX, overflows to inf
|
||||||
|
|
||||||
|
// a mix of other valid/invalid numeric syntax
|
||||||
|
{"3.14159", true},
|
||||||
|
{"-0.0", true},
|
||||||
|
{"1.0e10", true},
|
||||||
|
{"01", false},
|
||||||
|
{"-", false},
|
||||||
|
{"1.", false},
|
||||||
|
{"1e", false},
|
||||||
|
{"+1", false},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
const std::string& number = c.first;
|
||||||
|
const bool expected = c.second;
|
||||||
|
CAPTURE(number)
|
||||||
|
CAPTURE(expected)
|
||||||
|
|
||||||
|
// accept() takes the fast path (skips conversion when possible)
|
||||||
|
CHECK(json::accept(number) == expected);
|
||||||
|
|
||||||
|
// parse() always performs the full conversion; it must agree
|
||||||
|
json j;
|
||||||
|
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(number), nullptr, false).parse(true, j));
|
||||||
|
CHECK(!j.is_discarded() == expected);
|
||||||
|
|
||||||
|
// wrap in an array so get_token() is exercised beyond the
|
||||||
|
// very first (constructor-time) scan as well
|
||||||
|
std::string wrapped = "[";
|
||||||
|
wrapped += number;
|
||||||
|
wrapped += ",";
|
||||||
|
wrapped += number;
|
||||||
|
wrapped += "]";
|
||||||
|
CHECK(json::accept(wrapped) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1394,6 +1489,71 @@ TEST_CASE("parser class")
|
|||||||
CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
|
CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("issue #5412 - whitespace skipping bookkeeping (compact vs. pretty-printed)")
|
||||||
|
{
|
||||||
|
// lexer::skip_whitespace() reads its first character with get() (to
|
||||||
|
// honor a possibly pending unget() from the previous token) and every
|
||||||
|
// further whitespace character with get_ignoring_pending_unget() (a
|
||||||
|
// get() variant that skips the then-always-false next_unget check).
|
||||||
|
// This must not change the reported byte offset, line, or column of
|
||||||
|
// a syntax error, even when a long run of whitespace containing
|
||||||
|
// multiple newlines is skipped beforehand (as with pretty-printed
|
||||||
|
// input). The expected values below were captured from the
|
||||||
|
// unmodified do-while(get()) loop, so any regression that miscounts
|
||||||
|
// characters or newlines while skipping whitespace changes them.
|
||||||
|
const auto check_error = [](const std::string & input, std::size_t expected_byte,
|
||||||
|
const std::string & expected_what)
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json _ = json::parse(input);
|
||||||
|
FAIL_CHECK("expected a parse_error, but parsing succeeded");
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
CHECK(e.byte == expected_byte);
|
||||||
|
CHECK(std::string(e.what()) == expected_what);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// a nested document, serialized both compactly and pretty-printed
|
||||||
|
// (dump(4)), each truncated right before the final closing '}' so
|
||||||
|
// that the parser hits EOF after skipping all of the (in the
|
||||||
|
// pretty-printed case, substantial) indentation whitespace
|
||||||
|
const json doc =
|
||||||
|
{
|
||||||
|
{"a", 1},
|
||||||
|
{"b", json::array({true, false, nullptr, "x"})},
|
||||||
|
{"c", json::object({{"d", 3.14}, {"e", json::array({1, 2, 3})}})}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string compact = doc.dump();
|
||||||
|
const std::string pretty = doc.dump(4);
|
||||||
|
|
||||||
|
check_error(compact.substr(0, compact.size() - 1), 60,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 60: syntax error while parsing object - unexpected end of input; expected '}'");
|
||||||
|
check_error(pretty.substr(0, pretty.size() - 1), 193,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 17, column 1: syntax error while parsing object - unexpected end of input; expected '}'");
|
||||||
|
|
||||||
|
// an invalid token appearing after several indented, multi-line
|
||||||
|
// whitespace runs vs. the same document without any of that
|
||||||
|
// whitespace
|
||||||
|
check_error(R"({
|
||||||
|
"a": 1,
|
||||||
|
"b": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"c": @
|
||||||
|
})", 70,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 7, column 10: syntax error while parsing value - invalid literal; last read: '\"c\": @'");
|
||||||
|
check_error(R"({"a":1,"b":[true,false],"c":@})", 29,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing value - invalid literal; last read: '\"c\":@'");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("tests found by mutate++")
|
SECTION("tests found by mutate++")
|
||||||
{
|
{
|
||||||
// test case to make sure no comma precedes the first key
|
// test case to make sure no comma precedes the first key
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ using nlohmann::json;
|
|||||||
|
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class SaxEventLogger
|
class SaxEventLogger
|
||||||
@@ -629,7 +631,8 @@ TEST_CASE("parser class")
|
|||||||
SECTION("overflow")
|
SECTION("overflow")
|
||||||
{
|
{
|
||||||
// overflows during parsing yield an exception
|
// overflows during parsing yield an exception
|
||||||
CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("invalid numbers")
|
SECTION("invalid numbers")
|
||||||
|
|||||||
@@ -1389,6 +1389,37 @@ TEST_CASE("value conversion")
|
|||||||
// CHECK(m5["one"] == "eins");
|
// CHECK(m5["one"] == "eins");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("reserve is called on containers that support it (#5406)")
|
||||||
|
{
|
||||||
|
// build a larger object so that a missing/incorrect reserve()
|
||||||
|
// call would be more likely to corrupt or drop elements
|
||||||
|
json j_large;
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
j_large[std::to_string(i)] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("std::unordered_map (supports reserve)")
|
||||||
|
{
|
||||||
|
const auto m = j_large.get<std::unordered_map<std::string, int>>();
|
||||||
|
CHECK(m.size() == 100);
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
CHECK(m.at(std::to_string(i)) == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("std::map (no reserve, fallback path)")
|
||||||
|
{
|
||||||
|
const auto m = j_large.get<std::map<std::string, int>>();
|
||||||
|
CHECK(m.size() == 100);
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
CHECK(m.at(std::to_string(i)) == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("std::multimap")
|
SECTION("std::multimap")
|
||||||
{
|
{
|
||||||
j1.get<std::multimap<std::string, int>>();
|
j1.get<std::multimap<std::string, int>>();
|
||||||
|
|||||||
@@ -68,6 +68,72 @@ TEST_CASE("Better diagnostics with positions")
|
|||||||
CHECK(j.end_pos() == root.size());
|
CHECK(j.end_pos() == root.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("copying keeps the positions of nested values (#5387)")
|
||||||
|
{
|
||||||
|
// Values nested deeper than the copy constructor's descent bound are
|
||||||
|
// copied without the call stack, on a path that has to carry the
|
||||||
|
// positions over itself; shallower ones copy their containers, which
|
||||||
|
// bring the positions along. Both sides of the bound are checked here.
|
||||||
|
const auto check_copy = [](std::size_t depth, bool objects)
|
||||||
|
{
|
||||||
|
CAPTURE(depth)
|
||||||
|
CAPTURE(objects)
|
||||||
|
|
||||||
|
const std::string opening = objects ? R"({"a":)" : "[";
|
||||||
|
const std::string closing = objects ? "}" : "]";
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += opening;
|
||||||
|
}
|
||||||
|
text += "12";
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json original = json::parse(text);
|
||||||
|
const json copy(original); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
|
||||||
|
const json* o = &original;
|
||||||
|
const json* c = ©
|
||||||
|
for (std::size_t level = 0; level <= depth; ++level)
|
||||||
|
{
|
||||||
|
CAPTURE(level)
|
||||||
|
REQUIRE(c->start_pos() == o->start_pos());
|
||||||
|
REQUIRE(c->end_pos() == o->end_pos());
|
||||||
|
|
||||||
|
if (level < depth)
|
||||||
|
{
|
||||||
|
o = objects ? &o->at("a") : &o->at(0);
|
||||||
|
c = objects ? &c->at("a") : &c->at(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto check_arrays = [&check_copy](std::size_t depth)
|
||||||
|
{
|
||||||
|
check_copy(depth, false);
|
||||||
|
};
|
||||||
|
const auto check_objects = [&check_copy](std::size_t depth)
|
||||||
|
{
|
||||||
|
check_copy(depth, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
check_arrays(1);
|
||||||
|
check_arrays(127);
|
||||||
|
check_arrays(128);
|
||||||
|
check_arrays(129);
|
||||||
|
check_arrays(300);
|
||||||
|
|
||||||
|
check_objects(1);
|
||||||
|
check_objects(127);
|
||||||
|
check_objects(128);
|
||||||
|
check_objects(129);
|
||||||
|
check_objects(300);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("JSON patch add to primitive parent (#4292)")
|
SECTION("JSON patch add to primitive parent (#4292)")
|
||||||
{
|
{
|
||||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||||
|
|||||||
@@ -273,5 +273,62 @@ TEST_CASE("Regression tests for extended diagnostics")
|
|||||||
CHECK(j1["numbers"]["two"] == 2);
|
CHECK(j1["numbers"]["two"] == 2);
|
||||||
CHECK(j1["string"] == "t");
|
CHECK(j1["string"] == "t");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Regression test for issue #5387 - copying keeps the parents of nested values")
|
||||||
|
{
|
||||||
|
// A value nested deeper than the copy constructor's descent bound is
|
||||||
|
// copied without the call stack. Every container that path creates has
|
||||||
|
// to have the parents of its children set, or the JSON Pointer in the
|
||||||
|
// diagnostic is cut short.
|
||||||
|
const std::size_t depth = 300;
|
||||||
|
|
||||||
|
SECTION("objects")
|
||||||
|
{
|
||||||
|
json j = "not a number";
|
||||||
|
std::string pointer;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
j = json{{"a", j}};
|
||||||
|
pointer += "/a";
|
||||||
|
}
|
||||||
|
|
||||||
|
json const copy(j); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
|
||||||
|
const json* inner = ©
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
inner = &inner->at("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||||
|
int i = 0;
|
||||||
|
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), json::type_error);
|
||||||
|
CHECK(i == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("arrays")
|
||||||
|
{
|
||||||
|
json j = "not a number";
|
||||||
|
std::string pointer;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
j = json::array({j});
|
||||||
|
pointer += "/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
json const copy(j); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
|
||||||
|
const json* inner = ©
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
inner = &inner->at(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||||
|
int i = 0;
|
||||||
|
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), json::type_error);
|
||||||
|
CHECK(i == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// This file contains the C++17-only part of unit-items.cpp (structured
|
||||||
|
// bindings support for json::items()). It is kept in a separate
|
||||||
|
// translation unit so the (much larger) unit-items.cpp does not need to
|
||||||
|
// be compiled a second time just for this one SECTION.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
TEST_CASE("items()")
|
||||||
|
{
|
||||||
|
SECTION("object")
|
||||||
|
{
|
||||||
|
SECTION("structured bindings")
|
||||||
|
{
|
||||||
|
json j = { {"A", 1}, {"B", 2} };
|
||||||
|
|
||||||
|
std::map<std::string, int> m;
|
||||||
|
|
||||||
|
for (auto const&[key, value] : j.items())
|
||||||
|
{
|
||||||
|
m.emplace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(j.get<decltype(m)>() == m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -862,22 +862,6 @@ TEST_CASE("items()")
|
|||||||
|
|
||||||
CHECK(counter == 3);
|
CHECK(counter == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
SECTION("structured bindings")
|
|
||||||
{
|
|
||||||
json j = { {"A", 1}, {"B", 2} };
|
|
||||||
|
|
||||||
std::map<std::string, int> m;
|
|
||||||
|
|
||||||
for (auto const&[key, value] : j.items())
|
|
||||||
{
|
|
||||||
m.emplace(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(j.get<decltype(m)>() == m);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("const object")
|
SECTION("const object")
|
||||||
|
|||||||
@@ -1389,6 +1389,192 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch - remove with primitive or null parent (regression #5396)")
|
||||||
|
{
|
||||||
|
// Regression test for https://github.com/nlohmann/json/issues/5396
|
||||||
|
//
|
||||||
|
// RFC 6902 (§4.2) requires the target location of a "remove" operation
|
||||||
|
// to exist. When the target's parent resolves to a primitive value or
|
||||||
|
// null, the operation must fail. Previously operation_remove silently
|
||||||
|
// did nothing in this case (neither the "is_object" nor the "is_array"
|
||||||
|
// branch matched, and there was no final "else"), so the patch appeared
|
||||||
|
// to succeed without changing the document. It now throws
|
||||||
|
// out_of_range.413.
|
||||||
|
|
||||||
|
SECTION("parent is a primitive (number)")
|
||||||
|
{
|
||||||
|
json const doc = {{"a", 1}};
|
||||||
|
json const patch = {{{"op", "remove"}, {"path", "/a/b"}}};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] (/a) cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("parent is a primitive (string)")
|
||||||
|
{
|
||||||
|
json const doc = {{"foo", {{"bar", "a string"}}}};
|
||||||
|
json const patch = {{{"op", "remove"}, {"path", "/foo/bar/baz"}}};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] (/foo/bar) cannot remove value: the JSON Patch 'remove' target's parent is of type string, but must be an object or array", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type string, but must be an object or array", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("top-level document is null")
|
||||||
|
{
|
||||||
|
json const doc = nullptr;
|
||||||
|
json const patch = {{{"op", "remove"}, {"path", "/a"}}};
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type null, but must be an object or array", json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("legitimate removes still work")
|
||||||
|
{
|
||||||
|
// object member
|
||||||
|
json const doc1 = {{"a", 1}, {"b", 2}};
|
||||||
|
json const patch1 = {{{"op", "remove"}, {"path", "/a"}}};
|
||||||
|
CHECK(doc1.patch(patch1) == json({{"b", 2}}));
|
||||||
|
|
||||||
|
// array element
|
||||||
|
json const doc2 = R"([1, 2, 3])"_json;
|
||||||
|
json const patch2 = {{{"op", "remove"}, {"path", "/1"}}};
|
||||||
|
CHECK(doc2.patch(patch2) == R"([1, 3])"_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch - move where 'from' is a proper prefix of 'path' (regression #5397)")
|
||||||
|
{
|
||||||
|
// Regression test for https://github.com/nlohmann/json/issues/5397
|
||||||
|
//
|
||||||
|
// RFC 6902 (§4.4) forbids "from" from being a proper prefix of "path"
|
||||||
|
// for a "move" operation: "a location cannot be moved into one of its
|
||||||
|
// children." "move" is implemented as remove-then-add; for an object
|
||||||
|
// target this happened to throw anyway as a side effect of the "add"
|
||||||
|
// step re-resolving through the now-removed parent, but for an array
|
||||||
|
// target the removal shifted subsequent indices, so "path" silently
|
||||||
|
// re-resolved to a different element and the operation "succeeded"
|
||||||
|
// with a corrupted result. It now throws out_of_range.414 for both
|
||||||
|
// object and array targets.
|
||||||
|
|
||||||
|
SECTION("array target (from the issue)")
|
||||||
|
{
|
||||||
|
json const doc = R"([[1,2],[3]])"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/0"}, {"path", "/0/0"}}};
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-11) cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object target")
|
||||||
|
{
|
||||||
|
json const doc = R"({"a": {"b": 1}})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a/b"}}};
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-15) cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b'", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b'", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("from == path is not a proper prefix and must not be rejected")
|
||||||
|
{
|
||||||
|
// "from" equal to "path" is a no-op move; it is not a *proper*
|
||||||
|
// prefix relationship, so this new check must not reject it.
|
||||||
|
json const doc = R"({"a": 1, "b": 2})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a"}}};
|
||||||
|
CHECK(doc.patch(patch) == doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("raw string prefix that is not a pointer-token prefix must be allowed")
|
||||||
|
{
|
||||||
|
// "/ab" is a string-prefix of "/abc/x" as raw text, but "ab" and
|
||||||
|
// "abc" are different reference tokens, so this is NOT a
|
||||||
|
// pointer-token prefix relationship and the move must succeed.
|
||||||
|
// This is the key case proving the check compares tokens, not
|
||||||
|
// raw pointer text (a naive std::string prefix/rfind check on
|
||||||
|
// the undecoded pointer would wrongly reject this).
|
||||||
|
json const doc = R"({"ab": 1, "abc": {"x": 2}})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/ab"}, {"path", "/abc/x"}}};
|
||||||
|
json const result = R"({"abc": {"x": 1}})"_json;
|
||||||
|
CHECK(doc.patch(patch) == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("escaped reference tokens are compared unescaped")
|
||||||
|
{
|
||||||
|
// "from" is the single token "a/b" (escaped as "a~1b"); "path"
|
||||||
|
// addresses member "x" of that same value, so "from" is a
|
||||||
|
// proper (token-level) prefix of "path" and must be rejected.
|
||||||
|
json const doc = R"({"a/b": {"x": 1}})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/a~1b"}, {"path", "/a~1b/x"}}};
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-17) cannot move value: 'from' path '/a~1b' is a proper prefix of 'path' '/a~1b/x'", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a~1b' is a proper prefix of 'path' '/a~1b/x'", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ordinary valid moves still work")
|
||||||
|
{
|
||||||
|
// unrelated top-level members
|
||||||
|
json const doc1 = R"({"a": 1, "b": 2})"_json;
|
||||||
|
json const patch1 = {{{"op", "move"}, {"from", "/a"}, {"path", "/c"}}};
|
||||||
|
CHECK(doc1.patch(patch1) == R"({"b": 2, "c": 1})"_json);
|
||||||
|
|
||||||
|
// sibling paths that share a textual prefix but are unrelated
|
||||||
|
json const doc2 = R"({"a": {"x": 1}, "b": {"y": 2}})"_json;
|
||||||
|
json const patch2 = {{{"op", "move"}, {"from", "/a/x"}, {"path", "/b/z"}}};
|
||||||
|
CHECK(doc2.patch(patch2) == R"({"a": {}, "b": {"y": 2, "z": 1}})"_json);
|
||||||
|
|
||||||
|
// "path" is a proper prefix of "from" (the reverse relationship,
|
||||||
|
// which RFC 6902 does not forbid)
|
||||||
|
json const doc3 = R"({"a": {"b": 1}})"_json;
|
||||||
|
json const patch3 = {{{"op", "move"}, {"from", "/a/b"}, {"path", "/a"}}};
|
||||||
|
CHECK(doc3.patch(patch3) == R"({"a": 1})"_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("root 'from' is a proper prefix of every non-root 'path'")
|
||||||
|
{
|
||||||
|
// the whole document is a proper prefix of any location inside it
|
||||||
|
json const doc = R"({"a": 1})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", ""}, {"path", "/a"}}};
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-8) cannot move value: 'from' path '' is a proper prefix of 'path' '/a'", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '' is a proper prefix of 'path' '/a'", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("root 'path' is never a proper prefix violation for a non-root 'from'")
|
||||||
|
{
|
||||||
|
// the reverse of the above: moving a non-root location to the root
|
||||||
|
// is the "path is a prefix of from" relationship, which RFC 6902
|
||||||
|
// permits (already covered generally above; this pins the root
|
||||||
|
// case specifically, since root is the one path with no reference
|
||||||
|
// tokens at all)
|
||||||
|
json const doc = R"({"a": {"b": 1}})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", ""}}};
|
||||||
|
CHECK(doc.patch(patch) == R"({"b": 1})"_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the array-append token '-' is an ordinary child token")
|
||||||
|
{
|
||||||
|
// "-" (append-to-array) addresses a location *inside* the array,
|
||||||
|
// so "from" pointing at the array is still a proper prefix of
|
||||||
|
// "path" ending in "-" and must be rejected like any other child.
|
||||||
|
json const doc = R"({"a": [1, 2]})"_json;
|
||||||
|
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a/-"}}};
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-13) cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/-'", json::out_of_range&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/-'", json::out_of_range&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("JSON patch - diff emits array removals in descending index order")
|
TEST_CASE("JSON patch - diff emits array removals in descending index order")
|
||||||
{
|
{
|
||||||
SECTION("array shrunk to empty")
|
SECTION("array shrunk to empty")
|
||||||
|
|||||||
@@ -319,6 +319,44 @@ TEST_CASE("JSON pointers")
|
|||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
// #5395: contains() must not throw for a reference token that is a
|
||||||
|
// syntactically valid array index but numerically exceeds ULLONG_MAX
|
||||||
|
// (causing strtoull() to set errno to ERANGE) -- it should just report
|
||||||
|
// that the pointer does not resolve to an element
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// #5395: same as above, but using the exact reproduction from the issue
|
||||||
|
json::json_pointer const jp("/99999999999999999999");
|
||||||
|
std::string const throw_msg = "[json.exception.out_of_range.404] unresolved reference token '99999999999999999999'";
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// #5395: a reference token that is numerically representable in
|
||||||
|
// unsigned long long but exceeds size_type's max (e.g. ULLONG_MAX
|
||||||
|
// itself on typical 64-bit platforms, where size_type's max equals
|
||||||
|
// ULLONG_MAX) must not make contains() throw either
|
||||||
|
json::json_pointer const jp("/18446744073709551615");
|
||||||
|
std::string const throw_msg = "[json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type";
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
}
|
}
|
||||||
|
|
||||||
// on some machines, the check below is not constant
|
// on some machines, the check below is not constant
|
||||||
@@ -334,6 +372,10 @@ TEST_CASE("JSON pointers")
|
|||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
|
||||||
|
|
||||||
|
// #5395: contains() must not throw for a reference token exceeding size_type's max
|
||||||
|
CHECK(!j.contains(jp));
|
||||||
|
CHECK(!j_const.contains(jp));
|
||||||
}
|
}
|
||||||
|
|
||||||
DOCTEST_MSVC_SUPPRESS_WARNING_POP
|
DOCTEST_MSVC_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
TEST_CASE("tests on very large JSONs")
|
TEST_CASE("tests on very large JSONs")
|
||||||
{
|
{
|
||||||
@@ -27,3 +28,201 @@ TEST_CASE("tests on very large JSONs")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// Descend a chain of single-element containers and return the value at its end,
|
||||||
|
// reporting the number of levels traversed in @a depth.
|
||||||
|
//
|
||||||
|
// The values in the test case below are nested far deeper than the call stack
|
||||||
|
// can follow, so they must not be inspected with operator== or dump(): both are
|
||||||
|
// still recursive and would overflow the stack themselves.
|
||||||
|
const json* innermost_value(const json& j, std::size_t& depth)
|
||||||
|
{
|
||||||
|
const json* current = &j;
|
||||||
|
depth = 0;
|
||||||
|
|
||||||
|
while ((current->is_array() || current->is_object()) && !current->empty())
|
||||||
|
{
|
||||||
|
current = current->is_array()
|
||||||
|
? ¤t->front()
|
||||||
|
: ¤t->begin().value();
|
||||||
|
++depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("tests on deeply nested JSONs")
|
||||||
|
{
|
||||||
|
// deep enough to exhaust the call stack, but small enough to stay cheap:
|
||||||
|
// parsing is iterative, so building the values below costs little
|
||||||
|
const std::size_t depth = 100000;
|
||||||
|
|
||||||
|
SECTION("issue #5387 - stack overflow in the copy constructor")
|
||||||
|
{
|
||||||
|
SECTION("array")
|
||||||
|
{
|
||||||
|
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
||||||
|
|
||||||
|
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
|
||||||
|
std::size_t copy_depth = 0;
|
||||||
|
CHECK(*innermost_value(copy, copy_depth) == 0);
|
||||||
|
CHECK(copy_depth == depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object")
|
||||||
|
{
|
||||||
|
std::string s;
|
||||||
|
s.reserve((6 * depth) + 1);
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
s += "{\"a\":";
|
||||||
|
}
|
||||||
|
s += '1';
|
||||||
|
s.append(depth, '}');
|
||||||
|
|
||||||
|
const json j = json::parse(s);
|
||||||
|
|
||||||
|
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
|
||||||
|
std::size_t copy_depth = 0;
|
||||||
|
CHECK(*innermost_value(copy, copy_depth) == 1);
|
||||||
|
CHECK(copy_depth == depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("copy assignment")
|
||||||
|
{
|
||||||
|
// operator=(basic_json) takes its argument by value, so the deep
|
||||||
|
// copy happens in the copy constructor
|
||||||
|
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
||||||
|
|
||||||
|
json target;
|
||||||
|
target = j;
|
||||||
|
|
||||||
|
std::size_t target_depth = 0;
|
||||||
|
CHECK(*innermost_value(target, target_depth) == 0);
|
||||||
|
CHECK(target_depth == depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("depths around the bound of the recursive descent")
|
||||||
|
{
|
||||||
|
// The copy constructor descends into a bounded number of levels and
|
||||||
|
// completes whatever is below that without the call stack. Cover
|
||||||
|
// every depth around that bound, so that the two ways of copying
|
||||||
|
// are known to meet cleanly - wherever the bound is set.
|
||||||
|
for (std::size_t d = 1; d <= 300; ++d)
|
||||||
|
{
|
||||||
|
CAPTURE(d);
|
||||||
|
|
||||||
|
const json array = json::parse(std::string(d, '[') + '0' + std::string(d, ']'));
|
||||||
|
const json array_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
std::size_t array_depth = 0;
|
||||||
|
CHECK(*innermost_value(array_copy, array_depth) == 0);
|
||||||
|
CHECK(array_depth == d);
|
||||||
|
|
||||||
|
std::string object_text;
|
||||||
|
for (std::size_t i = 0; i < d; ++i)
|
||||||
|
{
|
||||||
|
object_text += "{\"a\":";
|
||||||
|
}
|
||||||
|
object_text += '1';
|
||||||
|
object_text.append(d, '}');
|
||||||
|
|
||||||
|
const json object = json::parse(object_text);
|
||||||
|
const json object_copy(object); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
std::size_t object_depth = 0;
|
||||||
|
CHECK(*innermost_value(object_copy, object_depth) == 1);
|
||||||
|
CHECK(object_depth == d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a value that is deep in one place only")
|
||||||
|
{
|
||||||
|
json j = json::object();
|
||||||
|
j["shallow"] = 1;
|
||||||
|
j["deep"] = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
||||||
|
j["also_shallow"] = json::array({1, 2, 3});
|
||||||
|
|
||||||
|
const json copy(j);
|
||||||
|
|
||||||
|
CHECK(copy["shallow"] == 1);
|
||||||
|
CHECK(copy["also_shallow"] == json::array({1, 2, 3}));
|
||||||
|
|
||||||
|
std::size_t deep_depth = 0;
|
||||||
|
CHECK(*innermost_value(copy["deep"], deep_depth) == 0);
|
||||||
|
CHECK(deep_depth == depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparing")
|
||||||
|
{
|
||||||
|
// Comparing used to descend once per level, and an ordered
|
||||||
|
// comparison used to compare every pair of elements twice, once in
|
||||||
|
// each direction, which took exponentially long in the nesting
|
||||||
|
// depth. Both are gone: these finish in milliseconds, where the
|
||||||
|
// second used to take longer than anyone would wait even for a
|
||||||
|
// value nested only a few dozen levels deep.
|
||||||
|
const std::string text = std::string(depth, '[') + '0' + std::string(depth, ']');
|
||||||
|
const json j = json::parse(text);
|
||||||
|
const json same = json::parse(text);
|
||||||
|
const json larger = json::parse(std::string(depth, '[') + '1' + std::string(depth, ']'));
|
||||||
|
|
||||||
|
CHECK(j == same);
|
||||||
|
CHECK_FALSE(j == larger);
|
||||||
|
CHECK(j != larger);
|
||||||
|
|
||||||
|
CHECK(j < larger);
|
||||||
|
CHECK_FALSE(larger < j);
|
||||||
|
CHECK(larger > j);
|
||||||
|
CHECK(j <= same);
|
||||||
|
CHECK(j >= same);
|
||||||
|
|
||||||
|
// a value that ends earlier is the smaller one
|
||||||
|
const json shorter = json::parse(std::string(depth - 1, '[') + '0' + std::string(depth - 1, ']'));
|
||||||
|
CHECK_FALSE(j == shorter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparing objects")
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
text.reserve((6 * depth) + 1);
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += "{\"a\":";
|
||||||
|
}
|
||||||
|
text += '1';
|
||||||
|
text.append(depth, '}');
|
||||||
|
|
||||||
|
const json j = json::parse(text);
|
||||||
|
const json same = json::parse(text);
|
||||||
|
|
||||||
|
CHECK(j == same);
|
||||||
|
CHECK_FALSE(j != same);
|
||||||
|
CHECK(j <= same);
|
||||||
|
CHECK(j >= same);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the copy is independent of the original")
|
||||||
|
{
|
||||||
|
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
||||||
|
|
||||||
|
json copy(j);
|
||||||
|
|
||||||
|
// reach the innermost value without recursing and replace it
|
||||||
|
json* current = ©
|
||||||
|
while (current->is_array() && !current->empty())
|
||||||
|
{
|
||||||
|
current = ¤t->front();
|
||||||
|
}
|
||||||
|
*current = 42;
|
||||||
|
|
||||||
|
std::size_t unused = 0;
|
||||||
|
CHECK(*innermost_value(copy, unused) == 42);
|
||||||
|
CHECK(*innermost_value(j, unused) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// This file makes sure that none of the internal JSON_HEDLEY_* macros (vendored
|
||||||
|
// from https://nemequ.github.io/hedley/, see
|
||||||
|
// include/nlohmann/thirdparty/hedley/hedley.hpp) leak into the including
|
||||||
|
// translation unit. include/nlohmann/detail/macro_unscope.hpp is supposed to
|
||||||
|
// #undef every JSON_HEDLEY_* macro (via hedley_undef.hpp) once json.hpp has
|
||||||
|
// been fully processed. See https://github.com/nlohmann/json/issues/5408,
|
||||||
|
// where JSON_HEDLEY_PRAGMA, JSON_HEDLEY_PREDICT_TRUE, JSON_HEDLEY_PREDICT_FALSE,
|
||||||
|
// and JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE escaped this cleanup because
|
||||||
|
// hedley_undef.hpp had no matching #undef for them.
|
||||||
|
//
|
||||||
|
// hedley_undef_checks.inc (included below) is generated at CMake configure/
|
||||||
|
// build time by cmake/scripts/gen_hedley_undef_check.cmake, which derives the
|
||||||
|
// full list of JSON_HEDLEY_* macro names directly from hedley.hpp. That way
|
||||||
|
// this test covers every macro Hedley actually defines -- not a hardcoded
|
||||||
|
// snapshot that would silently go stale the next time `make update_hedley`
|
||||||
|
// runs -- and can never drift from the vendored header.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("JSON_HEDLEY macros do not leak after including json.hpp")
|
||||||
|
{
|
||||||
|
#include "hedley_undef_checks.inc"
|
||||||
|
CHECK(true); // keep an assertion when nothing leaked
|
||||||
|
}
|
||||||
@@ -81,3 +81,37 @@ TEST_CASE("regression test for issue #3732 - iteration_proxy_value<iter_impl<ord
|
|||||||
};
|
};
|
||||||
static_cast<void>(fn);
|
static_cast<void>(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("copying an ordered_json with nested values")
|
||||||
|
{
|
||||||
|
// ordered_map is backed by a vector, so copying an object that has
|
||||||
|
// structured values takes a different route than copying a std::map-backed
|
||||||
|
// one; see https://github.com/nlohmann/json/issues/5387
|
||||||
|
ordered_json oj;
|
||||||
|
oj["z"] = 1;
|
||||||
|
oj["a"]["y"] = 2;
|
||||||
|
oj["a"]["b"]["x"] = 3;
|
||||||
|
oj["m"] = {1, 2, {{"w", 4}}};
|
||||||
|
|
||||||
|
const ordered_json copy(oj);
|
||||||
|
|
||||||
|
SECTION("the copy is equal to the original")
|
||||||
|
{
|
||||||
|
CHECK(copy == oj);
|
||||||
|
CHECK(copy.dump() == oj.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the key order is preserved at every level")
|
||||||
|
{
|
||||||
|
CHECK(copy.dump() == R"({"z":1,"a":{"y":2,"b":{"x":3}},"m":[1,2,{"w":4}]})");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the copy is independent of the original")
|
||||||
|
{
|
||||||
|
ordered_json mutated(oj);
|
||||||
|
mutated["a"]["b"]["x"] = 99;
|
||||||
|
|
||||||
|
CHECK(oj["a"]["b"]["x"] == 3);
|
||||||
|
CHECK(mutated["a"]["b"]["x"] == 99);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ using nlohmann::json;
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <variant>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "fifo_map.hpp"
|
#include "fifo_map.hpp"
|
||||||
|
|
||||||
@@ -1373,7 +1370,8 @@ TEST_CASE("regression tests 1")
|
|||||||
std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
|
std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
|
||||||
std::string const key1_str(reinterpret_cast<char*>(key1.data()));
|
std::string const key1_str(reinterpret_cast<char*>(key1.data()));
|
||||||
json const j = key1_str;
|
json const j = key1_str;
|
||||||
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
|||||||
+4
-1122
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,8 @@ using nlohmann::json;
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
TEST_CASE("serialization")
|
TEST_CASE("serialization")
|
||||||
{
|
{
|
||||||
SECTION("operator<<")
|
SECTION("operator<<")
|
||||||
@@ -84,8 +86,9 @@ TEST_CASE("serialization")
|
|||||||
{
|
{
|
||||||
const json j = "ä\xA9ü";
|
const json j = "ä\xA9ü";
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
CHECK_THROWS_WITH_AS(j.dump(1, ' ', false, json::error_handler_t::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
|
||||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
|
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
|
||||||
@@ -95,8 +98,9 @@ TEST_CASE("serialization")
|
|||||||
{
|
{
|
||||||
const json j = "123\xC2";
|
const json j = "123\xC2";
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
|
||||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
|
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
|
||||||
@@ -106,8 +110,9 @@ TEST_CASE("serialization")
|
|||||||
{
|
{
|
||||||
const json j = "123\xF1\xB0\x34\x35\x36";
|
const json j = "123\xF1\xB0\x34\x35\x36";
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
|
||||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
|
||||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
|
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using nlohmann::json;
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
TEST_CASE("Unicode (1/5)" * doctest::skip())
|
TEST_CASE("Unicode (1/5)" * doctest::skip())
|
||||||
{
|
{
|
||||||
@@ -240,7 +241,8 @@ void roundtrip(bool success_expected, const std::string& s)
|
|||||||
if (success_expected)
|
if (success_expected)
|
||||||
{
|
{
|
||||||
// serialization succeeds
|
// serialization succeeds
|
||||||
CHECK_NOTHROW(j.dump());
|
// dump() is nodiscard; this only checks that dumping does not throw
|
||||||
|
CHECK_NOTHROW(utils::ignore_return_value(j.dump()));
|
||||||
|
|
||||||
// exclude parse test for U+0000
|
// exclude parse test for U+0000
|
||||||
if (s[0] != '\0')
|
if (s[0] != '\0')
|
||||||
@@ -259,7 +261,8 @@ void roundtrip(bool success_expected, const std::string& s)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// serialization fails
|
// serialization fails
|
||||||
CHECK_THROWS_AS(j.dump(), json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
|
||||||
// parsing JSON text fails
|
// parsing JSON text fails
|
||||||
CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
|
CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
// this test suite uses static variables with non-trivial destructors
|
// this test suite uses static variables with non-trivial destructors
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
@@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// strict mode must throw if success is not expected
|
// strict mode must throw if success is not expected
|
||||||
CHECK_THROWS_AS(j.dump(), json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
// ignore and replace must create different dumps
|
// ignore and replace must create different dumps
|
||||||
CHECK(s_ignored != s_replaced);
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
// this test suite uses static variables with non-trivial destructors
|
// this test suite uses static variables with non-trivial destructors
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
@@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// strict mode must throw if success is not expected
|
// strict mode must throw if success is not expected
|
||||||
CHECK_THROWS_AS(j.dump(), json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
// ignore and replace must create different dumps
|
// ignore and replace must create different dumps
|
||||||
CHECK(s_ignored != s_replaced);
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
// this test suite uses static variables with non-trivial destructors
|
// this test suite uses static variables with non-trivial destructors
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
@@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// strict mode must throw if success is not expected
|
// strict mode must throw if success is not expected
|
||||||
CHECK_THROWS_AS(j.dump(), json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
// ignore and replace must create different dumps
|
// ignore and replace must create different dumps
|
||||||
CHECK(s_ignored != s_replaced);
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
// this test suite uses static variables with non-trivial destructors
|
// this test suite uses static variables with non-trivial destructors
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
@@ -97,7 +98,8 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// strict mode must throw if success is not expected
|
// strict mode must throw if success is not expected
|
||||||
CHECK_THROWS_AS(j.dump(), json::type_error&);
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
// ignore and replace must create different dumps
|
// ignore and replace must create different dumps
|
||||||
CHECK(s_ignored != s_replaced);
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user