From 84ee12a67cbee9b10109f6b3e4a834922f5e0ae5 Mon Sep 17 00:00:00 2001 From: Bas van Dijk Date: Fri, 9 Oct 2026 23:38:42 +0200 Subject: [PATCH 1/2] Make UTF8_ACCEPT/UTF8_REJECT inline variables to avoid TU-local exposure in modules (#5797) Signed-off-by: bvandijk Co-authored-by: bvandijk --- include/nlohmann/detail/string_utils.hpp | 4 ++-- single_include/nlohmann/json.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/string_utils.hpp b/include/nlohmann/detail/string_utils.hpp index 4ef748f13..e54a56da6 100644 --- a/include/nlohmann/detail/string_utils.hpp +++ b/include/nlohmann/detail/string_utils.hpp @@ -108,8 +108,8 @@ void encode_utf8(std::uint32_t cp, const Out& out) /////////////////// // UTF-8 decoder states used by decode() below -static constexpr std::uint8_t UTF8_ACCEPT = 0; -static constexpr std::uint8_t UTF8_REJECT = 1; +JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_ACCEPT = 0; +JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_REJECT = 1; /*! @brief process a byte of a UTF-8 sequence diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9de8ff2b2..09f67bdc3 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6395,8 +6395,8 @@ void encode_utf8(std::uint32_t cp, const Out& out) /////////////////// // UTF-8 decoder states used by decode() below -static constexpr std::uint8_t UTF8_ACCEPT = 0; -static constexpr std::uint8_t UTF8_REJECT = 1; +JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_ACCEPT = 0; +JSON_INLINE_VARIABLE constexpr std::uint8_t UTF8_REJECT = 1; /*! @brief process a byte of a UTF-8 sequence From 74aefc3486c6dcd03a5a039d86f1ac8d146c0f74 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 10 Oct 2026 00:05:02 +0200 Subject: [PATCH 2/2] Move custom object key tests out of unit-cbor.cpp and unit-msgpack.cpp (#5798) The custom object key tests from #5328 instantiate a second basic_json specialization in unit-cbor.cpp and unit-msgpack.cpp. This pushed unit-msgpack.cpp.obj past 65535 sections in the clang (MinGW) jobs of the Windows workflow, and linking test-msgpack_cpp11 fails with "relocation truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'". The GNU linker keeps the section an associative COMDAT section belongs to in 16 bits (x_associated in include/coff/internal.h), although big object files store 32 bits. In larger objects it therefore ties the jump tables of inline functions to the wrong function and discards them together with that function's duplicate. Move the four tests unchanged into unit-custom-object-key-type.cpp and document the limit in windows.yml. Signed-off-by: Niels Lohmann --- .github/workflows/windows.yml | 7 ++ tests/src/unit-cbor.cpp | 53 --------- tests/src/unit-custom-object-key-type.cpp | 127 ++++++++++++++++++++++ tests/src/unit-msgpack.cpp | 53 --------- 4 files changed, 134 insertions(+), 106 deletions(-) create mode 100644 tests/src/unit-custom-object-key-type.cpp diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 855d8af95..c5220e66c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -166,6 +166,13 @@ jobs: # link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash # before doctest prints its first line - 39 of 102 tests on clang 18. # Keep the objects small by splitting the test files instead. + # Objects with more than 65535 sections fail to link with "relocation + # truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'": the MinGW + # linker stores the section an associative COMDAT section belongs to in + # 16 bits (x_associated in binutils' include/coff/internal.h), so it + # discards the jump tables of inline functions together with the wrong + # function. Each basic_json specialization a test file instantiates adds + # many sections, so test a second one in a file of its own. - name: Run CMake run: cmake -S . -B build ^ -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^ diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 9b57d0664..9ee371495 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -28,7 +28,6 @@ using nlohmann::json; #include "make_test_data_available.hpp" #include "round_trip_corpus.hpp" #include "test_utils.hpp" -#include "custom_object_key_type.hpp" #include "sax_countdown.hpp" using utils::SaxCountdown; @@ -3358,55 +3357,3 @@ TEST_CASE("CBOR large strings and binaries (chunked reader)") } } } - -TEST_CASE("CBOR supports custom object key types") -{ - using custom_json = custom_object_key_test::json; - using custom_key = custom_object_key_test::key; - - custom_json::object_t object; - object.emplace(custom_key{"short"}, 1); - object.emplace( - custom_key{"a key longer than twenty-three characters"}, - 2); - - const custom_json value(std::move(object)); - const auto encoded = custom_json::to_cbor(value); - - CHECK(nlohmann::json::from_cbor(encoded) == nlohmann::json - { - {"short", 1}, - {"a key longer than twenty-three characters", 2} - }); -} - -TEST_CASE("CBOR supports custom object key types nested deeper than the recursion depth limit") -{ - // below detail::recursion_depth_limit(), keys are written by - // write_cbor_iterative instead of write_cbor - using custom_json = custom_object_key_test::json; - using custom_key = custom_object_key_test::key; - - const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10; - - custom_json value = 1; - nlohmann::json expected = 1; - for (std::size_t i = 0; i < depth; ++i) - { - // alternate short keys with ones long enough to need a length byte - const std::string name = (i % 2 == 0) ? "k" + std::to_string(i) - : "a key longer than thirty-one characters " + std::to_string(i); - - custom_json::object_t object; - object.emplace(custom_key{name}, std::move(value)); - value = custom_json(std::move(object)); - - nlohmann::json::object_t expected_object; - expected_object.emplace(name, std::move(expected)); - expected = nlohmann::json(std::move(expected_object)); - } - - const auto encoded = custom_json::to_cbor(value); - CHECK(encoded == nlohmann::json::to_cbor(expected)); - CHECK(nlohmann::json::from_cbor(encoded) == expected); -} diff --git a/tests/src/unit-custom-object-key-type.cpp b/tests/src/unit-custom-object-key-type.cpp new file mode 100644 index 000000000..9c64ebce0 --- /dev/null +++ b/tests/src/unit-custom-object-key-type.cpp @@ -0,0 +1,127 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include + +#include +#include +#include + +#include "custom_object_key_type.hpp" + +// These tests instantiate a second basic_json specialization. They live in +// their own file rather than in unit-cbor.cpp and unit-msgpack.cpp to keep +// those objects below 65535 sections: the MinGW linker stores the section a +// COMDAT section is associated with in 16 bits, so it misplaces the jump +// tables of larger objects (see the clang job in windows.yml). + +TEST_CASE("CBOR supports custom object key types") +{ + using custom_json = custom_object_key_test::json; + using custom_key = custom_object_key_test::key; + + custom_json::object_t object; + object.emplace(custom_key{"short"}, 1); + object.emplace( + custom_key{"a key longer than twenty-three characters"}, + 2); + + const custom_json value(std::move(object)); + const auto encoded = custom_json::to_cbor(value); + + CHECK(nlohmann::json::from_cbor(encoded) == nlohmann::json + { + {"short", 1}, + {"a key longer than twenty-three characters", 2} + }); +} + +TEST_CASE("CBOR supports custom object key types nested deeper than the recursion depth limit") +{ + // below detail::recursion_depth_limit(), keys are written by + // write_cbor_iterative instead of write_cbor + using custom_json = custom_object_key_test::json; + using custom_key = custom_object_key_test::key; + + const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10; + + custom_json value = 1; + nlohmann::json expected = 1; + for (std::size_t i = 0; i < depth; ++i) + { + // alternate short keys with ones long enough to need a length byte + const std::string name = (i % 2 == 0) ? "k" + std::to_string(i) + : "a key longer than thirty-one characters " + std::to_string(i); + + custom_json::object_t object; + object.emplace(custom_key{name}, std::move(value)); + value = custom_json(std::move(object)); + + nlohmann::json::object_t expected_object; + expected_object.emplace(name, std::move(expected)); + expected = nlohmann::json(std::move(expected_object)); + } + + const auto encoded = custom_json::to_cbor(value); + CHECK(encoded == nlohmann::json::to_cbor(expected)); + CHECK(nlohmann::json::from_cbor(encoded) == expected); +} + +TEST_CASE("MessagePack supports custom object key types") +{ + using custom_json = custom_object_key_test::json; + using custom_key = custom_object_key_test::key; + + custom_json::object_t object; + object.emplace(custom_key{"short"}, 1); + object.emplace( + custom_key{"a key longer than thirty-one characters"}, + 2); + + const custom_json value(std::move(object)); + const auto encoded = custom_json::to_msgpack(value); + + CHECK(nlohmann::json::from_msgpack(encoded) == nlohmann::json + { + {"short", 1}, + {"a key longer than thirty-one characters", 2} + }); +} + +TEST_CASE("MessagePack supports custom object key types nested deeper than the recursion depth limit") +{ + // below detail::recursion_depth_limit(), keys are written by + // write_msgpack_iterative instead of write_msgpack + using custom_json = custom_object_key_test::json; + using custom_key = custom_object_key_test::key; + + const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10; + + custom_json value = 1; + nlohmann::json expected = 1; + for (std::size_t i = 0; i < depth; ++i) + { + // alternate short keys with ones long enough to need a length byte + const std::string name = (i % 2 == 0) ? "k" + std::to_string(i) + : "a key longer than thirty-one characters " + std::to_string(i); + + custom_json::object_t object; + object.emplace(custom_key{name}, std::move(value)); + value = custom_json(std::move(object)); + + nlohmann::json::object_t expected_object; + expected_object.emplace(name, std::move(expected)); + expected = nlohmann::json(std::move(expected_object)); + } + + const auto encoded = custom_json::to_msgpack(value); + CHECK(encoded == nlohmann::json::to_msgpack(expected)); + CHECK(nlohmann::json::from_msgpack(encoded) == expected); +} diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 1e1005ad7..aeb0116d3 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -31,7 +31,6 @@ using nlohmann::json; #include "make_test_data_available.hpp" #include "round_trip_corpus.hpp" #include "test_utils.hpp" -#include "custom_object_key_type.hpp" #include "sax_countdown.hpp" using utils::SaxCountdown; @@ -2483,55 +2482,3 @@ TEST_CASE("MessagePack large strings and binaries (chunked reader)") } } } - -TEST_CASE("MessagePack supports custom object key types") -{ - using custom_json = custom_object_key_test::json; - using custom_key = custom_object_key_test::key; - - custom_json::object_t object; - object.emplace(custom_key{"short"}, 1); - object.emplace( - custom_key{"a key longer than thirty-one characters"}, - 2); - - const custom_json value(std::move(object)); - const auto encoded = custom_json::to_msgpack(value); - - CHECK(nlohmann::json::from_msgpack(encoded) == nlohmann::json - { - {"short", 1}, - {"a key longer than thirty-one characters", 2} - }); -} - -TEST_CASE("MessagePack supports custom object key types nested deeper than the recursion depth limit") -{ - // below detail::recursion_depth_limit(), keys are written by - // write_msgpack_iterative instead of write_msgpack - using custom_json = custom_object_key_test::json; - using custom_key = custom_object_key_test::key; - - const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10; - - custom_json value = 1; - nlohmann::json expected = 1; - for (std::size_t i = 0; i < depth; ++i) - { - // alternate short keys with ones long enough to need a length byte - const std::string name = (i % 2 == 0) ? "k" + std::to_string(i) - : "a key longer than thirty-one characters " + std::to_string(i); - - custom_json::object_t object; - object.emplace(custom_key{name}, std::move(value)); - value = custom_json(std::move(object)); - - nlohmann::json::object_t expected_object; - expected_object.emplace(name, std::move(expected)); - expected = nlohmann::json(std::move(expected_object)); - } - - const auto encoded = custom_json::to_msgpack(value); - CHECK(encoded == nlohmann::json::to_msgpack(expected)); - CHECK(nlohmann::json::from_msgpack(encoded) == expected); -}