diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 1fa2f9fc6..3c94bc1e4 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -31,6 +31,51 @@ jobs: - name: Build run: cmake --build build --target ci_test_gcc + ci_meson_install: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Get latest CMake and ninja + uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 + - name: Check that Meson and pkg-config offer the CMake options + run: make check_build_options + - name: Install Meson + run: pip install meson + - name: Install with Meson + run: | + meson setup build-meson --prefix=${{ github.workspace }}/install + meson install -C build-meson + - name: Use the installed package with find_package + run: | + cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install + cmake --build build-import + - name: Install with Meson and non-default options + run: | + meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false -DDisableTupleReferenceConversion=true -DStrictBinaryUTF8=true -DDeleteDeprecatedFunctions=true + meson install -C build-meson-options + - name: Check that the options reach the installed files + run: | + test -d install-options/include/nlohmann/detail + cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json) + echo "$cflags" + echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1' + echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0' + echo "$cflags" | grep -q -- '-DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' + echo "$cflags" | grep -q -- '-DJSON_STRICT_BINARY_UTF8=1' + echo "$cflags" | grep -q -- '-DJSON_DELETE_DEPRECATED_FUNCTIONS=1' + grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + grep -q 'JSON_STRICT_BINARY_UTF8=1;JSON_DELETE_DEPRECATED_FUNCTIONS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake + cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options + cmake --build build-import-options + - name: Install with Meson and the include directory outside the prefix + run: | + meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include + meson install -C build-meson-split + cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split + cmake --build build-import-split + ci_infer: runs-on: ubuntu-latest steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index fed9e3171..19f5dca30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,7 +189,39 @@ if (MSVC) ) endif() -# Install a pkg-config file, so other tools can find this. +# Install a pkg-config file, so other tools can find this. It carries the same +# compile definitions as the target above. +set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "") +if (NOT JSON_GlobalUDLs) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0") +endif() +if (NOT JSON_ImplicitConversions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0") +endif() +if (JSON_DisableEnumSerialization) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1") +endif() +if (JSON_DisableTupleReferenceConversion) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1") +endif() +if (JSON_Diagnostics) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1") +endif() +if (JSON_Diagnostic_Positions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1") +endif() +if (JSON_LegacyDiscardedValueComparison) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1") +endif() +if (JSON_StrictNulHandling) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1") +endif() +if (JSON_StrictBinaryUTF8) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_BINARY_UTF8=1") +endif() +if (JSON_DeleteDeprecatedFunctions) + string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DELETE_DEPRECATED_FUNCTIONS=1") +endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" diff --git a/FILES.md b/FILES.md index 861fdf610..fbb57815a 100644 --- a/FILES.md +++ b/FILES.md @@ -249,9 +249,31 @@ make BUILD.bazel The "Check amalgamation" workflow fails if the file is out of date. -### `meson.build` +### `meson.build` and `meson_options.txt` -The build definition for the [Meson](https://mesonbuild.com) build system. +Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology). + +Projects wishing to use the wrap can execute: +```sh +meson wrap install nlohmann_json +``` + +Which allows Meson to build from source when a system provided dependency isn't available. + +To build directly: +```sh +meson setup builddir +ninja -C builddir +``` + +`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example, +`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`. `make check_build_options` +([`tools/check_build_options`](tools/check_build_options/README.md)) checks in CI that both files and the pkg-config files +stay in sync with the CMake options. + +When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that +`find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from +the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson. ### `Package.swift` diff --git a/Makefile b/Makefile index 6e912cb9e..2b7bb8de6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check +.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check check_build_options ########################################################################## # configuration @@ -131,6 +131,10 @@ macro_builder_check: diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \ diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1) +# check that the Meson build and the pkg-config files offer the options of the CMake target +check_build_options: + python3 tools/check_build_options/check_build_options.py . + # check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources check-amalgamation: @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ @@ -191,7 +195,7 @@ json.tar.xz: # We use `-X` to make the resulting ZIP file reproducible, see # . include.zip: BUILD.bazel - zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) $(AMALGAMATED_VIEW_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT + zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) $(AMALGAMATED_VIEW_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT # Create the files for a release and add signatures and hashes. release: include.zip json.tar.xz diff --git a/cmake/clang_sanitizer_ignorelist.txt b/cmake/clang_sanitizer_ignorelist.txt index 153b6a76d..ec54ac571 100644 --- a/cmake/clang_sanitizer_ignorelist.txt +++ b/cmake/clang_sanitizer_ignorelist.txt @@ -4,5 +4,11 @@ # 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. +# +# libstdc++'s compares two views of different lengths through `_S_compare(size_type __n1, size_type +# __n2)`, which stores `__n1 - __n2` in a signed difference_type and clamps it to int. Any comparison with a shorter +# view wraps that subtraction on purpose, and -fsanitize=integer reports it as implicit-integer-sign-change. json_view +# compares keys as string views, so the same check is excluded for as well. [implicit-integer-sign-change] src:*/include/c++/*/format +src:*/include/c++/*/string_view diff --git a/cmake/nlohmann_jsonTargets.cmake.in b/cmake/nlohmann_jsonTargets.cmake.in new file mode 100644 index 000000000..b2e57a526 --- /dev/null +++ b/cmake/nlohmann_jsonTargets.cmake.in @@ -0,0 +1,42 @@ +# Imported target for installations made with Meson (see meson.build). +# +# CMake installations generate this file with install(EXPORT ...). Meson cannot +# do that, but as the library is header-only, the target only needs an include +# directory, the C++ standard, and the compile definitions of the options that +# differ from their defaults. Paths are computed relative to this file so that +# the installation can be relocated (e.g., into a sysroot), unless includedir or +# datadir is outside the prefix. + +if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@) + return() +endif() + +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE) +# As in CMake's generated file: avoid "//include" for an installation to "/". +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED) +set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@" +) +if(CMAKE_VERSION VERSION_LESS 3.8) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_range_for + ) +else() + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_FEATURES cxx_std_11 + ) +endif() + +set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@") +if(_NLOHMANN_JSON_COMPILE_DEFINITIONS) + set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}" + ) +endif() + +unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS) +unset(_IMPORT_PREFIX) diff --git a/cmake/pkg-config.pc.in b/cmake/pkg-config.pc.in index 21a91a3cf..70407f09b 100644 --- a/cmake/pkg-config.pc.in +++ b/cmake/pkg-config.pc.in @@ -4,4 +4,4 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: JSON for Modern C++ Version: @PROJECT_VERSION@ -Cflags: -I${includedir} +Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@ 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 675a8acee..2e7781378 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -67,7 +67,7 @@ By default, implicit conversions are enabled. `JSON_USE_IMPLICIT_CONVERSIONS` is defined to `0`: ```cpp - using wjson = nlohmann::basic_json; + using wjson = nlohmann::json::with_string_t; void load(const nlohmann::json& j); diff --git a/docs/mkdocs/docs/examples/custom_array_type.cpp b/docs/mkdocs/docs/examples/custom_array_type.cpp index 63651cadf..9765c51e8 100644 --- a/docs/mkdocs/docs/examples/custom_array_type.cpp +++ b/docs/mkdocs/docs/examples/custom_array_type.cpp @@ -1,11 +1,10 @@ #include -#include #include #include "custom_array_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_array_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_binary_type.cpp b/docs/mkdocs/docs/examples/custom_binary_type.cpp index cea34ea38..0a835d475 100644 --- a/docs/mkdocs/docs/examples/custom_binary_type.cpp +++ b/docs/mkdocs/docs/examples/custom_binary_type.cpp @@ -1,16 +1,10 @@ -#include #include -#include -#include -#include #include #include "custom_binary_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_binary_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_object_type.cpp b/docs/mkdocs/docs/examples/custom_object_type.cpp index d4f99ccf0..9d4b43daf 100644 --- a/docs/mkdocs/docs/examples/custom_object_type.cpp +++ b/docs/mkdocs/docs/examples/custom_object_type.cpp @@ -1,12 +1,11 @@ #include #include -#include #include #include "custom_object_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_object_t; int main() { diff --git a/docs/mkdocs/docs/examples/custom_string_type.cpp b/docs/mkdocs/docs/examples/custom_string_type.cpp index 63b798fc6..2e71c9378 100644 --- a/docs/mkdocs/docs/examples/custom_string_type.cpp +++ b/docs/mkdocs/docs/examples/custom_string_type.cpp @@ -1,12 +1,10 @@ #include -#include -#include #include #include "custom_string_type.hpp" -using custom_json = nlohmann::basic_json; +using custom_json = nlohmann::json::with_string_t; int main() { diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index a5a04d150..b65708040 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -353,9 +353,8 @@ The number types can be changed with template parameters. A `basic_json` type that uses `#!c long double` as floating-point type. - ```cpp hl_lines="2" - using json_ld = nlohmann::basic_json; + ```cpp hl_lines="1" + using json_ld = nlohmann::json::with_float_t; ``` Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 7960bfb28..b2560f372 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -8,6 +8,10 @@ these requirements so they do not have to be discovered by trial and error. Each that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0, Folly, EASTL 3.21, `ankerl::unordered_dense`, `phmap`, `gtl`, `robin_hood`, `tsl::ordered_map`, and Qt 6. +To change a single template parameter and keep the others, use the member alias templates +[`with_*_t`](../../api/basic_json/with_t.md); for instance, `nlohmann::json::with_float_t` is `json` with +`#!cpp long double` as [`number_float_t`](../../api/basic_json/number_float_t.md). + ## How to read this page Requirements are split into two groups: @@ -143,7 +147,7 @@ struct unordered_map_object using base_t::base_t; }; -using unordered_json = nlohmann::basic_json; +using unordered_json = nlohmann::json::with_object_t; ``` Whether `#!cpp std::unordered_map` can be instantiated at all depends on the standard library: `object_t` is formed @@ -176,7 +180,7 @@ struct flat_hash_object using base_t::base_t; }; -using flat_hash_json = nlohmann::basic_json; +using flat_hash_json = nlohmann::json::with_object_t; ``` `absl::node_hash_map` keeps references to the mapped values valid across insertions; `absl::flat_hash_map` does not, diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index 17b2eb360..bfbf850e0 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -116,7 +116,7 @@ function to use instead. === "Deprecated" ```cpp - using my_json = nlohmann::basic_json; + using my_json = nlohmann::json::with_string_t; nlohmann::json_pointer ptr("/foo/bar/1"); ``` diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md index d5357b8ff..bfa994340 100644 --- a/docs/mkdocs/docs/integration/package_managers.md +++ b/docs/mkdocs/docs/integration/package_managers.md @@ -125,10 +125,24 @@ meson wrap install nlohmann_json Please see the Meson project for any issues regarding the packaging. The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in -which case a [pkg-config](pkg-config.md) file is installed. To use it, have your build system require the -`nlohmann_json` pkg-config dependency. In Meson, it is preferred to use the -[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than -using the subproject directly. +which case a [pkg-config](pkg-config.md) file and the CMake package config files are installed. To use it, have your build system require +the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson, +it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a +subproject fallback, rather than using the subproject directly. + +The options that change the library's configuration are available in Meson as well, named like the +[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`, +`ImplicitConversions`, `DisableEnumSerialization`, `DisableTupleReferenceConversion`, `Diagnostics`, +`Diagnostic_Positions`, `LegacyDiscardedValueComparison`, `StrictNulHandling`, `StrictBinaryUTF8`, and +`DeleteDeprecatedFunctions`. They have the same defaults as in CMake, except that +`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when +the library is used as a subproject: + +```shell +meson setup build -Dnlohmann_json:Diagnostics=true +``` + +The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target. ??? example "Example: Wrap" diff --git a/gcm.cache/nlohmann.json.gcm b/gcm.cache/nlohmann.json.gcm new file mode 100644 index 000000000..b5ca610d3 Binary files /dev/null and b/gcm.cache/nlohmann.json.gcm differ diff --git a/include/nlohmann/detail/bit_ops.hpp b/include/nlohmann/detail/bit_ops.hpp index 9f86ce3a5..3dc781164 100644 --- a/include/nlohmann/detail/bit_ops.hpp +++ b/include/nlohmann/detail/bit_ops.hpp @@ -9,12 +9,15 @@ #pragma once #include // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include // __umulh, _umul128 +#endif #include // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -71,6 +74,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast(a) * b; return {static_cast(r), static_cast(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u; diff --git a/include/nlohmann/detail/view/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp index 6ba3c78ee..b55b9a09f 100644 --- a/include/nlohmann/detail/view/document_data.hpp +++ b/include/nlohmann/detail/view/document_data.hpp @@ -40,10 +40,10 @@ struct document_data std::size_t tape_cap = 0; node* inline_tape = nullptr; ///< node array allocated together with this header std::size_t inline_cap = 0; - std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init) + std::string arena; ///< decoded strings that contained escapes std::size_t arena_size = 0; ///< bytes of decoded strings at base[1] (the arena, or those of a loaded image) - std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init) - std::vector owned_image{}; ///< a loaded image the document owns (the text and the decoded strings point into it) // NOLINT(readability-redundant-member-init) + std::string owned; ///< owned copy of the input, if any + std::vector owned_image; ///< a loaded image the document owns (the text and the decoded strings point into it) // hash indexes of large objects (see object_index.hpp) static constexpr std::uint32_t index_min_members = 128; @@ -52,9 +52,9 @@ struct document_data std::size_t start; ///< first slot in index_slots std::uint32_t mask; ///< slot count - 1 (a power of two minus one) }; - std::vector indexes{}; // NOLINT(readability-redundant-member-init) - std::vector index_slots{}; // NOLINT(readability-redundant-member-init) - std::vector large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init) + std::vector indexes; + std::vector index_slots; + std::vector large_objects; ///< positions of the objects to index (noted while parsing) std::array base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage) bool discarded = true; diff --git a/include/nlohmann/detail/view/edit_storage.hpp b/include/nlohmann/detail/view/edit_storage.hpp index 11bff9faa..372bb3049 100644 --- a/include/nlohmann/detail/view/edit_storage.hpp +++ b/include/nlohmann/detail/view/edit_storage.hpp @@ -177,7 +177,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) /// still covers its original subtree. inline node* find_parent(const document_data& d, const node* target) { - const std::less lt; + const std::less lt{}; const node* lo = d.tape; const node* hi = d.tape + d.tape_size; const node* c = d.tape; diff --git a/include/nlohmann/detail/view/scan.hpp b/include/nlohmann/detail/view/scan.hpp index 427a53a10..ec684f9b5 100644 --- a/include/nlohmann/detail/view/scan.hpp +++ b/include/nlohmann/detail/view/scan.hpp @@ -85,7 +85,9 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned #define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; } NLOHMANN_VIEW_STEP(0) NLOHMANN_VIEW_STEP(1) NLOHMANN_VIEW_STEP(2) NLOHMANN_VIEW_STEP(3) NLOHMANN_VIEW_STEP(4) NLOHMANN_VIEW_STEP(5) NLOHMANN_VIEW_STEP(6) NLOHMANN_VIEW_STEP(7) - if (!Value || !NLOHMANN_VIEW_VECTOR) +#if NLOHMANN_VIEW_VECTOR + if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127) +#endif { NLOHMANN_VIEW_STEP(8) NLOHMANN_VIEW_STEP(9) NLOHMANN_VIEW_STEP(10) NLOHMANN_VIEW_STEP(11) NLOHMANN_VIEW_STEP(12) NLOHMANN_VIEW_STEP(13) NLOHMANN_VIEW_STEP(14) NLOHMANN_VIEW_STEP(15) diff --git a/include/nlohmann/detail/view/simd.hpp b/include/nlohmann/detail/view/simd.hpp index f92b62466..4c99c7072 100644 --- a/include/nlohmann/detail/view/simd.hpp +++ b/include/nlohmann/detail/view/simd.hpp @@ -26,6 +26,9 @@ // used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the // portable check. JSON_VIEW_USE_SSSE3 skips the CPU check (for code compiled // for SSSE3 anyway); JSON_VIEW_NO_SIMD selects the portable code. +// NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC +// ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the +// check portable without disabling the SSE2 code. #if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 @@ -44,7 +47,7 @@ #else #define NLOHMANN_VIEW_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum) #endif -#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) +#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) // (GCC before 4.9 has no SSSE3 intrinsics without -mssse3) #include #include diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 53ec3dfd7..b354447ae 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -703,11 +703,19 @@ class basic_json_view return detail::view::view_side(v); } + /// the template argument as a runtime condition: testing it directly in + /// `Editable && ...` makes the condition constant, which MSVC reports as + /// C4127 + static bool editable() noexcept + { + return Editable; + } + /// the number of source bytes of this value (estimated for values with /// decoded strings) std::size_t source_extent() const noexcept { - if (Editable && m_doc->edits != nullptr) + if (editable() && m_doc->edits != nullptr) { // positions of moved and new values are not source offsets return m_node == m_doc->tape ? m_doc->size + m_doc->edits->text_used : 64; diff --git a/meson.build b/meson.build index 2ccb0ef6e..19f734784 100644 --- a/meson.build +++ b/meson.build @@ -2,25 +2,120 @@ project('nlohmann_json', 'cpp', version : '3.12.0', license : 'MIT', + meson_version : '>= 0.64', + default_options: ['cpp_std=c++11'], ) +if get_option('MultipleHeaders') + incdir = 'include' +else + incdir = 'single_include' +endif + +# The same compile definitions as the CMake target (see target_compile_definitions +# in CMakeLists.txt): only an option that differs from its default adds one. +json_defines = [] +if not get_option('GlobalUDLs') + json_defines += 'JSON_USE_GLOBAL_UDLS=0' +endif +if not get_option('ImplicitConversions') + json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0' +endif +if get_option('DisableEnumSerialization') + json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1' +endif +if get_option('DisableTupleReferenceConversion') + json_defines += 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1' +endif +if get_option('Diagnostics') + json_defines += 'JSON_DIAGNOSTICS=1' +endif +if get_option('Diagnostic_Positions') + json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1' +endif +if get_option('LegacyDiscardedValueComparison') + json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1' +endif +if get_option('StrictNulHandling') + json_defines += 'JSON_STRICT_NUL_HANDLING=1' +endif +if get_option('StrictBinaryUTF8') + json_defines += 'JSON_STRICT_BINARY_UTF8=1' +endif +if get_option('DeleteDeprecatedFunctions') + json_defines += 'JSON_DELETE_DEPRECATED_FUNCTIONS=1' +endif + +cpp_args = [] +foreach define : json_defines + cpp_args += '-D' + define +endforeach + nlohmann_json_dep = declare_dependency( - include_directories: include_directories('single_include') + compile_args: cpp_args, + include_directories: include_directories(incdir) ) +meson.override_dependency('nlohmann_json', nlohmann_json_dep) +# The multi-header version under the name earlier versions of this file used nlohmann_json_multiple_headers = declare_dependency( + compile_args: cpp_args, include_directories: include_directories('include') ) if not meson.is_subproject() -install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann') -install_headers('single_include/nlohmann/json_view.hpp', subdir: 'nlohmann') + install_subdir( + incdir / 'nlohmann', + install_dir: get_option('includedir'), + install_tag: 'devel', + ) -pkgc = import('pkgconfig') -pkgc.generate(name: 'nlohmann_json', - version: meson.project_version(), - description: 'JSON for Modern C++' -) + pkgc = import('pkgconfig') + pkgc.generate(name: 'nlohmann_json', + version: meson.project_version(), + description: 'JSON for Modern C++', + extra_cflags: cpp_args, + install_dir: get_option('datadir') / 'pkgconfig', + ) + + # CMake package config files, so that find_package(nlohmann_json) works. The + # include directory is given relative to the config files, so that the + # installation can be relocated. This is not possible if includedir or datadir + # is an absolute path outside the prefix (e.g., with the separate outputs of + # Nix); then the absolute include directory is used, as CMake does. + fs = import('fs') + cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name() + cmake_to_prefix = [] + if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir) + cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/') + else + foreach component : cmake_install_dir.split('/') + cmake_to_prefix += '..' + endforeach + cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir') + endif + + cmake_conf = configuration_data() + cmake_conf.set('PROJECT_NAME', meson.project_name()) + cmake_conf.set('PROJECT_VERSION', meson.project_version()) + cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0]) + cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name()) + cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets') + cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir) + cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix)) + cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines)) + + foreach cmake_file : [ + ['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'], + ['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'], + ['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'], + ] + configure_file( + input: cmake_file[0], + output: cmake_file[1], + configuration: cmake_conf, + format: 'cmake@', + install_dir: cmake_install_dir, + ) + endforeach endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..d4bfb201f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,66 @@ +option( + 'MultipleHeaders', + type: 'boolean', + value: false, + description: 'Use non-amalgamated version of the library', +) +option( + 'GlobalUDLs', + type: 'boolean', + value: true, + description: 'Place user-defined string literals in the global namespace', +) +option( + 'ImplicitConversions', + type: 'boolean', + value: true, + description: 'Enable implicit conversions', +) +option( + 'DisableEnumSerialization', + type: 'boolean', + value: false, + description: 'Disable default integer enum serialization', +) +option( + 'DisableTupleReferenceConversion', + type: 'boolean', + value: false, + description: 'Disable conversion from a one-element tuple of a JSON reference', +) +option( + 'Diagnostics', + type: 'boolean', + value: false, + description: 'Use extended diagnostic messages', +) +option( + 'Diagnostic_Positions', + type: 'boolean', + value: false, + description: 'Enable diagnostic positions', +) +option( + 'LegacyDiscardedValueComparison', + type: 'boolean', + value: false, + description: 'Enable legacy discarded value comparison', +) +option( + 'StrictNulHandling', + type: 'boolean', + value: false, + description: 'Enable strict NUL-byte handling', +) +option( + 'StrictBinaryUTF8', + type: 'boolean', + value: false, + description: 'Enable UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers', +) +option( + 'DeleteDeprecatedFunctions', + type: 'boolean', + value: false, + description: 'Delete the deprecated functions instead of only deprecating them', +) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5535a333b..70d5ac805 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8863,13 +8863,16 @@ NLOHMANN_JSON_NAMESPACE_END #include // uint64_t +#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + #include // __umulh, _umul128 +#endif // #include // Portable bit-level helpers for the number and string scanners. They use -// compiler builtins where available and plain C++ otherwise, so they need no -// platform headers and work regardless of byte order. +// compiler builtins or platform-specific intrinsics where available and plain +// C++ otherwise, so they work regardless of byte order. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -8926,6 +8929,12 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc __extension__ using uint128 = unsigned __int128; const uint128 r = static_cast(a) * b; return {static_cast(r), static_cast(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; #else const std::uint64_t a_lo = a & 0xFFFFFFFFu; const std::uint64_t a_hi = a >> 32u; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index b4dd1cd32..994c490c0 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -25,7 +25,7 @@ #define INCLUDE_NLOHMANN_JSON_VIEW_HPP_ #include // size_t -#include // uint8_t, uint32_t +#include // uint32_t #include // memcpy, strlen #include // distance, input_iterator_tag, iterator_traits #include // map @@ -82,7 +82,7 @@ #include // array #include // size_t -#include // uint8_t, uint32_t +#include // uint32_t #include // memcpy #include // less #include // map @@ -305,10 +305,8 @@ struct document_data std::size_t tape_cap = 0; node* inline_tape = nullptr; ///< node array allocated together with this header std::size_t inline_cap = 0; - std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init) - std::size_t arena_size = 0; ///< bytes of decoded strings at base[1] (the arena, or those of a loaded image) - std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init) - std::vector owned_image{}; ///< a loaded image the document owns (the text and the decoded strings point into it) // NOLINT(readability-redundant-member-init) + std::string arena; ///< decoded strings that contained escapes + std::string owned; ///< owned copy of the input, if any // hash indexes of large objects (see object_index.hpp) static constexpr std::uint32_t index_min_members = 128; @@ -317,9 +315,9 @@ struct document_data std::size_t start; ///< first slot in index_slots std::uint32_t mask; ///< slot count - 1 (a power of two minus one) }; - std::vector indexes{}; // NOLINT(readability-redundant-member-init) - std::vector index_slots{}; // NOLINT(readability-redundant-member-init) - std::vector large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init) + std::vector indexes; + std::vector index_slots; + std::vector large_objects; ///< positions of the objects to index (noted while parsing) std::array base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage) bool discarded = true; @@ -559,6 +557,9 @@ NLOHMANN_JSON_NAMESPACE_END // used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the // portable check. JSON_VIEW_USE_SSSE3 skips the CPU check (for code compiled // for SSSE3 anyway); JSON_VIEW_NO_SIMD selects the portable code. +// NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC +// ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the +// check portable without disabling the SSE2 code. #if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 @@ -577,7 +578,7 @@ NLOHMANN_JSON_NAMESPACE_END #else #define NLOHMANN_VIEW_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum) #endif -#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) +#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)))) // (GCC before 4.9 has no SSSE3 intrinsics without -mssse3) #include #include @@ -935,7 +936,9 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned #define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; } NLOHMANN_VIEW_STEP(0) NLOHMANN_VIEW_STEP(1) NLOHMANN_VIEW_STEP(2) NLOHMANN_VIEW_STEP(3) NLOHMANN_VIEW_STEP(4) NLOHMANN_VIEW_STEP(5) NLOHMANN_VIEW_STEP(6) NLOHMANN_VIEW_STEP(7) - if (!Value || !NLOHMANN_VIEW_VECTOR) +#if NLOHMANN_VIEW_VECTOR + if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127) +#endif { NLOHMANN_VIEW_STEP(8) NLOHMANN_VIEW_STEP(9) NLOHMANN_VIEW_STEP(10) NLOHMANN_VIEW_STEP(11) NLOHMANN_VIEW_STEP(12) NLOHMANN_VIEW_STEP(13) NLOHMANN_VIEW_STEP(14) NLOHMANN_VIEW_STEP(15) @@ -2739,7 +2742,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) /// still covers its original subtree. inline node* find_parent(const document_data& d, const node* target) { - const std::less lt; + const std::less lt{}; const node* lo = d.tape; const node* hi = d.tape + d.tape_size; const node* c = d.tape; @@ -3847,840 +3850,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // int64_t, uint8_t, uint16_t, uint32_t, uint64_t -#include // memcmp, memcpy -#include // numeric_limits -#include // string -#include // vector - -// #include -// #include - -// #include - -// #include - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // size_t -#include // int64_t, uint64_t -#include // numeric_limits -#include // string -#include // integral_constant - -// #include -// #include - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -/*! -@brief locate the decimal point and the end of the mantissa of a float token - -Also checks that the token is a JSON number. Tokens of the parser and of edits -always are; an image loaded with image_check::bounds can hold any bytes, which -must not reach the conversion (it expects a well-formed token). -*/ -inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept -{ - const auto digit = [last](const char* q) - { - return q != last && is_digit(static_cast(*q)); - }; - const char* p = first; - p += (p != last && *p == '-') ? 1 : 0; - if (!digit(p) || (*p == '0' && digit(p + 1))) - { - return false; - } - while (digit(p)) - { - ++p; - } - dot = std::string::npos; - if (p != last && *p == '.') - { - dot = static_cast(p - first); - if (!digit(++p)) - { - return false; - } - while (digit(p)) - { - ++p; - } - } - mantissa_end = static_cast(p - first); - if (p != last && (*p == 'e' || *p == 'E')) - { - ++p; - p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0; - if (!digit(p)) - { - return false; - } - while (digit(p)) - { - ++p; - } - } - return p == last; -} - -/*! -@brief the value of the float token of a node, as parse() converts it - -Uses the lexer's conversion (detail::convert_float), so that the values are -bit-identical to parse(): float and double are converted without allocation -and independent of the locale. A token that is not a JSON number (only in a -damaged image loaded with image_check::bounds) yields 0. -*/ -template -NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) -{ - const char* const last = first + n.len; - std::size_t dot = 0; - std::size_t mantissa_end = 0; - if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end))) - { - return FloatType{}; - } - return convert_float(first, last, dot, mantissa_end); -} - -/*! -@brief the digits of a float token with at most 19 digits, from its layout - -The digit layout recorded while parsing says where the integer digits, the -fraction digits, and the exponent are, so the digits are read eight at a -time without scanning. - -@param[in] p first character of the token -@param[in] e end of the token -@param[in] limit end of the readable memory (the source text) -*/ -NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept -{ - const bool negative = *p == '-'; - p += negative ? 1 : 0; - std::uint64_t w = parse_upto19(p, int_digits, limit); - p += int_digits; - std::int64_t q = 0; - if (frac_digits != 0) - { - w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); - p += 1 + frac_digits; - q = -static_cast(frac_digits); - } - if (p != e) - { - // [eE][+-]digits; huge exponents saturate (the parser rejected - // overflow). The token is not read beyond e, and the digits are taken - // as unsigned, so that a token that is not well-formed (a damaged - // image loaded with image_check::bounds) yields a wrong value, but no - // overflow. - ++p; - const bool exp_negative = p != e && *p == '-'; - p += (p != e && (*p == '-' || *p == '+')) ? 1 : 0; - std::int64_t exp_value = 0; - for (; p != e; ++p) - { - if (exp_value < 0x10000000) - { - exp_value = (exp_value * 10) + static_cast(*p - '0'); - } - } - q += exp_negative ? -exp_value : exp_value; - } - - float_significand d; - d.w = w; - d.exponent = q; - d.negative = negative; - return d; -} - -/*! -@brief the value of a float token with at most 19 digits, from its layout - -The result is correctly rounded by the lexer's conversion -(detail::decimal_to_float(): Clinger's fast path where both operands are -exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 -digits), so it is the value parse() produces. -*/ -template -NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept -{ - return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); -} - -/// the value of a float set by an edit: its token (the shortest round-trip -/// text, or "nan", "inf", "-inf") in the edit arena -template -NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) -{ - if (token[0] == 'n') - { - return std::numeric_limits::quiet_NaN(); - } - if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i')) - { - return token[0] == 'i' ? std::numeric_limits::infinity() : -std::numeric_limits::infinity(); - } - return float_value(token, n); -} - -/// the value of the float token of a node, as parse() converts it; floats and -/// doubles with at most 19 digits are converted from the digit layout -template -FloatType float_value(const document_data& d, const node& n) -{ - if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited)) - { - return edited_float(d.str(n), n); - } - return float_value(d, n, std::integral_constant::value> {}); -} - -template -FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) -{ - const unsigned int_digits = n.extra & 0xFFu; - const unsigned frac_digits = n.extra >> 8u; - if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") - { - // (a float token not written by an edit is in the text) - const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - return layout_float(first, first + n.len, int_digits, frac_digits, reinterpret_cast(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - return float_value(d.str(n), n); -} - -template -FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/) -{ - return float_value(d.str(n), n); -} - -} // namespace view -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include - - -// Images: a document stored so that loading it needs no parsing. -// -// Layout (little-endian): a 64-byte header, the nodes, the text (the source, -// followed by the number tokens written by edits), a NUL, the decoded strings -// (followed by the strings written by edits), a NUL. The idea is that of -// zero-copy formats such as FlatBuffers (https://github.com/google/flatbuffers) -// and YaFF (https://github.com/yandex/yaff); no code is taken from them. -// check_image follows the idea of FlatBuffers' Verifier (bounds and -// structure) and also checks what the parser guarantees about strings and -// numbers, so that reading and serializing a checked image is safe and yields -// valid JSON. - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -/// how load() checks an image -enum class image_check -{ - /// everything the parser guarantees: structure and bounds, strings (valid - /// UTF-8; source strings without quotes, backslashes, and control - /// characters), and numbers (well-formed, matching the stored values) - full, - /// structure and bounds only: reading and serializing are safe, but a - /// crafted image can yield invalid UTF-8, strings that serialize to - /// invalid JSON, or numbers that differ from their text - bounds, - /// none: for images from a trusted source only (a damaged image is - /// undefined behavior) - none, -}; - -struct image_header -{ - std::array magic; ///< "NJVI" - std::uint32_t version; ///< 1 - std::uint64_t node_count; - std::uint64_t text_size; - std::uint64_t arena_size; - std::array reserved; ///< zero (for later versions) -}; -static_assert(sizeof(image_header) == 64, "the image header must be 64 bytes"); - -constexpr std::uint32_t image_version = 1; - -/// the largest node count and text or string size of an image (as for parsed -/// documents, offsets and counts must fit 32 bits) -constexpr std::uint64_t image_limit = 0xFFFFFFF0u; - -/// Copy the current structure of an edited document into nodes in document -/// order, as the parser would have written them. Text written by edits is -/// appended to text_tail (number tokens) and arena_tail (strings); floats that -/// are not finite become null, as dump() writes them. -inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector& out, std::string& text_tail, std::string& arena_tail) -{ - struct frame - { - const node* cur; - const node* end; - std::size_t index; ///< the container's node in out - std::uint32_t count; - bool object; - }; - std::vector stack; - const auto string_node = [&](const node & s) - { - node r = s; - r.extra = 0; - r.flags = static_cast(s.flags & node_flags::storage); - if (r.flags == node_flags::edited) - { - r.off = static_cast(arena_size + arena_tail.size()); - arena_tail.append(d.str(s), s.len); - r.flags = node_flags::escaped; - } - return r; - }; - const auto emit = [&](const node * v) - { - node r = *v; - switch (static_cast(v->kind)) - { - case value_t::object: - case value_t::array: - r.flags = 0; - r.extra = 0; - r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; - r.len = 0; // counted below - r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); - break; - case value_t::string: - r = string_node(*v); - break; - case value_t::number_integer: - case value_t::number_unsigned: - if ((v->flags & node_flags::storage) == node_flags::edited) - { - r.off = static_cast(d.size + text_tail.size()); - text_tail.append(d.str(*v), number_length(*v)); - } - r.flags = 0; - break; - case value_t::number_float: - if ((v->flags & node_flags::storage) == node_flags::edited) - { - const char* const t = d.str(*v); - if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) - { - r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); - break; - } - r.off = static_cast(d.size + text_tail.size()); - text_tail.append(t, v->len); - r.extra = 0xFFFFu; // the digit layout is not recorded - } - r.flags = 0; - break; - case value_t::boolean: - r.flags = static_cast(v->flags & node_flags::is_true); - break; - case value_t::null: - case value_t::binary: - case value_t::discarded: - default: - r.flags = 0; - break; - } - out.push_back(r); - }; - emit(d.tape); - while (!stack.empty()) - { - frame& top = stack.back(); - if (top.cur == top.end) - { - node& c = out[top.index]; - c.len = top.count; - c.next = static_cast(out.size() - top.index); - stack.pop_back(); - continue; - } - ++top.count; - const node* v = nullptr; - if (top.object) - { - out.push_back(string_node(*top.cur)); - v = document_data::deref(top.cur + 1); - top.cur = document_data::after(top.cur + 1); - } - else - { - v = document_data::deref(top.cur); - top.cur = document_data::after(top.cur); - } - emit(v); // may grow the stack (top is not used afterwards) - } -} - -/// the document as an image -inline std::vector save_image(const document_data& d) -{ -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif - const std::size_t arena_size = d.arena_size; - const node* nodes = d.tape; - std::size_t count = d.tape_size; - std::vector compacted; - std::string text_tail; - std::string arena_tail; - if (d.edits) - { - compact_nodes(d, arena_size, compacted, text_tail, arena_tail); - nodes = compacted.data(); - count = compacted.size(); - } - const std::size_t text_size = d.size + text_tail.size(); - const std::size_t total_arena = arena_size + arena_tail.size(); - if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) - { - // LCOV_EXCL_START (4 GiB) - throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document"); - // LCOV_EXCL_STOP - } - image_header h{}; - h.magic = {{'N', 'J', 'V', 'I'}}; - h.version = image_version; - h.node_count = count; - h.text_size = text_size; - h.arena_size = total_arena; - std::vector image(sizeof(h) + (count * sizeof(node)) + text_size + 1 + total_arena + 1); - std::uint8_t* o = image.data(); - std::memcpy(o, &h, sizeof(h)); - o += sizeof(h); - std::memcpy(o, nodes, count * sizeof(node)); - // the hash indexes are rebuilt by load() - for (std::size_t i = 0; i < count; ++i) - { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) - { - node n = nodes[i]; - n.extra = 0; - std::memcpy(o + (i * sizeof(node)), &n, sizeof(node)); - } - } - o += count * sizeof(node); - const auto append = [&o](const char* s, std::size_t n) - { - if (n != 0) - { - std::memcpy(o, s, n); - o += n; - } - }; - append(d.src, d.size); - append(text_tail.data(), text_tail.size()); - *o++ = 0; - append(d.base[1], arena_size); - append(arena_tail.data(), arena_tail.size()); - *o = 0; - return image; -} - -/// whether a number node matches its token the way the parser records it -/// (after the bounds check) -inline bool check_number(const node& n, const unsigned char* text) -{ - const std::size_t len = number_length(n); - const unsigned char* const s = text + n.off; - const unsigned char* const e = s + len; - const unsigned char* p = s; - const bool negative = *p == '-'; - p += negative ? 1 : 0; - const unsigned char* const int_start = p; - if (p == e) - { - return false; - } - if (*p == '0') - { - ++p; - } - else if (*p >= '1' && *p <= '9') - { - while (p != e && is_digit(*p)) - { - ++p; - } - } - else - { - return false; - } - const auto int_digits = static_cast(p - int_start); - std::size_t frac_digits = 0; - bool is_float = false; - if (p != e && *p == '.') - { - const unsigned char* const f0 = ++p; - while (p != e && is_digit(*p)) - { - ++p; - } - if (p == f0) - { - return false; - } - frac_digits = static_cast(p - f0); - is_float = true; - } - std::int64_t exponent = 0; - if (p != e && (*p | 0x20u) == 'e') - { - ++p; - const bool exp_negative = p != e && *p == '-'; - p += (p != e && (*p == '+' || *p == '-')) ? 1 : 0; - if (p == e || !is_digit(*p)) - { - return false; - } - while (p != e && is_digit(*p)) - { - exponent = exponent < 100000 ? (exponent * 10) + (*p - '0') : exponent; - ++p; - } - exponent = exp_negative ? -exponent : exponent; - is_float = true; - } - if (p != e) - { - return false; - } - if (n.kind == static_cast(value_t::number_float)) - { - // the digit layout the parser records (or "many", as compaction - // writes it), and a finite value - const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8u)); - if (n.extra != layout && n.extra != 0xFFFFu) - { - return false; - } - // parse() rejects floats that overflow; as there, only a number whose - // magnitude could reach 1e308 needs the conversion - if (static_cast(int_digits) + exponent > 300) - { - const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); - } - return true; - } - // integers: the token's value is the stored one; number_integer nodes of - // edits can be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); - if (is_float || int_digits > 20 || (negative && !integer)) - { - return false; - } - // (at most 19 digits cannot overflow; 20 digits are compared with 2^64 - 1) - if (int_digits == 20 && std::memcmp(int_start, "18446744073709551615", 20) > 0) - { - return false; - } - std::uint64_t m = 0; - for (const unsigned char* d = int_start; d != int_start + int_digits; ++d) - { - m = (m * 10) + static_cast(*d - '0'); - } - if (integer && m > (negative ? std::uint64_t{1} << 63u : (std::uint64_t{1} << 63u) - 1)) - { - return false; - } - return integer_bits(n) == (negative ? 0 - m : m); -} - -/// Check the nodes of a loaded image against its text and decoded strings: -/// kinds, flags, and `extra`; extents and element counts of arrays and -/// objects; keys; bounds; string contents (source strings as the parser -/// leaves them: no quotes, backslashes, or control characters; all strings -/// valid UTF-8); and number tokens. -inline bool check_image(const node* nodes, std::size_t count, const unsigned char* text, std::size_t text_size, - const unsigned char* arena, std::size_t arena_size, bool full) -{ - struct frame - { - std::size_t end; - std::uint32_t len; - std::uint32_t seen; - bool object; - bool expect_key; - }; - std::vector stack; - const auto check_string = [&](const node & n) -> bool - { - if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0) - { - return false; - } - const bool decoded = (n.flags & node_flags::escaped) != 0; - const unsigned char* const base = decoded ? arena : text; - const std::size_t limit = decoded ? arena_size : text_size; - if (n.off > limit || n.len > limit - n.off) - { - return false; - } - if (!full) - { - return true; - } - const unsigned char* const b = base + n.off; - return decoded ? valid_utf8_prefix(b, n.len) == n.len : scan_string_run(b, b + n.len) == b + n.len; - }; - // bounds of a number token; the recorded digit layout must lie within it - const auto number_in_bounds = [&](const node & n) -> bool - { - const std::size_t len = number_length(n); - if (len == 0 || n.off > text_size || len > text_size - n.off) - { - return false; - } - if (n.kind != static_cast(value_t::number_float)) - { - return (n.extra >> 8u) == 0; - } - // float_value() reads the sign, the integer digits, and the point and - // fraction digits the layout records (a layout of more than 19 digits - // means the general conversion, which stays within the token) - const std::size_t int_digits = n.extra & 0xFFu; - const std::size_t frac_digits = n.extra >> 8u; - const std::size_t need = (text[n.off] == '-' ? 1u : 0u) + int_digits + (frac_digits != 0 ? frac_digits + 1 : 0); - return int_digits + frac_digits > 19 || need <= len; - }; - std::size_t i = 0; - for (;;) - { - // close finished arrays and objects - while (!stack.empty() && i == stack.back().end) - { - const frame f = stack.back(); - if (f.seen != f.len || (f.object && !f.expect_key)) - { - return false; - } - stack.pop_back(); - if (!stack.empty()) - { - ++stack.back().seen; - stack.back().expect_key = true; - } - } - if (i == count) - { - return stack.empty(); - } - if (i != 0 && stack.empty()) - { - return false; // nodes after the root - } - const node& n = nodes[i]; - if (!stack.empty() && stack.back().object && stack.back().expect_key) - { - if (n.kind != static_cast(value_t::string) || !check_string(n)) - { - return false; - } - stack.back().expect_key = false; - ++i; - continue; - } - bool complete = true; - switch (static_cast(n.kind)) - { - case value_t::null: - // (the offset of a literal is read to size the output of dump()) - if (n.flags != 0 || n.extra != 0 || n.off > text_size) - { - return false; - } - break; - case value_t::boolean: - if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size) - { - return false; - } - break; - case value_t::string: - if (!check_string(n)) - { - return false; - } - break; - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::number_float: - if (n.flags != 0 || !number_in_bounds(n) || (full && !check_number(n, text))) - { - return false; - } - break; - case value_t::array: - case value_t::object: - { - const std::size_t limit = stack.empty() ? count : stack.back().end; - if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size) - { - return false; - } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); - complete = false; - break; - } - case value_t::binary: - case value_t::discarded: - default: - return false; - } - ++i; - if (complete && !stack.empty()) - { - ++stack.back().seen; - stack.back().expect_key = true; - } - } -} - -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what) -{ - throw_parse_error(116, concat("invalid json_document image: ", what)); -} - -/// Read an image into d. The text and the decoded strings stay in the image; -/// the nodes are copied (so that they are aligned, and edits can change them). -inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) -{ -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif - if (image == nullptr || size < sizeof(image_header)) - { - throw_invalid_image("too short"); - } - image_header h{}; - std::memcpy(&h, image, sizeof(h)); - // (the reserved fields are for later versions) - if (std::memcmp(h.magic.data(), "NJVI", 4) != 0 || h.version != image_version - || (h.reserved[0] | h.reserved[1] | h.reserved[2] | h.reserved[3]) != 0) - { - throw_invalid_image("unknown format"); - } - const std::size_t room = size - sizeof(h); - if (h.node_count == 0 || h.node_count > room / sizeof(node) || h.node_count >= image_limit || h.text_size >= image_limit || h.arena_size >= image_limit) - { - throw_invalid_image("sizes out of range"); - } - const auto count = static_cast(h.node_count); - const auto text_size = static_cast(h.text_size); - const auto arena_size = static_cast(h.arena_size); - const std::size_t text_at = sizeof(h) + (count * sizeof(node)); - // the text, a NUL, the decoded strings, a NUL, and nothing after them - if (size - text_at < 2 || text_size > size - text_at - 2 || arena_size != size - text_at - text_size - 2 - || image[text_at + text_size] != 0 || image[size - 1] != 0) - { - throw_invalid_image("sizes out of range"); - } - - d.discarded = true; - d.edits.reset(); - d.base[2] = nullptr; - d.owned.clear(); - if (d.owned_image.empty() || image != d.owned_image.data()) - { - d.owned_image.clear(); - } - d.arena.clear(); - d.indexes.clear(); - d.index_slots.clear(); - d.large_objects.clear(); - d.tape_size = 0; - d.reserve(count); - std::memcpy(d.tape, image + sizeof(h), count * sizeof(node)); - d.tape_size = count; - const std::uint8_t* const text = image + text_at; - const std::uint8_t* const arena = text + text_size + 1; - d.src = reinterpret_cast(text); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - d.size = text_size; - d.base[0] = d.src; - d.base[1] = reinterpret_cast(arena); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - d.arena_size = arena_size; - if (check != image_check::none && !check_image(d.tape, count, text, text_size, arena, arena_size, check == image_check::full)) - { - throw_invalid_image("the check failed"); - } - // the hash indexes of large objects, as after parsing - for (std::size_t i = 0; i < count; ++i) - { - node& n = d.tape[i]; - if (n.kind == static_cast(value_t::object)) - { - n.extra = 0; - if (n.len >= document_data::index_min_members) - { - d.large_objects.push_back(static_cast(i)); - } - } - } - build_object_indexes(d); - d.discarded = false; -} - -} // namespace view -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -5068,6 +4237,192 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // int64_t, uint64_t +#include // numeric_limits +#include // string +#include // integral_constant + +// #include +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/*! +@brief the value of the float token of a node, as parse() converts it + +Uses the lexer's conversion (detail::convert_float), so that the values are +bit-identical to parse(): float and double are converted without allocation +and independent of the locale. The digit layout recorded while parsing locates +the decimal point and the exponent without scanning the token. +*/ +template +NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) +{ + const char* const last = first + n.len; + const std::size_t neg = first[0] == '-' ? 1 : 0; + const std::size_t int_digits = n.extra & 0xFFu; + const std::size_t frac_digits = n.extra >> 8u; + std::size_t dot = std::string::npos; + std::size_t mantissa_end = n.len; + if (int_digits != 255 && frac_digits != 255) + { + dot = frac_digits != 0 ? neg + int_digits : std::string::npos; + mantissa_end = neg + int_digits + (frac_digits != 0 ? 1 + frac_digits : 0); + } + else + { + // more digits than the layout records: locate them + for (std::size_t i = 0; i < n.len; ++i) + { + if (first[i] == '.') + { + dot = i; + } + else if (first[i] == 'e' || first[i] == 'E') + { + mantissa_end = i; + break; + } + } + } + return convert_float(first, last, dot, mantissa_end); +} + +/*! +@brief the digits of a float token with at most 19 digits, from its layout + +The digit layout recorded while parsing says where the integer digits, the +fraction digits, and the exponent are, so the digits are read eight at a +time without scanning. + +@param[in] p first character of the token +@param[in] e end of the token +@param[in] limit end of the readable memory (the source text) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept +{ + const bool negative = *p == '-'; + p += negative ? 1 : 0; + std::uint64_t w = parse_upto19(p, int_digits, limit); + p += int_digits; + std::int64_t q = 0; + if (frac_digits != 0) + { + w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); + p += 1 + frac_digits; + q = -static_cast(frac_digits); + } + if (p != e) + { + // [eE][+-]digits; huge exponents saturate (the parser rejected overflow) + ++p; + const bool exp_negative = *p == '-'; + p += (*p == '-' || *p == '+') ? 1 : 0; + std::int64_t exp_value = 0; + for (; p != e; ++p) + { + if (exp_value < 0x10000000) + { + exp_value = (exp_value * 10) + (*p - '0'); + } + } + q += exp_negative ? -exp_value : exp_value; + } + + float_significand d; + d.w = w; + d.exponent = q; + d.negative = negative; + return d; +} + +/*! +@brief the value of a float token with at most 19 digits, from its layout + +The result is correctly rounded by the lexer's conversion +(detail::decimal_to_float(): Clinger's fast path where both operands are +exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 +digits), so it is the value parse() produces. +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept +{ + return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); +} + +/// the value of a float set by an edit: its token (the shortest round-trip +/// text, or "nan", "inf", "-inf") in the edit arena +template +NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) +{ + if (token[0] == 'n') + { + return std::numeric_limits::quiet_NaN(); + } + if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i')) + { + return token[0] == 'i' ? std::numeric_limits::infinity() : -std::numeric_limits::infinity(); + } + return float_value(token, n); +} + +/// the value of the float token of a node, as parse() converts it; floats and +/// doubles with at most 19 digits are converted from the digit layout +template +FloatType float_value(const document_data& d, const node& n) +{ + if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited)) + { + return edited_float(d.str(n), n); + } + return float_value(d, n, std::integral_constant::value> {}); +} + +template +FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) +{ + const unsigned int_digits = n.extra & 0xFFu; + const unsigned frac_digits = n.extra >> 8u; + if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") + { + // (a float token not written by an edit is in the text) + const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + return layout_float(first, first + n.len, int_digits, frac_digits, reinterpret_cast(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + return float_value(d.str(n), n); +} + +template +FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/) +{ + return float_value(d.str(n), n); +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN @@ -5693,9 +5048,7 @@ class view_serializer m_out.put('"'); } - /// as serializer::dump_escaped(); strings of a document are valid UTF-8, - /// except in a damaged image loaded with image_check::bounds, for which - /// this throws what basic_json::dump() throws for the string + /// as serializer::dump_escaped() for valid UTF-8 (the view has no other) template void write_escaped(const unsigned char* s, std::size_t n) { @@ -5719,13 +5072,12 @@ class view_serializer } std::uint32_t codepoint = s[i]; std::size_t len = 1; - if (codepoint >= 0x80) + if (codepoint >= 0xC0) { - len = validate_one_utf8(s + i, n - i); - if (NLOHMANN_VIEW_UNLIKELY(len == 0)) + len = 2; + if (codepoint >= 0xE0) { - invalid_utf8(s, n); - return; + len = codepoint >= 0xF0 ? 4 : 3; } codepoint &= 0xFFu >> (len + 1); for (std::size_t k = 1; k < len; ++k) @@ -5738,13 +5090,6 @@ class view_serializer } } - /// throw what basic_json::dump() throws for a string that is not valid UTF-8 - NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n) - { - const string_t dumped = BasicJsonType(string_t(reinterpret_cast(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - static_cast(dumped); - } - template void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len) { @@ -6675,11 +6020,19 @@ class basic_json_view return detail::view::view_side(v); } + /// the template argument as a runtime condition: testing it directly in + /// `Editable && ...` makes the condition constant, which MSVC reports as + /// C4127 + static bool editable() noexcept + { + return Editable; + } + /// the number of source bytes of this value (estimated for values with /// decoded strings) std::size_t source_extent() const noexcept { - if (Editable && m_doc->edits != nullptr) + if (editable() && m_doc->edits != nullptr) { // positions of moved and new values are not source offsets return m_node == m_doc->tape ? m_doc->size + m_doc->edits->text_used : 64; @@ -6907,7 +6260,7 @@ class basic_json_document /// whether the document holds its own copy of the text bool owns_source() const noexcept { - return m_data && ((!m_data->owned.empty() && m_data->src == m_data->owned.data()) || !m_data->owned_image.empty()); + return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data(); } /// number of index nodes (values plus object keys) @@ -6916,7 +6269,7 @@ class basic_json_document return m_data ? m_data->tape_size : 0; } - /// bytes held by the document (index, decoded strings, owned text or image) + /// bytes held by the document (index, decoded strings, owned text) std::size_t memory_usage() const noexcept { if (!m_data) @@ -6925,7 +6278,7 @@ class basic_json_document } return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node)) + (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0) - + m_data->arena.capacity() + m_data->owned.capacity() + m_data->owned_image.capacity() + + m_data->arena.capacity() + m_data->owned.capacity() + (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t)) + (m_data->large_objects.capacity() * sizeof(std::uint32_t)) + (m_data->edits != nullptr ? m_data->edits->bytes : 0); @@ -6945,10 +6298,8 @@ class basic_json_document // allocate everything first, so that an exception leaves the document // unchanged - // (the decoded strings of a loaded image stay in the image) - const bool arena_in_use = d.base[1] == d.arena.data(); const bool shrink_arena = d.arena.capacity() > d.arena.size(); - std::string arena(shrink_arena && arena_in_use ? d.arena : std::string()); + std::string arena(shrink_arena ? d.arena : std::string()); // (edits link to the nodes of the index, which then stays in place) const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap && d.edits == nullptr; const bool into_header = d.tape_size <= d.inline_cap; @@ -6964,62 +6315,10 @@ class basic_json_document if (shrink_arena) { d.arena.swap(arena); - if (arena_in_use) - { - d.base[1] = d.arena.data(); - } + d.base[1] = d.arena.data(); } } - //////////// - // images // - //////////// - - /// how load() checks an image (full, bounds, or none) - using image_check = detail::view::image_check; - - /// The document as an image that load() reads without parsing: the node - /// index, the text, and the decoded strings. An edited document is - /// written in its current state (floats that are not finite become null, - /// as in dump()). - std::vector save() const - { - if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) - { - detail::view::throw_type_error(320, "cannot save a discarded json_document"); - } - return detail::view::save_image(*m_data); - } - - /// Read an image written by save(). The image is borrowed: it must stay - /// alive and unchanged while the document is used. - NLOHMANN_VIEW_NODISCARD - static basic_json_document load(const std::uint8_t* image, std::size_t size, const image_check check = image_check::full) - { - basic_json_document d; - d.ensure_data(nullptr, 0); - detail::view::load_image(*d.m_data, image, size, check); - return d; - } - - /// read an image (borrowed) - NLOHMANN_VIEW_NODISCARD - static basic_json_document load(const std::vector& image, const image_check check = image_check::full) - { - return load(image.data(), image.size(), check); - } - - /// read an image and keep it (no copy) - NLOHMANN_VIEW_NODISCARD - static basic_json_document load(std::vector&& image, const image_check check = image_check::full) - { - basic_json_document d; - d.ensure_data(nullptr, 0); - d.m_data->owned_image = std::move(image); - detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); - return d; - } - /////////// // edits // /////////// @@ -7203,7 +6502,6 @@ class basic_json_document { d.owned.clear(); } - d.owned_image.clear(); d.src = src; d.size = size; d.tape_size = 0; @@ -7228,7 +6526,6 @@ class basic_json_document { d.base[0] = d.src; d.base[1] = d.arena.data(); - d.arena_size = d.arena.size(); detail::view::build_object_indexes(d); d.discarded = false; return; diff --git a/src/modules/json.cppm b/src/modules/json.cppm index 940c44ab0..ffc9e00e2 100644 --- a/src/modules/json.cppm +++ b/src/modules/json.cppm @@ -17,6 +17,12 @@ module; // 3. Or upgrade to a newer GCC version with better modules support. // See: https://github.com/nlohmann/json/issues/5103 +// GCC ignores the target attribute in modules ("'target' attribute currently +// unsupported in modules") and then rejects the SSSE3 intrinsics of json_view. +#if defined(__GNUC__) && !defined(__clang__) + #define NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE +#endif + #include #include #include diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index 07eb21c4e..4062870d5 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -370,14 +370,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") #if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) SECTION("std::map-backed object_t") { - using bad_alloc_json = nlohmann::basic_json; + using bad_alloc_json = nlohmann::json::with_allocator_t; check_deep_copy_survives_failing_allocation(false); check_deep_copy_survives_failing_allocation(true); @@ -385,14 +378,7 @@ TEST_CASE("copy of a deeply nested value survives a failing allocation (#5640)") SECTION("ordered_map-backed object_t") { - using bad_alloc_ordered_json = nlohmann::basic_json; + using bad_alloc_ordered_json = nlohmann::ordered_json::with_allocator_t; check_deep_copy_survives_failing_allocation(false); check_deep_copy_survives_failing_allocation(true); @@ -450,14 +436,7 @@ struct scratch_counting_allocator : std::allocator TEST_CASE("deep copy uses the provided allocator") { - using counting_json = nlohmann::basic_json; + using counting_json = nlohmann::json::with_allocator_t; // deeper than the 128 levels the copy constructor descends into, so the // innermost objects are copied by the iterative deep copy @@ -516,14 +495,7 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl // 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; + using countdown_json = nlohmann::json::with_allocator_t; // deeper than the 128 levels the converting constructor descends into, so // that failures land on both sides of the bound - or, built with @@ -631,14 +603,7 @@ TEST_CASE("destructor performs no allocation, only deallocation") // 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; + using counting_json = nlohmann::json::with_allocator_t; SECTION("array") { @@ -683,14 +648,7 @@ TEST_CASE("destructor performs no allocation, only deallocation") TEST_CASE("a failed allocation leaves the value unchanged") { // create JSON type using the throwing allocator - using my_json = nlohmann::basic_json; + using my_json = nlohmann::json::with_allocator_t; // Each of these creates a string, array, object, or binary value. The // value must be created before the type is changed: otherwise, a failed diff --git a/tests/src/unit-binary_formats.cpp b/tests/src/unit-binary_formats.cpp index bd10d5400..333252190 100644 --- a/tests/src/unit-binary_formats.cpp +++ b/tests/src/unit-binary_formats.cpp @@ -237,7 +237,7 @@ namespace // the binary formats as function pointers for "Binary formats with narrow number types"; // named functions rather than lambdas, because clang 3.5 cannot convert a lambda // to a function pointer in the braced initializer of the format table -using narrow_json = nlohmann::basic_json; +using narrow_json = nlohmann::json::with_integers_t::with_float_t; using bytes = std::vector; bytes encode_cbor(const json& j) diff --git a/tests/src/unit-class_lexer.cpp b/tests/src/unit-class_lexer.cpp index 3a842080c..82690a669 100644 --- a/tests/src/unit-class_lexer.cpp +++ b/tests/src/unit-class_lexer.cpp @@ -17,6 +17,7 @@ using nlohmann::json; #include // snprintf #include // strtod #include // memcpy +#include // numeric_limits #include // map #include // mt19937 #include // stringstream @@ -1136,8 +1137,39 @@ TEST_CASE("Eisel-Lemire float conversion") SECTION("128-bit products and leading zeros") { + const auto check_product = [](std::uint64_t a, std::uint64_t b) + { + const auto product = nlohmann::detail::full_multiplication(a, b); + CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + }; + + const std::uint64_t max = (std::numeric_limits::max)(); + const std::array, 13> edge_cases = + { + { + {0, 0}, + {0, 1}, + {1, 1}, + {1, max}, + {0xFFFFFFFFu, 0x100000000u}, + {0x100000000u, 0x100000000u}, + {0x100000001u, 0x100000001u}, + {max, max}, + {max, 2}, + {0xFFFFFFFF00000000u, 0x100000001u}, + {0x100000001u, 0xFFFFFFFF00000000u}, + {max, 1}, + {2, max}, + } + }; + + for (const auto& test : edge_cases) + { + check_product(test.first, test.second); + } + // whichever implementation the compiler gets (with or without a - // 128-bit integer type or a builtin) + // 128-bit integer type or a builtin / intrinsic) std::uint64_t state = 42; for (int i = 0; i < 10000; ++i) { @@ -1146,8 +1178,7 @@ TEST_CASE("Eisel-Lemire float conversion") state ^= state << 17u; const std::uint64_t a = state; const std::uint64_t b = (state * 0x9E3779B97F4A7C15u) >> (i % 64); - const auto product = nlohmann::detail::full_multiplication(a, b); - CHECK(big_from(product.high, product.low) == big_mul(big_from(0, a), big_from(0, b))); + check_product(a, b); const int k = i % 64; const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1)); diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index b9257cb17..1e409bfb0 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -824,7 +824,7 @@ struct unordered_object_t : std::map, Allocator> return !(lhs == rhs); } }; -using unordered_json = nlohmann::basic_json; +using unordered_json = nlohmann::json::with_object_t; // the entries "0" to "9", enumerated in ascending or in descending order unordered_json make_unordered_object(const bool descending) @@ -875,7 +875,7 @@ struct key_case_less template using key_case_map = std::map; -using key_case_json = nlohmann::basic_json; +using key_case_json = nlohmann::json::with_object_t; // the innermost value of a chain of single-element arrays template @@ -905,7 +905,7 @@ struct case_insensitive_less template using case_insensitive_map = std::map; -using ci_json = nlohmann::basic_json; +using ci_json = nlohmann::json::with_object_t; } // namespace TEST_CASE("equality of objects whose entries have no fixed order") diff --git a/tests/src/unit-custom-array-type.cpp b/tests/src/unit-custom-array-type.cpp index 7a374b612..6f015d234 100644 --- a/tests/src/unit-custom-array-type.cpp +++ b/tests/src/unit-custom-array-type.cpp @@ -22,7 +22,7 @@ namespace // std::deque has no capacity() member function, which the library only needs // to detect a reallocation for JSON_DIAGNOSTICS -using deque_json = nlohmann::basic_json; +using deque_json = nlohmann::json::with_array_t; // a std::vector whose at() is hidden: the library performs its own bounds // check and must not fall back to the container's checked accessor @@ -39,7 +39,7 @@ class vector_without_at : public std::vector void at() = delete; }; -using no_at_json = nlohmann::basic_json; +using no_at_json = nlohmann::json::with_array_t; } // namespace diff --git a/tests/src/unit-custom-object-type.cpp b/tests/src/unit-custom-object-type.cpp index 0eaaf2d3c..fdd063292 100644 --- a/tests/src/unit-custom-object-type.cpp +++ b/tests/src/unit-custom-object-type.cpp @@ -179,7 +179,7 @@ class no_key_compare_map } }; -using no_key_compare_json = nlohmann::basic_json; +using no_key_compare_json = nlohmann::json::with_object_t; // An ObjectType whose erase(iterator) returns void rather than the following // iterator, as for instance Abseil's hash maps do @@ -196,7 +196,7 @@ struct void_erase_map : std::map } }; -using void_erase_json = nlohmann::basic_json; +using void_erase_json = nlohmann::json::with_object_t; // wraps an iterator, but only offers the LegacyForwardIterator operations, // like the iterators of std::unordered_map and other hash maps @@ -388,7 +388,7 @@ class forward_only_map } }; -using forward_only_json = nlohmann::basic_json; +using forward_only_json = nlohmann::json::with_object_t; } // namespace diff --git a/tests/src/unit-hash.cpp b/tests/src/unit-hash.cpp index e70b39b5a..117bc0151 100644 --- a/tests/src/unit-hash.cpp +++ b/tests/src/unit-hash.cpp @@ -157,13 +157,13 @@ TEST_CASE("hash") // the ends of the integer ranges, which equal floats exactly const auto int_min = (std::numeric_limits::min)(); const auto int_max = (std::numeric_limits::max)(); - const auto two_63 = json::number_unsigned_t(1) << 63U; + const auto two_63 = static_cast(1) << 63U; CHECK(json(int_min) == json(-9223372036854775808.0)); CHECK(std::hash {}(json(int_min)) == std::hash {}(json(-9223372036854775808.0))); CHECK(json(two_63) == json(9223372036854775808.0)); CHECK(std::hash {}(json(two_63)) == std::hash {}(json(9223372036854775808.0))); - CHECK(json(json::number_unsigned_t(int_max)) == json(int_max)); - CHECK(std::hash {}(json(json::number_unsigned_t(int_max))) == std::hash {}(json(int_max))); + CHECK(json(static_cast(int_max)) == json(int_max)); + CHECK(std::hash {}(json(static_cast(int_max))) == std::hash {}(json(int_max))); } TEST_CASE("hash") diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 9494fb3e8..4acb6f81b 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -1136,8 +1136,10 @@ TEST_CASE("json_view dump") CHECK(d.root().dump() == j.dump()); CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true)); - const ordered_json_document keys = ordered_json_document::parse(R"({"é\n": {"\"": [], "": {}}})"); - CHECK(keys.root().dump(2, ' ', true) == ordered_json::parse(R"({"é\n": {"\"": [], "": {}}})").dump(2, ' ', true)); + const std::string key_text = R"({"é\n": {"\"": [], "": {}}})"; + const ordered_json_document keys = ordered_json_document::parse(key_text); + const ordered_json key_json = ordered_json::parse(key_text); + CHECK(keys.root().dump(2, ' ', true) == key_json.dump(2, ' ', true)); } SECTION("numbers") diff --git a/tests/src/unit-large_json.cpp b/tests/src/unit-large_json.cpp index 6ce5626d8..cd615f4c3 100644 --- a/tests/src/unit-large_json.cpp +++ b/tests/src/unit-large_json.cpp @@ -426,7 +426,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") { for (std::size_t depth = 120; depth <= 140; ++depth) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(7)); CHECK(json::from_cbor(json::to_cbor(array)) == array); @@ -464,7 +464,7 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") nlohmann::detail::recursion_depth_limit() + 1, nlohmann::detail::recursion_depth_limit() + 2 }) { - CAPTURE(depth); + CAPTURE(depth) const json array = nested_array(depth, json(0)); std::vector expected_cbor(depth, 0x81); @@ -505,10 +505,22 @@ TEST_CASE("issue #5392 - binary writers on deeply nested values") const json discarded_leaf(json::value_t::discarded); const json deep_discarded = nested_array(depth, discarded_leaf); - CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error); - CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error); - CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error); - CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error); + // with diagnostics, the message names the path to the discarded leaf +#if JSON_DIAGNOSTICS + std::string path; + for (std::size_t i = 0; i < depth; ++i) + { + path += "/0"; + } + const std::string prefix = "[json.exception.type_error.321] (" + path + ") "; +#else + const std::string prefix = "[json.exception.type_error.321] "; +#endif + + CHECK_THROWS_WITH_AS(json::to_cbor(deep_discarded), (prefix + "cannot serialize discarded value to CBOR").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_msgpack(deep_discarded), (prefix + "cannot serialize discarded value to MessagePack").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_ubjson(deep_discarded), (prefix + "cannot serialize discarded value to UBJSON").c_str(), json::type_error); + CHECK_THROWS_WITH_AS(json::to_bjdata(deep_discarded), (prefix + "cannot serialize discarded value to BJData").c_str(), json::type_error); } SECTION("does not overflow the C++ stack") diff --git a/tests/src/unit-locale-cpp.cpp b/tests/src/unit-locale-cpp.cpp index 5f5e78c12..b665b6531 100644 --- a/tests/src/unit-locale-cpp.cpp +++ b/tests/src/unit-locale-cpp.cpp @@ -172,7 +172,7 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)") // a floating-point type that is not a float or a double is written // with snprintf, whose locale-specific decimal point and thousands // separator are undone afterwards - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; CHECK(long_double_json(12345.5L).dump() == "12345.5"); CHECK(long_double_json(1.0L).dump() == "1.0"); CHECK(long_double_json(-0.25L).dump() == "-0.25"); @@ -273,7 +273,7 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519 } text += "]"; - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; // reference values, parsed without a locale switch REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr); @@ -449,7 +449,7 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)") // long double on 64-bit Arm, where it is IEEE-754 double) takes the // locale-independent to_chars() path instead, and this test is a no-op // there. - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; using ld_limits = std::numeric_limits; const bool is_ieee_single_or_double = (ld_limits::is_iec559 && ld_limits::digits == 24 && ld_limits::max_exponent == 128) || diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index b705f94b7..d29769738 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -2432,8 +2432,8 @@ TEST_CASE("MessagePack numbers use the active union member (see #5644)") // used to read the union member that was not the active one, writing // wrong bytes for some values; std::int64_t/std::uint64_t (the default // types, where both members have the same width) were not affected - using int32_json = nlohmann::basic_json; - using int16_json = nlohmann::basic_json; + using int32_json = nlohmann::json::with_integers_t; + using int16_json = nlohmann::json::with_integers_t; SECTION("number_integer_t = std::int32_t") { diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 8f0d4c747..084eb79fd 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -39,7 +39,7 @@ using nlohmann::json; template using my_workaround_fifo_map = nlohmann::fifo_map, A>; -using my_json = nlohmann::basic_json; +using my_json = nlohmann::json::with_object_t; ///////////////////////////////////////////////////////////////////// // for #977 @@ -86,8 +86,7 @@ struct foo_serializer < T, typename std::enable_if < !std::is_same::valu }; } // namespace ns -using foo_json = nlohmann::basic_json>; +using foo_json = nlohmann::json::with_json_serializer_t; ///////////////////////////////////////////////////////////////////// // for #805 @@ -254,7 +253,7 @@ TEST_CASE("regression tests 1") { // create JSON class with nonstandard integer number type using custom_json = - nlohmann::basic_json; + nlohmann::json::with_integers_t::with_float_t; custom_json j; j["int_1"] = 1; CHECK(j["int_1"] == 1); @@ -470,18 +469,17 @@ TEST_CASE("regression tests 1") // create JSON class with nonstandard float number type // float - nlohmann::basic_json const j_float = + nlohmann::json::with_integers_t::with_float_t const j_float = 1.23e25f; CHECK(j_float.get() == 1.23e25f); // double - nlohmann::basic_json const j_double = + nlohmann::json const j_double = 1.23e35; CHECK(j_double.get() == 1.23e35); // long double - nlohmann::basic_json - const j_long_double = 1.23e45L; + nlohmann::json::with_float_t const j_long_double = 1.23e45L; CHECK(j_long_double.get() == 1.23e45L); } diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index d084e6432..b532e75ae 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -64,18 +64,7 @@ using ordered_json = nlohmann::ordered_json; ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t>; #endif #ifdef JSON_HAS_CPP_20 @@ -107,7 +96,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") // for #1021 ///////////////////////////////////////////////////////////////////// -using float_json = nlohmann::basic_json; +using float_json = nlohmann::json::with_float_t; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) namespace @@ -155,10 +144,8 @@ struct failing_allocator : std::allocator }; }; -using failing_json = nlohmann::basic_json; -using failing_ordered_json = nlohmann::basic_json; +using failing_json = nlohmann::json::with_allocator_t; +using failing_ordered_json = nlohmann::ordered_json::with_allocator_t; // builds `depth` levels of nesting around a scalar, iteratively (never // recursing: each wrap only moves the previous, already-built value, which diff --git a/tests/src/unit-regression3.cpp b/tests/src/unit-regression3.cpp index 69a95fb9b..f8d9740fa 100644 --- a/tests/src/unit-regression3.cpp +++ b/tests/src/unit-regression3.cpp @@ -62,18 +62,7 @@ using ordered_json = nlohmann::ordered_json; ///////////////////////////////////////////////////////////////////// // for #4804 ///////////////////////////////////////////////////////////////////// - using json_4804 = nlohmann::basic_json, // BinaryType - void // CustomBaseClass - >; + using json_4804 = nlohmann::json::with_binary_t>; #endif #ifdef JSON_HAS_CPP_20 @@ -930,7 +919,7 @@ TEST_CASE("regression test #5476 - array type without reserve()") { // the capacity reserved for definite-length arrays must not require the // array type to have a reserve() member function - using deque_json = nlohmann::basic_json; + using deque_json = nlohmann::json::with_array_t; SECTION("std::deque") { diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 1aa2111e7..c56c4f5c1 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -367,8 +367,7 @@ TEST_CASE("dump for basic_json with long double number_float_t") // serializer::dump_float(x, std::false_type). That branch must use the // "%.*Lg" format specifier; using "%.*g" with a long double argument is // undefined behavior and corrupts the output. - using long_double_json = nlohmann::basic_json; + using long_double_json = nlohmann::json::with_float_t; SECTION("round-trip dump/parse") { diff --git a/tools/check_build_options/README.md b/tools/check_build_options/README.md new file mode 100644 index 000000000..1b24a55ff --- /dev/null +++ b/tools/check_build_options/README.md @@ -0,0 +1,21 @@ +# check_build_options + +Checks that the Meson build and the pkg-config files offer the same options as the CMake target, so that a new CMake +option is not forgotten in one of them. + +The compile definitions of the CMake target (`target_compile_definitions` in [`CMakeLists.txt`](../../CMakeLists.txt)) +are the reference. When you add an option there, also add it to + +- the pkg-config block in `CMakeLists.txt` (`NLOHMANN_JSON_PKGCONFIG_CFLAGS`), +- [`meson_options.txt`](../../meson_options.txt), named without the `JSON_` prefix and with the same default, +- [`meson.build`](../../meson.build) (`json_defines`), and +- the list of Meson options in + [`docs/mkdocs/docs/integration/package_managers.md`](../../docs/mkdocs/docs/integration/package_managers.md). + +Run the check with + +```shell +make check_build_options +``` + +It needs only Python 3 and runs in the `ci_meson_install` CI job. diff --git a/tools/check_build_options/check_build_options.py b/tools/check_build_options/check_build_options.py new file mode 100755 index 000000000..9b9debfdc --- /dev/null +++ b/tools/check_build_options/check_build_options.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Check that the Meson build and the pkg-config files offer the CMake options. + +The compile definitions of the CMake target (target_compile_definitions in +CMakeLists.txt) are the reference. For every option used there, the script +checks that + +- the CMake pkg-config file adds the same definition under the same condition, +- meson_options.txt has a boolean option of the same name without the "JSON_" + prefix and with the same default, +- meson.build adds the same definition under the same condition, and +- the Meson section of the package manager documentation lists the option. + +Meson's MultipleHeaders option selects the include directory and adds no +definition; it is the only Meson option without a definition. +""" + +import argparse +import os +import re +import sys + +REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..')) +DOCS = os.path.join('docs', 'mkdocs', 'docs', 'integration', 'package_managers.md') + +# Meson options that add no compile definition, with their default +MESON_ONLY = {'MultipleHeaders': 'false'} + + +def read(root, path): + with open(os.path.join(root, path), encoding='utf-8') as f: + return f.read() + + +def cmake_target_definitions(cmake): + """Return {option: (definition, add_if_on)} from target_compile_definitions.""" + block = re.search(r'target_compile_definitions\(\s*\$\{NLOHMANN_JSON_TARGET_NAME\}\s*INTERFACE(.*?)\n\)', cmake, re.S) + if not block: + sys.exit('CMakeLists.txt: target_compile_definitions of the target not found') + result = {} + for line in block.group(1).split('\n'): + line = line.strip() + if not line: + continue + m = re.fullmatch(r'\$<\$>:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), False) + continue + m = re.fullmatch(r'\$<\$:(\w+=\w+)>', line) + if m: + result[m.group(1)] = (m.group(2), True) + continue + sys.exit(f'CMakeLists.txt: unexpected line in target_compile_definitions: {line}') + return result + + +def cmake_defaults(cmake): + """Return {option: 'true'/'false'} for the option() calls of the form JSON_.""" + return {m.group(1): 'true' if m.group(2) == 'ON' else 'false' + for m in re.finditer(r'^option\(JSON_(\w+)\s+"[^"]*"\s+(ON|OFF)\)', cmake, re.M)} + + +def cmake_pkgconfig_definitions(cmake): + """Return {option: (definition, add_if_on)} from the pkg-config block.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r'if \((NOT )?JSON_(\w+)\)\s*\n\s*string\(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -D(\w+=\w+)"\)', cmake)} + + +def meson_options(options): + """Return {option: default} for the boolean options in meson_options.txt.""" + result = {} + for block in re.findall(r'option\((.*?)\)', options, re.S): + name = re.search(r"'(\w+)'", block).group(1) + kind = re.search(r"type\s*:\s*'(\w+)'", block) + value = re.search(r'value\s*:\s*(\w+)', block) + result[name] = value.group(1) if kind and kind.group(1) == 'boolean' and value else None + return result + + +def meson_definitions(meson): + """Return {option: (definition, add_if_on)} from meson.build.""" + return {m.group(2): (m.group(3), m.group(1) is None) + for m in re.finditer(r"if (not )?get_option\('(\w+)'\)\s*\n\s*json_defines \+= '(\w+=\w+)'", meson)} + + +def describe(definition): + name, add_if_on = definition + return f'{name} if {"enabled" if add_if_on else "disabled"}' + + +def compare(errors, where, expected, actual): + for option, definition in expected.items(): + if option not in actual: + errors.append(f'{where}: no definition for option {option} (expected {describe(definition)})') + elif actual[option] != definition: + errors.append(f'{where}: option {option} adds {describe(actual[option])}, expected {describe(definition)}') + for option in actual.keys() - expected.keys(): + errors.append(f'{where}: definition for option {option}, which the CMake target does not have') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + parser.add_argument('root', nargs='?', default=REPO_ROOT, help='repository root (default: %(default)s)') + root = parser.parse_args().root + + cmake = read(root, 'CMakeLists.txt') + reference = cmake_target_definitions(cmake) + defaults = cmake_defaults(cmake) + errors = [] + + compare(errors, 'CMakeLists.txt (pkg-config)', reference, cmake_pkgconfig_definitions(cmake)) + compare(errors, 'meson.build', reference, meson_definitions(read(root, 'meson.build'))) + + options = meson_options(read(root, 'meson_options.txt')) + for option in reference: + if option not in defaults: + errors.append(f'CMakeLists.txt: no option(JSON_{option} ... ON|OFF)') + elif option not in options: + errors.append(f'meson_options.txt: option {option} missing') + elif options[option] != defaults[option]: + errors.append(f'meson_options.txt: option {option} must be boolean with value {defaults[option]} as in CMake') + for option, default in MESON_ONLY.items(): + if options.get(option) != default: + errors.append(f'meson_options.txt: option {option} must be boolean with value {default}') + for option in options.keys() - reference.keys() - MESON_ONLY.keys(): + errors.append(f'meson_options.txt: option {option} has no counterpart in the CMake target') + + docs = read(root, DOCS) + for option in sorted(set(options) & (reference.keys() | MESON_ONLY.keys())): + if f'`{option}`' not in docs: + errors.append(f'{DOCS}: Meson option {option} not listed') + + for error in errors: + print(error, file=sys.stderr) + if errors: + print('The Meson build and the pkg-config files must offer the options of the CMake target; see ' + 'tools/check_build_options/README.md.', file=sys.stderr) + return 1 + print(f'OK: {len(reference)} options with compile definitions agree between CMake, pkg-config, and Meson.') + return 0 + + +if __name__ == '__main__': + sys.exit(main())