diff --git a/.github/external_ci/appveyor.yml b/.github/external_ci/appveyor.yml index 805195742..4f0d3f5e3 100644 --- a/.github/external_ci/appveyor.yml +++ b/.github/external_ci/appveyor.yml @@ -16,6 +16,9 @@ only_commits: environment: matrix: + # The Visual Studio 2017 jobs compile everything with /std:c++17, so they + # only build the C++17 variant of each test, split into two jobs each to + # stay below AppVeyor's 60-minute limit per job. - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 configuration: Debug platform: x86 @@ -34,7 +37,13 @@ environment: configuration: Release platform: x86 CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX" - CMAKE_OPTIONS: "" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2" + GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + configuration: Release + platform: x86 + CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2" GENERATOR: Visual Studio 15 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 @@ -55,7 +64,13 @@ environment: configuration: Release platform: x64 CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX" - CMAKE_OPTIONS: "" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2" + GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + configuration: Release + platform: x64 + CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2" GENERATOR: Visual Studio 15 2017 init: @@ -66,7 +81,7 @@ install: - if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32 before_build: - - cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%" + - cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS% build_script: - cmake --build . --config "%configuration%" --parallel 2 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1e4a72870..354cf2391 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -107,7 +107,7 @@ jobs: container: ubuntu:24.04 strategy: matrix: - 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_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local] + 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_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local] steps: - name: Install build-essential run: apt-get update ; apt-get install -y build-essential unzip wget git diff --git a/CMakeLists.txt b/CMakeLists.txt index b5092ae49..a2501a92d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ option(JSON_MultipleHeaders "Use non-amalgamated version of the l option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF) option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF) +option(JSON_DeleteDeprecatedFunctions "Delete the deprecated functions instead of only deprecating them." OFF) if (JSON_CI) include(ci) @@ -123,6 +124,10 @@ if (JSON_StrictBinaryUTF8) message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)") endif() +if (JSON_DeleteDeprecatedFunctions) + message(STATUS "Deprecated functions are deleted (JSON_DELETE_DEPRECATED_FUNCTIONS=1)") +endif() + if (JSON_Diagnostic_Positions) message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)") endif() @@ -159,6 +164,7 @@ target_compile_definitions( $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> $<$:JSON_STRICT_NUL_HANDLING=1> $<$:JSON_STRICT_BINARY_UTF8=1> + $<$:JSON_DELETE_DEPRECATED_FUNCTIONS=1> ) target_include_directories( diff --git a/cmake/ci.cmake b/cmake/ci.cmake index d0af68786..c4bced5ba 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -249,6 +249,20 @@ add_custom_target(ci_test_strict_nul_handling COMMENT "Compile and test with strict NUL-byte handling enabled" ) +############################################################################### +# Delete the deprecated functions. +############################################################################### + +add_custom_target(ci_test_delete_deprecated_functions + COMMAND ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DeleteDeprecatedFunctions=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_delete_deprecated_functions + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions + COMMAND cd ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMENT "Compile and test with the deprecated functions deleted" +) + ############################################################################### # Disable global UDLs. ############################################################################### @@ -362,7 +376,7 @@ add_custom_target(ci_test_coverage # Sanitizers. ############################################################################### -set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base") +set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt") add_custom_target(ci_test_clang_sanitizer COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND} @@ -705,7 +719,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY) # the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0 set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind - JSON_StrictNulHandling JSON_StrictBinaryUTF8) + JSON_StrictNulHandling JSON_StrictBinaryUTF8 JSON_DeleteDeprecatedFunctions) set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0}) diff --git a/cmake/clang_sanitizer_ignorelist.txt b/cmake/clang_sanitizer_ignorelist.txt new file mode 100644 index 000000000..153b6a76d --- /dev/null +++ b/cmake/clang_sanitizer_ignorelist.txt @@ -0,0 +1,8 @@ +# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist). +# +# libstdc++ 14's declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format +# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only are +# excluded, so implicit sign changes in the library and the tests are still reported. +[implicit-integer-sign-change] +src:*/include/c++/*/format diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 801ade93a..8e956413c 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -219,6 +219,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', ' INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html'); @@ -249,6 +250,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); diff --git a/docs/mkdocs/docs/api/basic_json/diff.md b/docs/mkdocs/docs/api/basic_json/diff.md index fb6683ec8..ae22454dd 100644 --- a/docs/mkdocs/docs/api/basic_json/diff.md +++ b/docs/mkdocs/docs/api/basic_json/diff.md @@ -8,7 +8,7 @@ static basic_json diff(const basic_json& source, Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling [`patch`](patch.md) function. -For two JSON values `source` and `target`, the following code yields always `#!cpp true`: +For two JSON values `source` and `target`, the following code always yields `#!cpp true`: ```cpp source.patch(diff(source, target)) == target; ``` @@ -27,7 +27,7 @@ a JSON patch to convert the `source` to `target` ## Exception safety -Strong guarantee: if an exception is thrown, there are no changes in the JSON value. +Strong guarantee: `source` and `target` are never modified. ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index a45e00ad5..f19f9e4a9 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -120,3 +120,12 @@ Linear in the size of the input. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. - Added `error_handler` parameter in version 3.13.0. + +!!! warning "Deprecation" + + - Overload (2) replaces calls to `from_bjdata` with a pointer and a length as first two parameters, which has been + deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like + `#!cpp from_bjdata(ptr, len, ...);` with `#!cpp from_bjdata(ptr, ptr+len, ...);`. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. diff --git a/docs/mkdocs/docs/api/basic_json/from_bon8.md b/docs/mkdocs/docs/api/basic_json/from_bon8.md index 2f9d1e749..cac6b31eb 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/from_bon8.md @@ -106,3 +106,12 @@ Linear in the size of the input. ## Version history - Added in version 3.13.0. + +!!! warning "Deprecation" + + - Overload (2) replaces calls to `from_bon8` with a pointer and a length as first two parameters, which has been + deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like + `#!cpp from_bon8(ptr, len, ...);` with `#!cpp from_bon8(ptr, ptr+len, ...);`. + + You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated + function. diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index df3b69004..583139068 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to BJData"` ## Complexity @@ -119,4 +121,6 @@ Linear in the size of the JSON value `j`. - BJData version parameter (for draft3 binary encoding) added in version 3.12.0. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if - [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. \ No newline at end of file + [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid BJData. \ No newline at end of file diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index ad974e048..05f8b8548 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded + (the top-level value itself is covered by `type_error.317` above, since it must be an object); example: + `"cannot serialize discarded value to BSON"` ## Complexity @@ -110,6 +113,8 @@ pass before anything is written. - Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0. - Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0. - `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0. +- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently + skipped, producing a document whose declared size did not match what was actually written. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index c72f310bb..f5b6a07bb 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to CBOR"` ## Complexity @@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid CBOR. diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index 17b9fe29c..2e32208bf 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va `"subtype 70000 is too large for the MessagePack ext type (max 255)"` - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to MessagePack"` ## Complexity @@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`. - Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly; before, integers could be serialized with the wrong value if `number_integer_t` was narrower than `number_unsigned_t`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid MessagePack. diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 6860c01c6..6aad86683 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) +- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is + discarded; example: `"cannot serialize discarded value to UBJSON"` ## Complexity @@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`. - Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key that is not valid UTF-8 unchanged, as before; `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. +- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an + array or object was silently skipped, producing invalid UBJSON. diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index ffeb9639c..eb2807b69 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -61,6 +61,11 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum keys as objects +## Deprecated functions + +- [**JSON_DELETE_DEPRECATED_FUNCTIONS**](json_delete_deprecated_functions.md) - opt in to deleting the deprecated + functions ahead of their removal in version 4.0.0 + ## Comparison behavior - [**JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON**](json_use_legacy_discarded_value_comparison.md) - diff --git a/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md b/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md new file mode 100644 index 000000000..20f0ead0c --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md @@ -0,0 +1,96 @@ +# JSON_DELETE_DEPRECATED_FUNCTIONS + +```cpp +#define JSON_DELETE_DEPRECATED_FUNCTIONS /* value */ +``` + +When defined to `1`, all [deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) of the +library are declared as deleted (`= delete`) instead of only being marked as deprecated. Code that still calls one of +them no longer compiles. This way, you can find all calls that need to be replaced before version 4.0.0 removes these +functions; the [migration guide](../../integration/migration_guide.md#replace-deprecated-functions) describes how. + +A deleted function, unlike a removed one, still takes part in overload resolution. A call that would select it +therefore fails to compile instead of silently selecting another overload. This matters for the deprecated +`from_*(ptr, len)` overloads of [`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), +[`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md), +[`from_bon8`](../basic_json/from_bon8.md), and [`from_bson`](../basic_json/from_bson.md): without them, a call like +`from_cbor(ptr, len)` would compile, read `ptr` as a NUL-terminated string, and convert `len` to the `strict` parameter. + +The macro does not affect the deprecated legacy comparison of discarded values, which is controlled by +[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](json_use_legacy_discarded_value_comparison.md). + +## Default definition + +The default value is `0` (disabled, the deprecated functions can still be called, and the compiler warns about it). + +```cpp +#define JSON_DELETE_DEPRECATED_FUNCTIONS 0 +``` + +## Notes + +!!! info "CMake option" + + The macro can also be set with the CMake option + [`JSON_DeleteDeprecatedFunctions`](../../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default). + +!!! warning "Opt-in only" + + This macro must be defined **before** including ``. Defining it after the include has no + effect. Define it for the whole project to avoid different declarations of the same class in different + translation units. + +!!! note "ABI compatibility" + + The macro only turns calls that compile into calls that do not; it does not change the layout or the behavior of + any type. Its value is therefore not encoded in the [namespace](../../features/namespace.md). + +## Examples + +??? example "Example: default behavior (macro not defined)" + + Without the macro, the deprecated overload is called, and the compiler warns about it: + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + const std::vector v = {0x82, 0x01, 0x02}; + auto j = json::from_cbor(v.data(), v.size()); + // warning: 'from_cbor' is deprecated: Since 3.8.0; use from_cbor(ptr, ptr + len) + } + ``` + +??? example "Example: deleted deprecated functions (macro defined to 1)" + + With the macro, the call does not compile: + + ```cpp + #define JSON_DELETE_DEPRECATED_FUNCTIONS 1 + #include + + using json = nlohmann::json; + + int main() + { + const std::vector v = {0x82, 0x01, 0x02}; + auto j = json::from_cbor(v.data(), v.size()); + // error: call to deleted function 'from_cbor' + } + ``` + +## See also + +- [Roadmap: removal of deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) - the + deprecated functions and the version they were deprecated in +- [Migration guide: replace deprecated functions](../../integration/migration_guide.md#replace-deprecated-functions) - + how to replace each deprecated function + +## Version history + +- Added in version 3.13.0. +- Planned to be removed in version 4.0.0, which removes the deprecated functions. The deprecated `from_*(ptr, len)` + overloads stay deleted in version 4.0.0. diff --git a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md index c69240726..0a2216b57 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md +++ b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md @@ -112,3 +112,4 @@ The default value is `0` (disabled — existing behavior is preserved). ## Version history - Added in version 3.13.0. +- Planned to become the default (with the macro removed) in version 4.0.0. diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 113344412..675a8acee 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -44,7 +44,7 @@ By default, implicit conversions are enabled. ## Examples -??? example "Example: implicit conversion" +??? example "Example: implicit and explicit conversions" This is an example for an implicit conversion: diff --git a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md index a84d83129..ca4139c48 100644 --- a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md +++ b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md @@ -69,7 +69,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, a map with enum keys is stored as an array of pairs: @@ -96,7 +96,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Objects for enum-keyed maps (macro defined to 1)" +??? example "Example: objects for enum-keyed maps (macro defined to 1)" With the macro, the same map is stored as an object: diff --git a/docs/mkdocs/docs/community/roadmap.md b/docs/mkdocs/docs/community/roadmap.md index 48977b295..36d5d7e7f 100644 --- a/docs/mkdocs/docs/community/roadmap.md +++ b/docs/mkdocs/docs/community/roadmap.md @@ -64,6 +64,8 @@ The following macros guard changes that are planned to become the default in ver | [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 | | [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 | | [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 | +| [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) | `0` | `1`: a `basic_json` value can no longer be created from a one-element tuple of a reference to it, such as `std::forward_as_tuple(j)`| [`JSON_DisableTupleReferenceConversion`](../integration/cmake.md#json_disabletuplereferenceconversion) | 3.13.0 | +| [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) | `0` | removed: the deprecated functions are removed (see below); the `from_*(ptr, len)` overloads stay deleted | [`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) | 3.13.0 | For example, the following makes a 3.x release behave like version 4.0 with respect to these changes: @@ -75,6 +77,8 @@ For example, the following makes a 3.x release behave like version 4.0 with resp #define JSON_PRECISE_STREAM_POSITION 1 #define JSON_STRICT_NUL_HANDLING 1 #define JSON_STRICT_BINARY_UTF8 1 +#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1 +#define JSON_DELETE_DEPRECATED_FUNCTIONS 1 #include ``` @@ -84,8 +88,13 @@ way to achieve this. ### Removal of deprecated functions Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your -code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to -replace each of them. +code still uses. Defining [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to +`1` turns these warnings into errors, as the deprecated functions are then deleted. The +[migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to replace each of them. + +The `from_*` overloads taking a pointer and a length are not removed in version 4.0, but stay deleted. Without them, a +call like `from_cbor(ptr, len)` would still compile: it would read `ptr` as a NUL-terminated string and convert `len` +to the `strict` parameter. | Deprecated | Since | Migration | |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------| @@ -97,6 +106,7 @@ replace each of them. | [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | | [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | | Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | +| [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) with `(ptr, len)` | 3.13.0 | [Parsing](../integration/migration_guide.md#parsing) | The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above. diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index 4fd05c12c..ef41ce0d5 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -23,6 +23,17 @@ This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/ See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md). +## `JSON_DELETE_DEPRECATED_FUNCTIONS` + +When defined to `1`, all deprecated functions are declared as deleted instead of only being marked as deprecated, so +code that still calls them no longer compiles. This way, you can find all calls that need to be replaced before version +4.0.0 removes these functions. + +The macro can also be set with the CMake option +[`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default). + +See [full documentation of `JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md). + ## `JSON_DIAGNOSTICS` This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable @@ -86,7 +97,8 @@ See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json ## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON -value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. +value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. This +is planned to become the default in version 4.0.0. See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 673c0c8dd..9cb5c8f62 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -804,6 +804,20 @@ does not list an enumerator and it is therefore converted like the first listed [json.exception.type_error.318] duplicate object key 'red' ``` +### json.exception.type_error.321 + +A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the +value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with +[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or +nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON. + +!!! failure "Example message" + + Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback: + ``` + [json.exception.type_error.321] cannot serialize discarded value to CBOR + ``` + ## Out of range This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys. diff --git a/docs/mkdocs/docs/integration/cmake.md b/docs/mkdocs/docs/integration/cmake.md index 9151d12be..d75aef47b 100644 --- a/docs/mkdocs/docs/integration/cmake.md +++ b/docs/mkdocs/docs/integration/cmake.md @@ -132,6 +132,12 @@ Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/ Enable CI build targets. The exact targets are used during the several CI steps and are subject to change without notice. This option is `OFF` by default. +### `JSON_DeleteDeprecatedFunctions` + +Delete the deprecated functions instead of only deprecating them by defining the macro +[`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md). This option is `OFF` by +default. + ### `JSON_Diagnostics` Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default. diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 8a07d48a3..17b2eb360 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -14,6 +14,13 @@ deprecations are annotated with [`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which function to use instead. +!!! tip "Find all calls of deprecated functions" + + Define [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to `1` (or set the + CMake option [`JSON_DeleteDeprecatedFunctions`](cmake.md#json_deletedeprecatedfunctions)) to delete all deprecated + functions. Every remaining call then fails to compile, even if deprecation warnings are disabled, so your code is + ready for version 4.0.0 once it compiles with the macro. + ### Parsing - Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use @@ -41,8 +48,10 @@ function to use instead. [`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of `from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`, - `from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of - `from_cbor(ptr, len)`. + `from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0, and to + [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) since 3.13.0; call + `from_cbor(ptr, ptr+len)` instead of `from_cbor(ptr, len)`. These overloads will not be removed in version 4.0.0, but + deleted, so a call like `from_cbor(ptr, len)` cannot compile and convert `len` to the `strict` parameter. === "Deprecated" diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 2c3f2a804..82f28aaf3 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -293,6 +293,7 @@ nav: - 'JSON_BRACE_INIT_COPY_SEMANTICS': api/macros/json_brace_init_copy_semantics.md - 'JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER': api/macros/json_throw_user.md - 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md + - 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md - 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md - 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md - 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 53f6d01cf..884ff014a 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) typename BasicJsonType::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); - e = static_cast(static_cast(val)); + e = static_cast(bool_aware_static_cast(val)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 5b332d4e9..f3b4994f1 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -287,9 +287,20 @@ struct external_constructor ///////////// #ifdef JSON_HAS_CPP_17 +// whether storing the value of a std::optional cannot throw; MSVC 2017 +// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so +// the exception would call std::terminate (#5642) +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 + template + using is_nothrow_optional_to_json = std::false_type; +#else + template + using is_nothrow_optional_to_json = std::is_nothrow_assignable; +#endif + template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(is_nothrow_optional_to_json::value) { if (opt.has_value()) { @@ -365,7 +376,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; - external_constructor::construct(j, static_cast(e)); + external_constructor::construct(j, bool_aware_static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 77d6061a1..63b8c057f 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -836,10 +836,11 @@ class binary_reader } // the value is -1 - number, which fits into number_integer_t - // whenever number does + // whenever number does; the outer cast undoes the integral promotion + // for number_integer_t types narrower than int if (JSON_HEDLEY_LIKELY(value_in_range_of(number))) { - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } // like the lexer does for JSON text, store a value too small for @@ -4683,7 +4684,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -4706,7 +4708,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -4717,7 +4720,8 @@ class binary_reader and NaN in the input are passed on unchanged. Integers only overflow if number_float_t cannot represent 2^64, e.g., a half-precision type. - @tparam NumberType a floating-point or integer type + @tparam NumberType a floating-point type (emit_signed and emit_unsigned + convert integers to long double first) @param[in] number the number @return whether the SAX parser accepted the value diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 30edb4a3b..92f52aedc 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -81,9 +81,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/ JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) operator string_t() const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return to_string(); } +#endif #ifndef JSON_NO_IO /// @brief write string representation of the JSON pointer to stream @@ -1022,9 +1026,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer)) bool operator==(const string_t& rhs) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return *this == json_pointer(rhs); } +#endif /// @brief 3-way compares two JSON pointers template @@ -1102,18 +1110,26 @@ template& lhs, const StringType& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return lhs == json_pointer(rhs); } +#endif template::string_t> JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) inline bool operator==(const StringType& lhs, const json_pointer& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return json_pointer(lhs) == rhs; } +#endif template inline bool operator!=(const json_pointer& lhs, @@ -1127,18 +1143,26 @@ template& lhs, const StringType& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return !(lhs == rhs); } +#endif template::string_t> JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) inline bool operator!=(const StringType& lhs, const json_pointer& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return !(lhs == rhs); } +#endif template inline bool operator<(const json_pointer& lhs, diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 17118927c..1a847e1a9 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -888,6 +888,10 @@ #define JSON_DISABLE_ENUM_SERIALIZATION 0 #endif +#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS + #define JSON_DELETE_DEPRECATED_FUNCTIONS 0 +#endif + #ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0 #endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index 75bf14c92..494cba0d8 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -43,6 +43,7 @@ #undef JSON_PRECISE_STREAM_POSITION #undef JSON_STRICT_NUL_HANDLING #undef JSON_STRICT_BINARY_UTF8 + #undef JSON_DELETE_DEPRECATED_FUNCTIONS #undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS #endif diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 69ab05f4e..4c90c68a3 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -906,6 +906,21 @@ T conditional_static_cast(U value) return value; } +// like conditional_static_cast, but converts to bool by comparing with zero, +// because MSVC 2015 warns about any conversion to bool (C4800), even with an +// explicit cast; used for enums whose underlying type is bool +template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > +T bool_aware_static_cast(U value) +{ + return conditional_static_cast(value); +} + +template::value, int> = 0> +bool bool_aware_static_cast(U value) +{ + return value != U(); +} + template using all_integral = conjunction...>; diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index a4921f217..8a26a451c 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -127,6 +127,7 @@ class binary_writer @throw type_error.316 if a string value or an object key is not valid UTF-8 @throw type_error.317 if @a j is not an object + @throw type_error.321 if a value nested in @a j is discarded */ void write_bson(const BasicJsonType& j) { @@ -158,6 +159,7 @@ class binary_writer @param[in] j JSON value to serialize @throw type_error.316 if a string value or an object key is not valid UTF-8 + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_cbor(const BasicJsonType& j) { @@ -322,7 +324,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "CBOR"); } } @@ -382,6 +384,7 @@ class binary_writer /*! @param[in] j JSON value to serialize + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_msgpack(const BasicJsonType& j) { @@ -655,7 +658,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "MessagePack"); } } @@ -668,6 +671,7 @@ class binary_writer @param[in] bjdata_version which BJData version to use, default is draft2 @throw type_error.316 if a string value or an object key is not valid UTF-8 + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, @@ -901,7 +905,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON"); } } @@ -921,6 +925,15 @@ class binary_writer } private: + /*! + @brief throws because @a j is discarded and cannot be serialized + @throw type_error.321 always + */ + JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name) + { + JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + } + ////////// // BSON // ////////// @@ -1172,6 +1185,7 @@ class binary_writer into a byte, before anything is written @throw type_error.316 if @a j is a string that is not valid UTF-8, before anything is written + @throw type_error.321 if @a j is discarded */ std::size_t calc_bson_value_size(const BasicJsonType& j) { @@ -1198,10 +1212,12 @@ class binary_writer case value_t::null: return 0ul; + case value_t::discarded: + throw_on_discarded(j, "BSON"); + // LCOV_EXCL_START case value_t::object: case value_t::array: - case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return 0ul; @@ -1238,10 +1254,12 @@ class binary_writer case value_t::null: return write_bson_null(name); + case value_t::discarded: + throw_on_discarded(j, "BSON"); + // LCOV_EXCL_START case value_t::object: case value_t::array: - case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return; @@ -1308,6 +1326,8 @@ class binary_writer byte, before anything is written @throw type_error.316 if a string value or a key is not valid UTF-8, before anything is written + @throw type_error.321 if a value nested in @a document is discarded, + before anything is written */ std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 7765d43f5..f161784d0 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -612,100 +612,210 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// constructor for rvalue binary arrays (internal type) json_value(binary_t&& value) : binary(create(std::move(value))) {} - void destroy(value_t t) +private: + // raw, allocation-free transfer of m_data from src to dst: no + // set_parents()/assert_invariant() (the former is O(#children) per + // call under JSON_DIAGNOSTICS, which would make the walk below + // quadratic); dst takes ownership, src is left as value_t::null. + static void take(basic_json& dst, basic_json& src) noexcept + { + dst.m_data.m_type = src.m_data.m_type; + dst.m_data.m_value = src.m_data.m_value; + src.m_data.m_type = value_t::null; + } + + // true if v is not an array/object, or is an already-empty one + static bool has_no_children(const basic_json& v) noexcept + { + switch (v.m_data.m_type) + { + case value_t::array: + return v.m_data.m_value.array->empty(); + case value_t::object: + return v.m_data.m_value.object->empty(); + default: + return true; + } + } + + static basic_json& last_child(basic_json& v) + { + if (v.m_data.m_type == value_t::array) + { + return v.m_data.m_value.array->back(); + } + JSON_ASSERT(v.m_data.m_type == value_t::object); + return v.m_data.m_value.object->rbegin()->second; + } + + // removes the last child of a non-empty array/object v; this never + // allocates, and since it is only ever called when that child is a + // scalar or an already-empty array/object, destroying it never + // recurses more than one level deep (see destroy() below) + static void pop_last_child(basic_json& v) + { + if (v.m_data.m_type == value_t::array) + { + v.m_data.m_value.array->pop_back(); + } + else + { + JSON_ASSERT(v.m_data.m_type == value_t::object); + // erase() needs a forward iterator, so std::prev(end()) is + // used here rather than rbegin() (see last_child() above) + v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end())); + } + } + + // deallocates the (already empty) array/object held by v; this is + // the same allocator-based free the old recursive implementation + // used, just factored out so every level of the walk in destroy() + // can share it + static void free_container(basic_json& v) noexcept + { + if (v.m_data.m_type == value_t::array) + { + JSON_ASSERT(v.m_data.m_value.array->empty()); + AllocatorType alloc; + std::allocator_traits::destroy(alloc, v.m_data.m_value.array); + std::allocator_traits::deallocate(alloc, v.m_data.m_value.array, 1); + } + else + { + JSON_ASSERT(v.m_data.m_type == value_t::object); + JSON_ASSERT(v.m_data.m_value.object->empty()); + AllocatorType alloc; + std::allocator_traits::destroy(alloc, v.m_data.m_value.object); + std::allocator_traits::deallocate(alloc, v.m_data.m_value.object, 1); + } + v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed + } + +public: + void destroy_string() noexcept + { + if (string == nullptr) + { + // not initialized (e.g., due to exception in the ctor) + return; + } + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + } + + void destroy_binary() noexcept + { + if (binary == nullptr) + { + // not initialized (e.g., due to exception in the ctor) + return; + } + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + } + + // t must be value_t::array or value_t::object + void destroy_container(value_t t) noexcept { if ( (t == value_t::object && object == nullptr) || - (t == value_t::array && array == nullptr) || - (t == value_t::string && string == nullptr) || - (t == value_t::binary && binary == nullptr) + (t == value_t::array && array == nullptr) ) { // not initialized (e.g., due to exception in the ctor) return; } - if (t == value_t::array || t == value_t::object) + + // Destroy the tree without recursing per nesting level and + // without any heap allocation: a heap-allocated flattening + // stack (the previous implementation) can itself throw + // bad_alloc, which would escape this noexcept destructor and + // terminate the program (#5135). + // + // Instead, walk down the "last child" chain, reversing links + // as we go: cur is the container currently being emptied, + // and prev is its parent (value_t::null when there is none). + // Each parent's last child slot doubles as storage for that + // parent's own parent link while we are below it, so no + // extra memory is needed. We only ever remove a child once + // it is a scalar or an empty array/object, which neither + // allocates nor recurses more than one level deep. + // + // This json_value is not itself a basic_json, so the + // top-level container is first moved into a local stand-in + // ("cur"); a default-constructed basic_json has a null + // pointer in its m_value (see data::m_value's initializer), + // so swapping it with *this leaves this union's own pointer + // null, and it is never looked at or freed a second time. + basic_json cur; + cur.m_data.m_type = t; + using std::swap; + swap(cur.m_data.m_value, *this); + + basic_json prev; // value_t::null: no parent + + while (true) { - // flatten the current json_value to a heap-allocated stack - std::vector stack; - - // move the top-level items to stack - if (t == value_t::array) + if (has_no_children(cur)) { - stack.reserve(array->size()); - std::move(array->begin(), array->end(), std::back_inserter(stack)); - } - else - { - stack.reserve(object->size()); - for (auto&& it : *object) + if (prev.m_data.m_type == value_t::null) { - stack.push_back(std::move(it.second)); - } - } - - while (!stack.empty()) - { - // move the last item to a local variable to be processed - basic_json current_item(std::move(stack.back())); - stack.pop_back(); - - // if current_item is array/object, move - // its children to the stack to be processed later - if (current_item.is_array()) - { - std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); - - current_item.m_data.m_value.array->clear(); - } - else if (current_item.is_object()) - { - for (auto&& it : *current_item.m_data.m_value.object) - { - stack.push_back(std::move(it.second)); - } - - current_item.m_data.m_value.object->clear(); + free_container(cur); + return; // back at the top with nothing left to do } - // it's now safe that current_item gets destructed - // since it doesn't have any children + // ascend: detach the grandparent link from prev's + // last slot, drop that (now null) slot, free cur + // (it is empty), then move up one level + basic_json gp; + take(gp, last_child(prev)); + pop_last_child(prev); + + free_container(cur); + + take(cur, prev); + take(prev, gp); + continue; } + + basic_json& cur_last_ref = last_child(cur); + + if (has_no_children(cur_last_ref)) + { + // scalar, or already-empty array/object + pop_last_child(cur); + continue; + } + + // descend into the non-empty last child, reversing the + // link: its slot takes over prev, and the child becomes + // the new cur + basic_json tmp; + take(tmp, cur_last_ref); + take(cur_last_ref, prev); + take(prev, cur); + take(cur, tmp); } + } + void destroy(value_t t) + { switch (t) { - case value_t::object: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, object); - std::allocator_traits::deallocate(alloc, object, 1); - break; - } - - case value_t::array: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, array); - std::allocator_traits::deallocate(alloc, array, 1); - break; - } - case value_t::string: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, string); - std::allocator_traits::deallocate(alloc, string, 1); + destroy_string(); break; - } case value_t::binary: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, binary); - std::allocator_traits::deallocate(alloc, binary, 1); + destroy_binary(); + break; + + case value_t::object: + case value_t::array: + destroy_container(t); break; - } case value_t::null: case value_t::boolean: @@ -714,9 +824,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::number_float: case value_t::discarded: default: - { break; - } } } }; @@ -3492,9 +3600,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) ValueType value(const ::nlohmann::json_pointer& ptr, const ValueType& default_value) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return value(ptr.convert(), default_value); } +#endif template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type::type, detail::enable_if_t < @@ -3503,9 +3615,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) ReturnType value(const ::nlohmann::json_pointer& ptr, ValueType && default_value) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return value(ptr.convert(), std::forward(default_value)); } +#endif /// @brief access the first element /// @sa https://json.nlohmann.me/api/basic_json/front/ @@ -3863,9 +3979,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) bool contains(const typename ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.contains(this); } +#endif /// @} @@ -3976,9 +4096,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(reference ref) noexcept +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ref.items(); } +#endif /// @brief wrapper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ @@ -3987,9 +4111,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(const_reference ref) noexcept +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ref.items(); } +#endif /// @brief helper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ @@ -5078,7 +5206,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -5087,7 +5215,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -5108,11 +5236,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif } + // The friend comparisons with a scalar name the JSON type via decltype of + // their parameter in noexcept, because older MSVC versions do not see the + // class scope there: MSVC 2015 and 2017 take basic_json as the template, + // and MSVC 2019 16.0 rejects member types and template parameters. + /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs == basic_json(rhs); } @@ -5121,7 +5254,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) == rhs; } @@ -5137,7 +5270,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs != basic_json(rhs); } @@ -5146,7 +5279,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) != rhs; } @@ -5166,7 +5299,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs < basic_json(rhs); } @@ -5175,7 +5308,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) < rhs; } @@ -5195,7 +5328,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs <= basic_json(rhs); } @@ -5204,7 +5337,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -5225,7 +5358,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs > basic_json(rhs); } @@ -5234,7 +5367,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) > rhs; } @@ -5254,7 +5387,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs >= basic_json(rhs); } @@ -5263,7 +5396,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -5299,6 +5432,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return o; } +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in @@ -5310,6 +5446,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return o << j; } +#endif #endif // JSON_NO_IO /// @} @@ -5361,12 +5498,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { basic_json result; auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); p.parse(true, result); return result; } +#endif /// @brief check if the input is valid JSON /// @sa https://json.nlohmann.me/api/basic_json/accept/ @@ -5396,9 +5537,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static bool accept(detail::span_input_adapter&& i, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); } +#endif /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ @@ -5459,6 +5604,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { auto ia = i.get(); return format == input_format_t::json @@ -5467,10 +5615,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); } +#endif #if defined(__clang__) #pragma clang diagnostic pop #endif #ifndef JSON_NO_IO +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in @@ -5482,6 +5634,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return operator>>(i, j); } +#endif /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ @@ -5829,9 +5982,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) @@ -5839,9 +5996,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler); } +#endif /// @brief create a JSON value from an input in MessagePack format /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ @@ -5874,18 +6035,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_msgpack(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_msgpack(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions); } +#endif /// @brief create a JSON value from an input in UBJSON format /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ @@ -5918,18 +6087,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_ubjson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_ubjson(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions); } +#endif /// @brief create a JSON value from an input in BJData format /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ @@ -5956,6 +6133,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bjdata(ptr, ptr + len)) + static basic_json from_bjdata(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else + { + return from_bjdata(ptr, ptr + len, strict, allow_exceptions); + } +#endif + /// @brief create a JSON value from an input in BON8 format /// @sa https://json.nlohmann.me/api/basic_json/from_bon8/ template @@ -5979,6 +6170,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions); } + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bon8(ptr, ptr + len)) + static basic_json from_bon8(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else + { + return from_bon8(ptr, ptr + len, strict, allow_exceptions); + } +#endif + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -6010,18 +6215,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_bson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_bson(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions); } +#endif /// @} ////////////////////////// @@ -6041,9 +6254,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) reference operator[](const ::nlohmann::json_pointer& ptr) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_unchecked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ @@ -6055,9 +6272,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) const_reference operator[](const ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_unchecked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ @@ -6069,9 +6290,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) reference at(const ::nlohmann::json_pointer& ptr) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_checked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ @@ -6083,9 +6308,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) const_reference at(const ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_checked(this); } +#endif /// @brief return flattened JSON value /// @sa https://json.nlohmann.me/api/basic_json/flatten/ @@ -7062,6 +7291,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO) + /// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_ltlt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete; + + /// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_gtgt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete; +#endif + NLOHMANN_JSON_NAMESPACE_END /////////////////////// diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cd7296458..b3d61e748 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3225,6 +3225,10 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_DISABLE_ENUM_SERIALIZATION 0 #endif +#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS + #define JSON_DELETE_DEPRECATED_FUNCTIONS 0 +#endif + #ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0 #endif @@ -4901,6 +4905,21 @@ T conditional_static_cast(U value) return value; } +// like conditional_static_cast, but converts to bool by comparing with zero, +// because MSVC 2015 warns about any conversion to bool (C4800), even with an +// explicit cast; used for enums whose underlying type is bool +template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > +T bool_aware_static_cast(U value) +{ + return conditional_static_cast(value); +} + +template::value, int> = 0> +bool bool_aware_static_cast(U value) +{ + return value != U(); +} + template using all_integral = conjunction...>; @@ -5696,7 +5715,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) typename BasicJsonType::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); - e = static_cast(static_cast(val)); + e = static_cast(bool_aware_static_cast(val)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -7063,9 +7082,20 @@ struct external_constructor ///////////// #ifdef JSON_HAS_CPP_17 +// whether storing the value of a std::optional cannot throw; MSVC 2017 +// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so +// the exception would call std::terminate (#5642) +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 + template + using is_nothrow_optional_to_json = std::false_type; +#else + template + using is_nothrow_optional_to_json = std::is_nothrow_assignable; +#endif + template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(is_nothrow_optional_to_json::value) { if (opt.has_value()) { @@ -7141,7 +7171,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; - external_constructor::construct(j, static_cast(e)); + external_constructor::construct(j, bool_aware_static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -15117,10 +15147,11 @@ class binary_reader } // the value is -1 - number, which fits into number_integer_t - // whenever number does + // whenever number does; the outer cast undoes the integral promotion + // for number_integer_t types narrower than int if (JSON_HEDLEY_LIKELY(value_in_range_of(number))) { - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } // like the lexer does for JSON text, store a value too small for @@ -18964,7 +18995,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -18987,7 +19019,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -18998,7 +19031,8 @@ class binary_reader and NaN in the input are passed on unchanged. Integers only overflow if number_float_t cannot represent 2^64, e.g., a half-precision type. - @tparam NumberType a floating-point or integer type + @tparam NumberType a floating-point type (emit_signed and emit_unsigned + convert integers to long double first) @param[in] number the number @return whether the SAX parser accepted the value @@ -22130,9 +22164,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/ JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) operator string_t() const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return to_string(); } +#endif #ifndef JSON_NO_IO /// @brief write string representation of the JSON pointer to stream @@ -23071,9 +23109,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer)) bool operator==(const string_t& rhs) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return *this == json_pointer(rhs); } +#endif /// @brief 3-way compares two JSON pointers template @@ -23151,18 +23193,26 @@ template& lhs, const StringType& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return lhs == json_pointer(rhs); } +#endif template::string_t> JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) inline bool operator==(const StringType& lhs, const json_pointer& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return json_pointer(lhs) == rhs; } +#endif template inline bool operator!=(const json_pointer& lhs, @@ -23176,18 +23226,26 @@ template& lhs, const StringType& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return !(lhs == rhs); } +#endif template::string_t> JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) inline bool operator!=(const StringType& lhs, const json_pointer& rhs) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return !(lhs == rhs); } +#endif template inline bool operator<(const json_pointer& lhs, @@ -23729,6 +23787,7 @@ class binary_writer @throw type_error.316 if a string value or an object key is not valid UTF-8 @throw type_error.317 if @a j is not an object + @throw type_error.321 if a value nested in @a j is discarded */ void write_bson(const BasicJsonType& j) { @@ -23760,6 +23819,7 @@ class binary_writer @param[in] j JSON value to serialize @throw type_error.316 if a string value or an object key is not valid UTF-8 + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_cbor(const BasicJsonType& j) { @@ -23924,7 +23984,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "CBOR"); } } @@ -23984,6 +24044,7 @@ class binary_writer /*! @param[in] j JSON value to serialize + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_msgpack(const BasicJsonType& j) { @@ -24257,7 +24318,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, "MessagePack"); } } @@ -24270,6 +24331,7 @@ class binary_writer @param[in] bjdata_version which BJData version to use, default is draft2 @throw type_error.316 if a string value or an object key is not valid UTF-8 + @throw type_error.321 if @a j or a value nested in it is discarded */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, @@ -24503,7 +24565,7 @@ class binary_writer case value_t::discarded: default: - break; + throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON"); } } @@ -24523,6 +24585,15 @@ class binary_writer } private: + /*! + @brief throws because @a j is discarded and cannot be serialized + @throw type_error.321 always + */ + JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name) + { + JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j)); + } + ////////// // BSON // ////////// @@ -24774,6 +24845,7 @@ class binary_writer into a byte, before anything is written @throw type_error.316 if @a j is a string that is not valid UTF-8, before anything is written + @throw type_error.321 if @a j is discarded */ std::size_t calc_bson_value_size(const BasicJsonType& j) { @@ -24800,10 +24872,12 @@ class binary_writer case value_t::null: return 0ul; + case value_t::discarded: + throw_on_discarded(j, "BSON"); + // LCOV_EXCL_START case value_t::object: case value_t::array: - case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return 0ul; @@ -24840,10 +24914,12 @@ class binary_writer case value_t::null: return write_bson_null(name); + case value_t::discarded: + throw_on_discarded(j, "BSON"); + // LCOV_EXCL_START case value_t::object: case value_t::array: - case value_t::discarded: default: JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) return; @@ -24910,6 +24986,8 @@ class binary_writer byte, before anything is written @throw type_error.316 if a string value or a key is not valid UTF-8, before anything is written + @throw type_error.321 if a value nested in @a document is discarded, + before anything is written */ std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector& nested_sizes) { @@ -29934,100 +30012,210 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// constructor for rvalue binary arrays (internal type) json_value(binary_t&& value) : binary(create(std::move(value))) {} - void destroy(value_t t) +private: + // raw, allocation-free transfer of m_data from src to dst: no + // set_parents()/assert_invariant() (the former is O(#children) per + // call under JSON_DIAGNOSTICS, which would make the walk below + // quadratic); dst takes ownership, src is left as value_t::null. + static void take(basic_json& dst, basic_json& src) noexcept + { + dst.m_data.m_type = src.m_data.m_type; + dst.m_data.m_value = src.m_data.m_value; + src.m_data.m_type = value_t::null; + } + + // true if v is not an array/object, or is an already-empty one + static bool has_no_children(const basic_json& v) noexcept + { + switch (v.m_data.m_type) + { + case value_t::array: + return v.m_data.m_value.array->empty(); + case value_t::object: + return v.m_data.m_value.object->empty(); + default: + return true; + } + } + + static basic_json& last_child(basic_json& v) + { + if (v.m_data.m_type == value_t::array) + { + return v.m_data.m_value.array->back(); + } + JSON_ASSERT(v.m_data.m_type == value_t::object); + return v.m_data.m_value.object->rbegin()->second; + } + + // removes the last child of a non-empty array/object v; this never + // allocates, and since it is only ever called when that child is a + // scalar or an already-empty array/object, destroying it never + // recurses more than one level deep (see destroy() below) + static void pop_last_child(basic_json& v) + { + if (v.m_data.m_type == value_t::array) + { + v.m_data.m_value.array->pop_back(); + } + else + { + JSON_ASSERT(v.m_data.m_type == value_t::object); + // erase() needs a forward iterator, so std::prev(end()) is + // used here rather than rbegin() (see last_child() above) + v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end())); + } + } + + // deallocates the (already empty) array/object held by v; this is + // the same allocator-based free the old recursive implementation + // used, just factored out so every level of the walk in destroy() + // can share it + static void free_container(basic_json& v) noexcept + { + if (v.m_data.m_type == value_t::array) + { + JSON_ASSERT(v.m_data.m_value.array->empty()); + AllocatorType alloc; + std::allocator_traits::destroy(alloc, v.m_data.m_value.array); + std::allocator_traits::deallocate(alloc, v.m_data.m_value.array, 1); + } + else + { + JSON_ASSERT(v.m_data.m_type == value_t::object); + JSON_ASSERT(v.m_data.m_value.object->empty()); + AllocatorType alloc; + std::allocator_traits::destroy(alloc, v.m_data.m_value.object); + std::allocator_traits::deallocate(alloc, v.m_data.m_value.object, 1); + } + v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed + } + +public: + void destroy_string() noexcept + { + if (string == nullptr) + { + // not initialized (e.g., due to exception in the ctor) + return; + } + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + } + + void destroy_binary() noexcept + { + if (binary == nullptr) + { + // not initialized (e.g., due to exception in the ctor) + return; + } + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + } + + // t must be value_t::array or value_t::object + void destroy_container(value_t t) noexcept { if ( (t == value_t::object && object == nullptr) || - (t == value_t::array && array == nullptr) || - (t == value_t::string && string == nullptr) || - (t == value_t::binary && binary == nullptr) + (t == value_t::array && array == nullptr) ) { // not initialized (e.g., due to exception in the ctor) return; } - if (t == value_t::array || t == value_t::object) + + // Destroy the tree without recursing per nesting level and + // without any heap allocation: a heap-allocated flattening + // stack (the previous implementation) can itself throw + // bad_alloc, which would escape this noexcept destructor and + // terminate the program (#5135). + // + // Instead, walk down the "last child" chain, reversing links + // as we go: cur is the container currently being emptied, + // and prev is its parent (value_t::null when there is none). + // Each parent's last child slot doubles as storage for that + // parent's own parent link while we are below it, so no + // extra memory is needed. We only ever remove a child once + // it is a scalar or an empty array/object, which neither + // allocates nor recurses more than one level deep. + // + // This json_value is not itself a basic_json, so the + // top-level container is first moved into a local stand-in + // ("cur"); a default-constructed basic_json has a null + // pointer in its m_value (see data::m_value's initializer), + // so swapping it with *this leaves this union's own pointer + // null, and it is never looked at or freed a second time. + basic_json cur; + cur.m_data.m_type = t; + using std::swap; + swap(cur.m_data.m_value, *this); + + basic_json prev; // value_t::null: no parent + + while (true) { - // flatten the current json_value to a heap-allocated stack - std::vector stack; - - // move the top-level items to stack - if (t == value_t::array) + if (has_no_children(cur)) { - stack.reserve(array->size()); - std::move(array->begin(), array->end(), std::back_inserter(stack)); - } - else - { - stack.reserve(object->size()); - for (auto&& it : *object) + if (prev.m_data.m_type == value_t::null) { - stack.push_back(std::move(it.second)); - } - } - - while (!stack.empty()) - { - // move the last item to a local variable to be processed - basic_json current_item(std::move(stack.back())); - stack.pop_back(); - - // if current_item is array/object, move - // its children to the stack to be processed later - if (current_item.is_array()) - { - std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); - - current_item.m_data.m_value.array->clear(); - } - else if (current_item.is_object()) - { - for (auto&& it : *current_item.m_data.m_value.object) - { - stack.push_back(std::move(it.second)); - } - - current_item.m_data.m_value.object->clear(); + free_container(cur); + return; // back at the top with nothing left to do } - // it's now safe that current_item gets destructed - // since it doesn't have any children + // ascend: detach the grandparent link from prev's + // last slot, drop that (now null) slot, free cur + // (it is empty), then move up one level + basic_json gp; + take(gp, last_child(prev)); + pop_last_child(prev); + + free_container(cur); + + take(cur, prev); + take(prev, gp); + continue; } + + basic_json& cur_last_ref = last_child(cur); + + if (has_no_children(cur_last_ref)) + { + // scalar, or already-empty array/object + pop_last_child(cur); + continue; + } + + // descend into the non-empty last child, reversing the + // link: its slot takes over prev, and the child becomes + // the new cur + basic_json tmp; + take(tmp, cur_last_ref); + take(cur_last_ref, prev); + take(prev, cur); + take(cur, tmp); } + } + void destroy(value_t t) + { switch (t) { - case value_t::object: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, object); - std::allocator_traits::deallocate(alloc, object, 1); - break; - } - - case value_t::array: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, array); - std::allocator_traits::deallocate(alloc, array, 1); - break; - } - case value_t::string: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, string); - std::allocator_traits::deallocate(alloc, string, 1); + destroy_string(); break; - } case value_t::binary: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, binary); - std::allocator_traits::deallocate(alloc, binary, 1); + destroy_binary(); + break; + + case value_t::object: + case value_t::array: + destroy_container(t); break; - } case value_t::null: case value_t::boolean: @@ -30036,9 +30224,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case value_t::number_float: case value_t::discarded: default: - { break; - } } } }; @@ -32814,9 +33000,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) ValueType value(const ::nlohmann::json_pointer& ptr, const ValueType& default_value) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return value(ptr.convert(), default_value); } +#endif template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type::type, detail::enable_if_t < @@ -32825,9 +33015,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec && !std::is_same>::value, int > = 0 > JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) ReturnType value(const ::nlohmann::json_pointer& ptr, ValueType && default_value) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return value(ptr.convert(), std::forward(default_value)); } +#endif /// @brief access the first element /// @sa https://json.nlohmann.me/api/basic_json/front/ @@ -33185,9 +33379,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) bool contains(const typename ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.contains(this); } +#endif /// @} @@ -33298,9 +33496,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(reference ref) noexcept +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ref.items(); } +#endif /// @brief wrapper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ @@ -33309,9 +33511,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) static iteration_proxy iterator_wrapper(const_reference ref) noexcept +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ref.items(); } +#endif /// @brief helper to access iterator member functions in range-based for /// @sa https://json.nlohmann.me/api/basic_json/items/ @@ -34400,7 +34606,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -34409,7 +34615,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -34430,11 +34636,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif } + // The friend comparisons with a scalar name the JSON type via decltype of + // their parameter in noexcept, because older MSVC versions do not see the + // class scope there: MSVC 2015 and 2017 take basic_json as the template, + // and MSVC 2019 16.0 rejects member types and template parameters. + /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs == basic_json(rhs); } @@ -34443,7 +34654,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) == rhs; } @@ -34459,7 +34670,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs != basic_json(rhs); } @@ -34468,7 +34679,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) != rhs; } @@ -34488,7 +34699,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs < basic_json(rhs); } @@ -34497,7 +34708,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) < rhs; } @@ -34517,7 +34728,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs <= basic_json(rhs); } @@ -34526,7 +34737,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -34547,7 +34758,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs > basic_json(rhs); } @@ -34556,7 +34767,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) > rhs; } @@ -34576,7 +34787,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs >= basic_json(rhs); } @@ -34585,7 +34796,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -34621,6 +34832,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return o; } +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in @@ -34632,6 +34846,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return o << j; } +#endif #endif // JSON_NO_IO /// @} @@ -34683,12 +34898,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { basic_json result; auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); p.parse(true, result); return result; } +#endif /// @brief check if the input is valid JSON /// @sa https://json.nlohmann.me/api/basic_json/accept/ @@ -34718,9 +34937,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static bool accept(detail::span_input_adapter&& i, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); } +#endif /// @brief generate SAX events /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ @@ -34781,6 +35004,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool ignore_comments = false, const bool ignore_trailing_commas = false) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { auto ia = i.get(); return format == input_format_t::json @@ -34789,10 +35015,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) : detail::binary_reader(std::move(ia), format).sax_parse(sax, strict); } +#endif #if defined(__clang__) #pragma clang diagnostic pop #endif #ifndef JSON_NO_IO +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in @@ -34804,6 +35034,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return operator>>(i, j); } +#endif /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ @@ -35151,9 +35382,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) @@ -35161,9 +35396,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool strict = true, const bool allow_exceptions = true, const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler); } +#endif /// @brief create a JSON value from an input in MessagePack format /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ @@ -35196,18 +35435,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_msgpack(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_msgpack(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) static basic_json from_msgpack(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions); } +#endif /// @brief create a JSON value from an input in UBJSON format /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ @@ -35240,18 +35487,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_ubjson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_ubjson(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) static basic_json from_ubjson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions); } +#endif /// @brief create a JSON value from an input in BJData format /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ @@ -35278,6 +35533,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler); } + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bjdata(ptr, ptr + len)) + static basic_json from_bjdata(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else + { + return from_bjdata(ptr, ptr + len, strict, allow_exceptions); + } +#endif + /// @brief create a JSON value from an input in BON8 format /// @sa https://json.nlohmann.me/api/basic_json/from_bon8/ template @@ -35301,6 +35570,20 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions); } + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bon8(ptr, ptr + len)) + static basic_json from_bon8(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else + { + return from_bon8(ptr, ptr + len, strict, allow_exceptions); + } +#endif + /// @brief create a JSON value from an input in BSON format /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ template @@ -35332,18 +35615,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json from_bson(const T* ptr, std::size_t len, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_bson(ptr, ptr + len, strict, allow_exceptions); } +#endif JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) static basic_json from_bson(detail::span_input_adapter&& i, const bool strict = true, const bool allow_exceptions = true) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions); } +#endif /// @} ////////////////////////// @@ -35363,9 +35654,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) reference operator[](const ::nlohmann::json_pointer& ptr) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_unchecked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ @@ -35377,9 +35672,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) const_reference operator[](const ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_unchecked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ @@ -35391,9 +35690,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) reference at(const ::nlohmann::json_pointer& ptr) +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_checked(this); } +#endif /// @brief access specified element via JSON Pointer /// @sa https://json.nlohmann.me/api/basic_json/at/ @@ -35405,9 +35708,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template::value, int> = 0> JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) const_reference at(const ::nlohmann::json_pointer& ptr) const +#if JSON_DELETE_DEPRECATED_FUNCTIONS + = delete; +#else { return ptr.get_checked(this); } +#endif /// @brief return flattened JSON value /// @sa https://json.nlohmann.me/api/basic_json/flatten/ @@ -36384,6 +36691,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO) + /// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_ltlt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete; + + /// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_gtgt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete; +#endif + NLOHMANN_JSON_NAMESPACE_END /////////////////////// @@ -36567,6 +36886,7 @@ struct formatter // NOLINT(cert-dcl58-c #undef JSON_PRECISE_STREAM_POSITION #undef JSON_STRICT_NUL_HANDLING #undef JSON_STRICT_BINARY_UTF8 + #undef JSON_DELETE_DEPRECATED_FUNCTIONS #undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aac6fee5e..8735934de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).") set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.") +set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as / (e.g. 0/2), to split them across CI jobs with a time limit.") # using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default") if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}") @@ -288,6 +289,33 @@ elseif(NOT json_32bit_test) list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp) endif() +# with JSON_TestShard=/, keep every -th unit test file, +# starting at (the glob is sorted, so the split is stable) +set(test_shard_index 0) +if(NOT "${JSON_TestShard}" STREQUAL "") + if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$") + message(FATAL_ERROR "JSON_TestShard must be /, e.g. 0/2, not '${JSON_TestShard}'.") + endif() + set(test_shard_index ${CMAKE_MATCH_1}) + set(test_shard_count ${CMAKE_MATCH_2}) + if(NOT test_shard_index LESS test_shard_count) + message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.") + endif() + list(LENGTH files test_file_count) + set(shard_files "") + set(file_position 0) + foreach(file ${files}) + math(EXPR file_shard "${file_position} % ${test_shard_count}") + if(file_shard EQUAL test_shard_index) + list(APPEND shard_files ${file}) + endif() + math(EXPR file_position "${file_position} + 1") + endforeach() + set(files ${shard_files}) + list(LENGTH files shard_file_count) + message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files") +endif() + foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() @@ -305,6 +333,9 @@ if(json_32bit_test_only) return() endif() +# the following variants of single test files are only built in the first shard +if(test_shard_index EQUAL 0) + # test legacy comparison of discarded values json_test_set_test_options(test-comparison_legacy COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 @@ -332,6 +363,8 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} ) +endif() + # *DO NOT* use json_test_set_test_options() below this line ############################################################################# diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index bc283ab28..c716b3a5b 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -521,6 +521,10 @@ struct countdown_allocator : std::allocator TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)") { + // MSVC 2015's debug STL constructs the containers' debug proxies through + // the allocator in noexcept constructors, so a failing construction crashes + // the program there instead of throwing std::bad_alloc. Nothing to check. +#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) using countdown_json = nlohmann::basic_json 0); +#endif } namespace @@ -602,3 +607,88 @@ TEST_CASE("bad my_allocator::construct") j["test"].push_back("should not leak"); } } + +namespace +{ +std::size_t counting_allocator_allocations = 0; +std::size_t counting_allocator_deallocations = 0; + +template +struct counting_allocator : std::allocator +{ + using std::allocator::allocator; + + T* allocate(std::size_t n) + { + ++counting_allocator_allocations; + return std::allocator::allocate(n); + } + + void deallocate(T* p, std::size_t n) + { + ++counting_allocator_deallocations; + std::allocator::deallocate(p, n); + } + + template + struct rebind + { + using other = counting_allocator; + }; +}; +} // namespace + +TEST_CASE("destructor performs no allocation, only deallocation") +{ + // see https://github.com/nlohmann/json/issues/4842 and + // https://github.com/nlohmann/json/issues/5135: destroying nested + // arrays/objects used to allocate a temporary stack (first with + // std::allocator, later - after #4842 - with the provided allocator). + // Since that stack could itself throw bad_alloc from inside the + // noexcept destructor (#5135), destroy() no longer allocates anything: + // it only ever frees what is already there. + using counting_json = nlohmann::basic_json; + + SECTION("array") + { + auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory) + const auto allocations_before = counting_allocator_allocations; + const auto deallocations_before = counting_allocator_deallocations; + delete j; // NOLINT(cppcoreguidelines-owning-memory) + CHECK(counting_allocator_allocations == allocations_before); + CHECK(counting_allocator_deallocations > deallocations_before); + } + + SECTION("object") + { + auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory) + const auto allocations_before = counting_allocator_allocations; + const auto deallocations_before = counting_allocator_deallocations; + delete j; // NOLINT(cppcoreguidelines-owning-memory) + CHECK(counting_allocator_allocations == allocations_before); + CHECK(counting_allocator_deallocations > deallocations_before); + } + + SECTION("mixed tree of empty/non-empty arrays and objects") + { + auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory) + { + {"empty_obj", counting_json::object()}, + {"empty_arr", counting_json::array()}, + {"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}}, + {"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})} + }); + const auto allocations_before = counting_allocator_allocations; + const auto deallocations_before = counting_allocator_deallocations; + delete j; // NOLINT(cppcoreguidelines-owning-memory) + CHECK(counting_allocator_allocations == allocations_before); + CHECK(counting_allocator_deallocations > deallocations_before); + } +} diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 0098541cf..cd989ef88 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -9,6 +9,14 @@ #include "doctest_compatibility.h" #define JSON_TESTS_PRIVATE +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -106,10 +114,59 @@ TEST_CASE("BJData") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to BJData json const j = json::value_t::discarded; - const auto result = json::to_bjdata(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } + + SECTION("optimized array of all-discarded elements") + { + json const j = {discarded, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&); +#endif + } } SECTION("null") @@ -4490,3 +4547,33 @@ TEST_CASE("BJData roundtrips" * doctest::skip()) } } } + +TEST_CASE("issue #5648 - from_bjdata(ptr, len) must read len bytes, not treat ptr as a C string") +{ + // to_bjdata() encodes the integer 0 as the two bytes 'i' 0x00 (a BJData + // type marker followed by the value byte 0x00), so the packed data + // below contains a 0x00 byte before its end. + const json j = {{"a", 0}}; + const std::vector packed = json::to_bjdata(j); + bool contains_nul = false; + for (const auto byte : packed) + { + contains_nul |= (byte == 0x00); + } + REQUIRE(contains_nul); + +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED + // before the fix, from_bjdata had no (ptr, len) overload, so this call + // bound to from_bjdata(InputType&&, bool strict) instead: ptr was read + // as a NUL-terminated C string (stopping at the embedded 0x00 byte), and + // len was silently converted to the strict flag. The deprecated + // overload added for this issue forwards to from_bjdata(ptr, ptr + len, + // ...) instead, like from_ubjson's deprecated (ptr, len) overload does. + json result; + CHECK_NOTHROW(result = json::from_bjdata(packed.data(), packed.size())); + CHECK(result == j); + + // len must not collapse into the strict flag either + CHECK(json::from_bjdata(packed.data(), packed.size(), false) == j); +#endif +} diff --git a/tests/src/unit-bon8.cpp b/tests/src/unit-bon8.cpp index 491189867..b1d82ca29 100644 --- a/tests/src/unit-bon8.cpp +++ b/tests/src/unit-bon8.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; #ifdef JSON_TEST_NO_GLOBAL_UDLS @@ -1014,6 +1022,36 @@ TEST_CASE("BON8 roundtrips" * doctest::skip()) } } +TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr as a C string") +{ + // to_bon8() encodes the integer 40 as the two bytes 0xC2 0x00 (a BON8 + // lead byte followed by a continuation byte of 0x00), so the packed data + // below contains a 0x00 byte before its end. + const json j = {{"a", 40}, {"b", "x"}}; + const std::vector packed = json::to_bon8(j); + bool contains_nul = false; + for (const auto byte : packed) + { + contains_nul |= (byte == 0x00); + } + REQUIRE(contains_nul); + +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED + // before the fix, from_bon8 had no (ptr, len) overload, so this call + // bound to from_bon8(InputType&&, bool strict) instead: ptr was read as + // a NUL-terminated C string (stopping at the embedded 0x00 byte), and + // len was silently converted to the strict flag. The deprecated + // overload added for this issue forwards to from_bon8(ptr, ptr + len, + // ...) instead, like from_cbor's deprecated (ptr, len) overload does. + json result; + CHECK_NOTHROW(result = json::from_bon8(packed.data(), packed.size())); + CHECK(result == j); + + // len must not collapse into the strict flag either + CHECK(json::from_bon8(packed.data(), packed.size(), false) == j); +#endif +} + #ifdef JSON_HAS_CPP_17 TEST_CASE("BON8 with std::byte") { diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 17819dc65..352ad6f1a 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -141,6 +149,54 @@ TEST_CASE("BSON") json const j = std::vector {1, 2, 3, 4, 5, 6, 7}; CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&); } + + SECTION("discarded") + { + json const j = json::value_t::discarded; + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&); + } + } + + SECTION("discarded values nested in a container cannot be serialized to BSON") + { + json const discarded = json::value_t::discarded; + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } + + SECTION("in an array that is an object value") + { + json j; + j["a"] = json::array({1, discarded, 2}); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in object)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json j; + j["outer"] = middle_object; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&); +#endif + } } SECTION("keys containing code-point U+0000 cannot be serialized to BSON") @@ -1261,8 +1317,10 @@ TEST_CASE("BSON input that cannot be read is discarded by every overload") CHECK_THROWS_AS(_ = json::from_bson(input.begin(), input.end()), json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); CHECK(json::from_bson(input.begin(), input.end(), true, false).is_discarded()); +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(json::from_bson(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_bson({input.data(), input.size()}, true, false).is_discarded()); +#endif } TEST_CASE("BSON SAX parsing stops at every event") @@ -1740,6 +1798,7 @@ TEST_CASE("BSON roundtrips" * doctest::skip()) CHECK(j1 == j2); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED { INFO_WITH_TEMP(filename + ": uint8_t* and size"); // parse JSON file @@ -1754,6 +1813,7 @@ TEST_CASE("BSON roundtrips" * doctest::skip()) // compare parsed JSON values CHECK(j1 == j2); } +#endif { INFO_WITH_TEMP(filename + ": output to output adapters"); diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 546e256b1..38763b4db 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -30,10 +38,49 @@ TEST_CASE("CBOR") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to CBOR json const j = json::value_t::discarded; - const auto result = json::to_cbor(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&); +#endif + } } SECTION("NaN") @@ -2215,8 +2262,10 @@ TEST_CASE("CBOR input that cannot be read is discarded by every overload") CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&); CHECK(json::from_cbor(input, true, false).is_discarded()); CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded()); +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded()); +#endif // a string that ends early, read through iterators that are not // contiguous and have to be copied from one element at a time @@ -2613,12 +2662,14 @@ TEST_CASE("CBOR roundtrips" * doctest::skip()) CHECK(j1 == j2); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED { INFO_WITH_TEMP(filename + ": uint8_t* and size"); json j2; CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()})); CHECK(j1 == j2); } +#endif { INFO_WITH_TEMP(filename + ": output to output adapters"); diff --git a/tests/src/unit-conversions2.cpp b/tests/src/unit-conversions2.cpp index eca482b83..c543e4ff8 100644 --- a/tests/src/unit-conversions2.cpp +++ b/tests/src/unit-conversions2.cpp @@ -191,8 +191,10 @@ TEST_CASE("value conversion") { enum class bool_enum : bool { off, on }; - CHECK(json(bool_enum::off).get() == bool_enum::off); - CHECK(json(bool_enum::on).get() == bool_enum::on); + // the extra parentheses keep doctest from printing the enum via its + // underlying type, which MSVC 2015 reports as C4800 + CHECK((json(bool_enum::off).get() == bool_enum::off)); + CHECK((json(bool_enum::on).get() == bool_enum::on)); } #endif @@ -851,8 +853,11 @@ TEST_CASE("std::optional") CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); // the conversion is noexcept exactly when converting the contained value is + // (except with MSVC 2017, where it is never noexcept, see to_json.hpp) +#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920 static_assert(!std::is_nothrow_constructible&>::value); static_assert(std::is_nothrow_constructible&>::value); +#endif } #endif } diff --git a/tests/src/unit-delete_deprecated_functions.cpp b/tests/src/unit-delete_deprecated_functions.cpp new file mode 100644 index 000000000..a1d895370 --- /dev/null +++ b/tests/src/unit-delete_deprecated_functions.cpp @@ -0,0 +1,229 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +// This file tests the opt-in JSON_DELETE_DEPRECATED_FUNCTIONS, so it defines the +// macro itself instead of relying on the build configuration. +#ifdef JSON_DELETE_DEPRECATED_FUNCTIONS + #undef JSON_DELETE_DEPRECATED_FUNCTIONS +#endif +#define JSON_DELETE_DEPRECATED_FUNCTIONS 1 + +#include +using nlohmann::json; + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +template +struct make_void +{ + using type = void; +}; + +template +using void_t = typename make_void::type; + +// A deleted function is still found by overload resolution, but naming it in an +// unevaluated operand is ill-formed, so each of the following traits is false +// exactly if the expression selects a deleted (or no) function. +#define JSON_TEST_DETECT(name, expr) \ + template \ + struct name : std::false_type {}; \ + template \ + struct name> : std::true_type {} + +using ptr_t = const std::uint8_t*; + +JSON_TEST_DETECT(from_cbor_ptr_len, J::from_cbor(std::declval(), std::declval())); +JSON_TEST_DETECT(from_msgpack_ptr_len, J::from_msgpack(std::declval(), std::declval())); +JSON_TEST_DETECT(from_ubjson_ptr_len, J::from_ubjson(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bjdata_ptr_len, J::from_bjdata(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bon8_ptr_len, J::from_bon8(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bson_ptr_len, J::from_bson(std::declval(), std::declval())); + +JSON_TEST_DETECT(from_cbor_ptr_ptr, J::from_cbor(std::declval(), std::declval())); +JSON_TEST_DETECT(from_msgpack_ptr_ptr, J::from_msgpack(std::declval(), std::declval())); +JSON_TEST_DETECT(from_ubjson_ptr_ptr, J::from_ubjson(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bjdata_ptr_ptr, J::from_bjdata(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bon8_ptr_ptr, J::from_bon8(std::declval(), std::declval())); +JSON_TEST_DETECT(from_bson_ptr_ptr, J::from_bson(std::declval(), std::declval())); + +JSON_TEST_DETECT(from_cbor_init_list, J::from_cbor({std::declval(), std::declval()})); +JSON_TEST_DETECT(from_msgpack_init_list, J::from_msgpack({std::declval(), std::declval()})); +JSON_TEST_DETECT(from_ubjson_init_list, J::from_ubjson({std::declval(), std::declval()})); +JSON_TEST_DETECT(from_bson_init_list, J::from_bson({std::declval(), std::declval()})); + +JSON_TEST_DETECT(parse_init_list, J::parse({std::declval(), std::declval()})); +JSON_TEST_DETECT(accept_init_list, J::accept({std::declval(), std::declval()})); +JSON_TEST_DETECT(sax_parse_init_list, J::sax_parse({std::declval(), std::declval()}, std::declval*>())); +JSON_TEST_DETECT(parse_ptr_ptr, J::parse(std::declval(), std::declval())); + +JSON_TEST_DETECT(iterator_wrapper, J::iterator_wrapper(std::declval())); +JSON_TEST_DETECT(iterator_wrapper_const, J::iterator_wrapper(std::declval())); +JSON_TEST_DETECT(items, std::declval().items()); + +JSON_TEST_DETECT(json_ltlt_istream, std::declval() << std::declval()); +JSON_TEST_DETECT(istream_gtgt_json, std::declval() >> std::declval()); +JSON_TEST_DETECT(json_gtgt_ostream, std::declval() >> std::declval()); +JSON_TEST_DETECT(ostream_ltlt_json, std::declval() << std::declval()); + +JSON_TEST_DETECT(ptr_eq_ptr, std::declval() == std::declval()); +JSON_TEST_DETECT(ptr_ne_ptr, std::declval() != std::declval()); +JSON_TEST_DETECT(ptr_eq_string, std::declval() == std::declval()); +JSON_TEST_DETECT(string_eq_ptr, std::declval() == std::declval()); +JSON_TEST_DETECT(ptr_eq_c_string, std::declval() == std::declval()); +JSON_TEST_DETECT(c_string_eq_ptr, std::declval() == std::declval()); +JSON_TEST_DETECT(ptr_ne_string, std::declval() != std::declval()); +JSON_TEST_DETECT(string_ne_ptr, std::declval() != std::declval()); +JSON_TEST_DETECT(ptr_to_string, std::declval().to_string()); + +// json_pointer with a basic_json type as template argument +template +using legacy_ptr_t = const nlohmann::json_pointer& ; +JSON_TEST_DETECT(legacy_ptr_value, std::declval().value(std::declval>(), 0)); +JSON_TEST_DETECT(legacy_ptr_value_rvalue, std::declval().value(std::declval>(), std::string())); +JSON_TEST_DETECT(legacy_ptr_contains, std::declval().contains(std::declval>())); +JSON_TEST_DETECT(legacy_ptr_subscript, std::declval()[std::declval>()]); +JSON_TEST_DETECT(legacy_ptr_subscript_const, std::declval()[std::declval>()]); +JSON_TEST_DETECT(legacy_ptr_at, std::declval().at(std::declval>())); +JSON_TEST_DETECT(legacy_ptr_at_const, std::declval().at(std::declval>())); +JSON_TEST_DETECT(ptr_value, std::declval().value(std::declval(), 0)); +JSON_TEST_DETECT(ptr_at, std::declval().at(std::declval())); + +#undef JSON_TEST_DETECT +} // namespace + +TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS") +{ + // MSVC 2015 does not treat selecting a deleted function in decltype as a + // substitution failure, so the traits cannot tell deleted functions apart + // there; calling them still fails to compile +#if !(defined(_MSC_VER) && _MSC_VER < 1910) + SECTION("from_* with a pointer and a length") + { + // the overloads are deleted rather than removed, so the length cannot + // silently bind to the strict parameter of from_*(InputType&&, bool) + CHECK_FALSE(from_cbor_ptr_len::value); + CHECK_FALSE(from_msgpack_ptr_len::value); + CHECK_FALSE(from_ubjson_ptr_len::value); + CHECK_FALSE(from_bjdata_ptr_len::value); + CHECK_FALSE(from_bon8_ptr_len::value); + CHECK_FALSE(from_bson_ptr_len::value); + + // the replacement + CHECK(from_cbor_ptr_ptr::value); + CHECK(from_msgpack_ptr_ptr::value); + CHECK(from_ubjson_ptr_ptr::value); + CHECK(from_bjdata_ptr_ptr::value); + CHECK(from_bon8_ptr_ptr::value); + CHECK(from_bson_ptr_ptr::value); + } + + SECTION("initializer lists of a pointer and a length") + { + CHECK_FALSE(from_cbor_init_list::value); + CHECK_FALSE(from_msgpack_init_list::value); + CHECK_FALSE(from_ubjson_init_list::value); + CHECK_FALSE(from_bson_init_list::value); + CHECK_FALSE(parse_init_list::value); + CHECK_FALSE(accept_init_list::value); + CHECK_FALSE(sax_parse_init_list::value); + + // the replacement + CHECK(parse_ptr_ptr::value); + } + + SECTION("iterator_wrapper") + { + CHECK_FALSE(iterator_wrapper::value); + CHECK_FALSE(iterator_wrapper_const::value); + + // the replacement + CHECK(items::value); + } + + SECTION("stream operators with reversed operands") + { + CHECK_FALSE(json_ltlt_istream::value); + CHECK_FALSE(json_gtgt_ostream::value); + + // the replacement + CHECK(istream_gtgt_json::value); + CHECK(ostream_ltlt_json::value); + } + + SECTION("json_pointer") + { + // conversion to a string + CHECK_FALSE(std::is_constructible::value); + CHECK_FALSE(std::is_convertible::value); + + // comparison with a string + CHECK_FALSE(ptr_eq_string::value); + CHECK_FALSE(string_eq_ptr::value); + CHECK_FALSE(ptr_eq_c_string::value); + CHECK_FALSE(c_string_eq_ptr::value); + CHECK_FALSE(ptr_ne_string::value); + CHECK_FALSE(string_ne_ptr::value); + + // the replacement + CHECK(ptr_to_string::value); + CHECK(ptr_eq_ptr::value); + CHECK(ptr_ne_ptr::value); + } + + SECTION("json_pointer with a basic_json type as template argument") + { + CHECK_FALSE(legacy_ptr_value::value); + CHECK_FALSE(legacy_ptr_value_rvalue::value); + CHECK_FALSE(legacy_ptr_contains::value); + CHECK_FALSE(legacy_ptr_subscript::value); + CHECK_FALSE(legacy_ptr_subscript_const::value); + CHECK_FALSE(legacy_ptr_at::value); + CHECK_FALSE(legacy_ptr_at_const::value); + + // the replacement + CHECK(ptr_value::value); + CHECK(ptr_at::value); + } + +#endif + + SECTION("the non-deprecated functions still work") + { + const json j = {{"a", {1, 2}}}; + const auto cbor = json::to_cbor(j); + CHECK(json::from_cbor(cbor.data(), cbor.data() + cbor.size()) == j); + + const json::json_pointer ptr("/a/1"); + CHECK(ptr == json::json_pointer("/a/1")); + CHECK(ptr.to_string() == "/a/1"); + CHECK(j[ptr] == 2); + CHECK(j.at(ptr) == 2); + CHECK(j.value(ptr, 0) == 2); + CHECK(j.contains(ptr)); + + std::ostringstream os; + os << j; + CHECK(os.str() == R"({"a":[1,2]})"); + std::istringstream is(os.str()); + json parsed; + is >> parsed; + CHECK(parsed == j); + } +} diff --git a/tests/src/unit-deserialization.cpp b/tests/src/unit-deserialization.cpp index 2f9ff63fa..ca9b63112 100644 --- a/tests/src/unit-deserialization.cpp +++ b/tests/src/unit-deserialization.cpp @@ -16,6 +16,14 @@ #define JSON_TEST_STRICT_NUL_HANDLING_ENABLED 1 #endif +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; #ifdef JSON_TEST_NO_GLOBAL_UDLS @@ -291,6 +299,7 @@ TEST_CASE("deserialization") })); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED SECTION("operator<<") { std::stringstream ss; @@ -299,6 +308,7 @@ TEST_CASE("deserialization") j << ss; CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}})); } +#endif SECTION("operator>>") { @@ -423,6 +433,7 @@ TEST_CASE("deserialization") CHECK_THROWS_WITH_AS(_ = json::parse(nullptr), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED SECTION("operator<<") { std::stringstream ss; @@ -430,6 +441,7 @@ TEST_CASE("deserialization") json j; CHECK_THROWS_WITH_AS(j << ss, "[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing array - unexpected end of input; expected ']'", json::parse_error&); } +#endif SECTION("operator>>") { @@ -1148,9 +1160,9 @@ TEST_CASE("deserialization") { std::istringstream s(bom + "123 456"); json j; - j << s; + s >> j; CHECK(j == 123); - j << s; + s >> j; CHECK(j == 456); } } diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index 9a7a58e6b..1b59d8add 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -1551,6 +1551,10 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j // count(0) used to compile and then crash instead of failing to compile using nlohmann::detail::is_detected; + // MSVC 2015 does not treat selecting a deleted function in decltype as + // a substitution failure, so it detects the deleted overloads as + // callable; calling them still fails to compile +#if !(defined(_MSC_VER) && _MSC_VER < 1910) CHECK_FALSE(is_detected::value); CHECK_FALSE(is_detected::value); CHECK_FALSE(is_detected::value); @@ -1562,6 +1566,7 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j // another integral literal type must be rejected as well, not just int CHECK_FALSE(is_detected::value); +#endif // the valid overloads must remain callable CHECK(is_detected::value); diff --git a/tests/src/unit-items.cpp b/tests/src/unit-items.cpp index 81959db8d..71b0522a8 100644 --- a/tests/src/unit-items.cpp +++ b/tests/src/unit-items.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -19,6 +27,7 @@ DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING("-Wrange-loop-construct") +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED TEST_CASE("iterator_wrapper") { SECTION("object") @@ -715,6 +724,7 @@ TEST_CASE("iterator_wrapper") } } } +#endif TEST_CASE("items()") { diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 86c636c2a..b4ed2c9cc 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -9,6 +9,14 @@ #include "doctest_compatibility.h" #define JSON_TESTS_PRIVATE +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; #ifdef JSON_TEST_NO_GLOBAL_UDLS @@ -564,7 +572,9 @@ TEST_CASE("JSON pointers") std::stringstream ss; ss << ptr; CHECK(ptr.to_string() == ptr_str); +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(std::string(ptr) == ptr_str); +#endif CHECK(ss.str() == ptr_str); } } @@ -744,8 +754,6 @@ TEST_CASE("JSON pointers") SECTION("equality comparison") { - const char* ptr_cpstring = "/foo/bar"; - const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) std::string ptr_string{"/foo/bar"}; auto ptr1 = json::json_pointer(ptr_string); auto ptr2 = json::json_pointer(ptr_string); @@ -755,6 +763,12 @@ TEST_CASE("JSON pointers") CHECK(ptr1 == ptr2); + CHECK_FALSE(ptr1 != ptr2); + +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED + const char* ptr_cpstring = "/foo/bar"; + const char ptr_castring[] = "/foo/bar"; // NOLINT(misc-const-correctness,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) + CHECK(ptr1 == "/foo/bar"); CHECK(ptr1 == ptr_cpstring); CHECK(ptr1 == ptr_castring); @@ -765,8 +779,6 @@ TEST_CASE("JSON pointers") CHECK(ptr_castring == ptr1); CHECK(ptr_string == ptr1); - CHECK_FALSE(ptr1 != ptr2); - CHECK_FALSE(ptr1 != "/foo/bar"); CHECK_FALSE(ptr1 != ptr_cpstring); CHECK_FALSE(ptr1 != ptr_castring); @@ -788,6 +800,7 @@ TEST_CASE("JSON pointers") CHECK_THROWS_WITH_AS("/~~" == ptr1, "[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&); } +#endif } SECTION("less-than comparison") @@ -839,6 +852,7 @@ TEST_CASE("JSON pointers") json_ptr_j ptr_j{ptr_string}; json_ptr_oj ptr_oj{ptr_string}; +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(j.contains(ptr)); CHECK(j.contains(ptr_j)); CHECK(j.contains(ptr_oj)); @@ -851,6 +865,7 @@ TEST_CASE("JSON pointers") CHECK(j.value(ptr, "x") == j.value(ptr_j, "x")); CHECK(j.value(ptr, "x") == j.value(ptr_oj, "x")); +#endif CHECK(ptr == ptr_j); CHECK(ptr == ptr_oj); diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index c4b447d64..bc29b301b 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; #ifdef JSON_TEST_NO_GLOBAL_UDLS @@ -33,10 +41,49 @@ TEST_CASE("MessagePack") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to MessagePack json const j = json::value_t::discarded; - const auto result = json::to_msgpack(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&); +#endif + } } SECTION("null") @@ -1795,8 +1842,10 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload" CHECK_THROWS_AS(_ = json::from_msgpack(input.begin(), input.end()), json::parse_error&); CHECK(json::from_msgpack(input, true, false).is_discarded()); CHECK(json::from_msgpack(input.begin(), input.end(), true, false).is_discarded()); +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(json::from_msgpack(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_msgpack({input.data(), input.size()}, true, false).is_discarded()); +#endif } TEST_CASE("MessagePack SAX parsing stops at every event") @@ -2099,12 +2148,14 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip()) CHECK(j1 == j2); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED { INFO_WITH_TEMP(filename + ": uint8_t* and size"); json j2; CHECK_NOTHROW(j2 = json::from_msgpack({packed.data(), packed.size()})); CHECK(j1 == j2); } +#endif { INFO_WITH_TEMP(filename + ": output to output adapters"); diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 07236d342..81738d80b 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json; #endif #include +#include #include +#include #include #include #include @@ -107,6 +109,84 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") using float_json = nlohmann::basic_json; +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) +namespace +{ +// An allocator whose allocate() can be told to fail on demand, so tests can +// check that ~basic_json() tolerates - in fact, after #5135, never even +// triggers - an allocation failure. This replaces an earlier version of +// this test that overrode the process-wide ::operator new/::operator +// delete, which affected every allocation in the whole unit-regression2 +// binary rather than just the values under test. +std::size_t failing_allocator_allocations = 0; +std::size_t failing_allocator_deallocations = 0; +bool fail_next_allocation = false; + +template +struct failing_allocator : std::allocator +{ + using std::allocator::allocator; + + failing_allocator() noexcept = default; + template + failing_allocator(const failing_allocator& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor) + + T* allocate(std::size_t n) + { + if (fail_next_allocation) + { + fail_next_allocation = false; + throw std::bad_alloc(); + } + ++failing_allocator_allocations; + return std::allocator::allocate(n); + } + + void deallocate(T* p, std::size_t n) + { + ++failing_allocator_deallocations; + std::allocator::deallocate(p, n); + } + + template + struct rebind + { + using other = failing_allocator; + }; +}; + +using failing_json = nlohmann::basic_json; +using failing_ordered_json = nlohmann::basic_json; + +// builds `depth` levels of nesting around a scalar, iteratively (never +// recursing: each wrap only moves the previous, already-built value, which +// is O(1)), each level an array or an object depending on `nest_objects` +template +BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects) +{ + BasicJsonType v = 0; + for (std::size_t i = 0; i < depth; ++i) + { + if (nest_objects) + { + BasicJsonType wrapper = BasicJsonType::object(); + wrapper["x"] = std::move(v); + v = std::move(wrapper); + } + else + { + BasicJsonType wrapper = BasicJsonType::array(); + wrapper.push_back(std::move(v)); + v = std::move(wrapper); + } + } + return v; +} +} // namespace +#endif + ///////////////////////////////////////////////////////////////////// // for #1647 ///////////////////////////////////////////////////////////////////// @@ -569,7 +649,7 @@ TEST_CASE("regression tests 2") SECTION("issue #2067 - cannot serialize binary data to text JSON") { const std::array data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}}; - const json j = json::from_msgpack(data.data(), data.size()); + const json j = json::from_msgpack(data.begin(), data.end()); // dump() is nodiscard; this only checks that dumping does not throw CHECK_NOTHROW( utils::ignore_return_value( @@ -1526,4 +1606,211 @@ TEST_CASE("regression test - #3989 SAX parse_error() returning true") } } +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) +TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure") +{ + // Before the fix, ~basic_json() flattened a nested array/object into a + // heap-allocated std::vector to avoid recursing; that allocation could + // itself throw bad_alloc, which escapes a noexcept destructor and + // terminates the program. destroy() no longer allocates anything, so + // none of the sections below ever observe fail_next_allocation being + // consumed: CHECK(fail_next_allocation) confirms it was never touched. + + SECTION("the original report: a small, mixed array/object nest") + { + failing_allocator_allocations = 0; + failing_allocator_deallocations = 0; + { + failing_json j = failing_json::array( + { + failing_json::array({1, 2}), + failing_json::object({{"key", failing_json::array({3})}}) + }); + fail_next_allocation = true; + } // j is destroyed here, with every further allocation set to fail + + CHECK(fail_next_allocation); + fail_next_allocation = false; + CHECK(failing_allocator_deallocations > 0); + } + + SECTION("100000-deep nested array") + { + std::size_t allocations_before = 0; + { + failing_json j = make_deep_nest(100000, false); + allocations_before = failing_allocator_allocations; + fail_next_allocation = true; + } + + CHECK(fail_next_allocation); + fail_next_allocation = false; + CHECK(failing_allocator_allocations == allocations_before); + } + + SECTION("100000-deep nested object") + { + std::size_t allocations_before = 0; + { + failing_json j = make_deep_nest(100000, true); + allocations_before = failing_allocator_allocations; + fail_next_allocation = true; + } + + CHECK(fail_next_allocation); + fail_next_allocation = false; + CHECK(failing_allocator_allocations == allocations_before); + } + + SECTION("100000-deep nested ordered_json") + { + std::size_t allocations_before = 0; + { + failing_ordered_json j = make_deep_nest(100000, true); + allocations_before = failing_allocator_allocations; + fail_next_allocation = true; + } + + CHECK(fail_next_allocation); + fail_next_allocation = false; + CHECK(failing_allocator_allocations == allocations_before); + } + + SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object") + { + std::size_t allocations_before = 0; + { + failing_json wide = failing_json::array(); + for (std::size_t i = 0; i < 1000; ++i) + { + failing_json inner = failing_json::array(); + for (std::size_t k = 0; k < 1000; ++k) + { + inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}})); + } + wide.push_back(std::move(inner)); + } + + allocations_before = failing_allocator_allocations; + fail_next_allocation = true; + } + + CHECK(fail_next_allocation); + fail_next_allocation = false; + CHECK(failing_allocator_allocations == allocations_before); + } +} +#endif + +namespace +{ +// a single-element chain of `depth` arrays, built iteratively (never +// recursing: each wrap only moves the previous, already-built value) +template +BasicJsonType make_single_chain(std::size_t depth) +{ + BasicJsonType v = 1; + for (std::size_t i = 0; i < depth; ++i) + { + BasicJsonType wrapper = BasicJsonType::array(); + wrapper.push_back(std::move(v)); + v = std::move(wrapper); + } + return v; +} + +// copies value first, to make sure nothing was corrupted by building it, +// then lets both the copy and the original destruct via normal scope exit +template +void check_destroy_edge_case(const BasicJsonType& value) +{ + const BasicJsonType copy = value; + CHECK(copy == value); +} +} // namespace + +TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json) +{ + using binary_t = typename BasicJsonType::binary_t; + + SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars") + { + BasicJsonType root = BasicJsonType::array(); + root.push_back(BasicJsonType::object()); + root.push_back(BasicJsonType::array()); + root.push_back(BasicJsonType::object({{"k", 1}})); + root.push_back(BasicJsonType::array({1, 2, 3})); + root.push_back(nullptr); + root.push_back(true); + root.push_back(42); + root.push_back(3.14); + root.push_back("a string"); + root.push_back(BasicJsonType(binary_t({1, 2, 3}))); + check_destroy_edge_case(root); + } + + SECTION("container child in first position only") + { + BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5}); + check_destroy_edge_case(root); + } + + SECTION("container child in last position only") + { + BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})}); + check_destroy_edge_case(root); + } + + SECTION("container children in first and last position") + { + BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})}); + check_destroy_edge_case(root); + } + + SECTION("single-element chain, 1000 levels deep") + { + BasicJsonType root = make_single_chain(1000); + check_destroy_edge_case(root); + } + + SECTION("top-level empty array") + { + BasicJsonType root = BasicJsonType::array(); + check_destroy_edge_case(root); + } + + SECTION("top-level empty object") + { + BasicJsonType root = BasicJsonType::object(); + check_destroy_edge_case(root); + } + + SECTION("object whose last child is a non-empty array whose last child is an empty object") + { + BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()}); + BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}}); + check_destroy_edge_case(root); + } + + SECTION("destruction via erase() on a deeply nested child") + { + BasicJsonType root = BasicJsonType::array(); + root.push_back(make_single_chain(500)); + root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}})); + // erase() must destroy the removed subtree without recursing or + // allocating beyond what erase() itself needs + root.erase(0); + CAPTURE(root.size()) + CHECK(root.size() == 1); + } + + SECTION("destruction via assignment on a deep tree") + { + BasicJsonType root = make_single_chain(2000); + // assigning a new value destroys the old one in place + root = nullptr; + CHECK(root.is_null()); + } +} + DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 4b959021e..4b7a53b10 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -48,6 +56,7 @@ TEST_CASE("serialization") } } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED SECTION("operator>>") { SECTION("no given width") @@ -79,6 +88,7 @@ TEST_CASE("serialization") "[\n\t\"foo\",\n\t1,\n\t2,\n\t3,\n\tfalse,\n\t{\n\t\t\"one\": 1\n\t}\n]"); } } +#endif SECTION("dump") { diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 80ca214a4..9a45546b6 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -8,6 +8,14 @@ #include "doctest_compatibility.h" +// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command +// line *before* including json.hpp, since the library #undefs it once the header +// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the +// tests of deprecated functions are skipped if these functions are deleted +#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1) + #define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED +#endif + #include using nlohmann::json; @@ -27,10 +35,59 @@ TEST_CASE("UBJSON") { SECTION("discarded") { - // discarded values are not serialized + // a discarded value cannot be serialized to UBJSON json const j = json::value_t::discarded; - const auto result = json::to_ubjson(j); - CHECK(result.empty()); + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); + } + + SECTION("discarded values nested in a container") + { + json const discarded = json::value_t::discarded; + + SECTION("in an array") + { + json const j = {1, discarded, 2}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("as an object value") + { + json j; + j["a"] = 1; + j["b"] = discarded; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("nested deeper (array in object in array)") + { + json inner_array = {1, discarded}; + json middle_object; + middle_object["x"] = inner_array; + json const j = {middle_object}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } + + SECTION("optimized array of all-discarded elements") + { + json const j = {discarded, discarded}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif + } } SECTION("null") @@ -2091,9 +2148,14 @@ TEST_CASE("UBJSON") SECTION("discarded") { + // a discarded value cannot be serialized to UBJSON, even as part + // of an optimized array of a single (here: valueless) type json const j = {json::value_t::discarded, json::value_t::discarded}; - std::vector expected = {'[', '$', 'N', '#', 'i', 2}; - CHECK(json::to_ubjson(j, true, true) == expected); +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&); +#else + CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&); +#endif } } } @@ -2212,8 +2274,10 @@ TEST_CASE("UBJSON input that cannot be read is discarded by every overload") CHECK_THROWS_AS(_ = json::from_ubjson(input.begin(), input.end()), json::parse_error&); CHECK(json::from_ubjson(input, true, false).is_discarded()); CHECK(json::from_ubjson(input.begin(), input.end(), true, false).is_discarded()); +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED CHECK(json::from_ubjson(input.data(), input.size(), true, false).is_discarded()); CHECK(json::from_ubjson({input.data(), input.size()}, true, false).is_discarded()); +#endif } TEST_CASE("UBJSON SAX parsing stops at every event") @@ -2961,12 +3025,14 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip()) CHECK(j1 == j2); } +#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED { INFO_WITH_TEMP(filename + ": uint8_t* and size"); json j2; CHECK_NOTHROW(j2 = json::from_ubjson({packed.data(), packed.size()})); CHECK(j1 == j2); } +#endif { INFO_WITH_TEMP(filename + ": output to output adapters");