diff --git a/tests/src/unit-custom-number-types.cpp b/tests/src/unit-custom-number-types.cpp index 5555af34e..e5d9d7b30 100644 --- a/tests/src/unit-custom-number-types.cpp +++ b/tests/src/unit-custom-number-types.cpp @@ -133,7 +133,7 @@ __extension__ typedef __int128 int128; // NOLINT(modernize-use-using) __extension__ typedef unsigned __int128 uint128; // NOLINT(modernize-use-using) static_assert(std::numeric_limits::digits == 127 && std::numeric_limits::digits == 128 && - std::is_integral::value && std::is_integral::value, + std::is_integral::value && std::is_integral::value, // NOLINT(misc-redundant-expression) "__int128 is not fully supported by the standard library"); using wide_json = nlohmann::basic_json; @@ -209,9 +209,16 @@ TEST_CASE("custom number types: integers beyond 64 bits") SECTION("BSON") { + // wrap() stores the value at /v, which JSON_DIAGNOSTICS adds to the message +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big)), "[json.exception.out_of_range.407] (/v) integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&); + CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big_negative)), "[json.exception.out_of_range.407] (/v) integer number -1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&); + CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(uint_big)), "[json.exception.out_of_range.407] (/v) integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit uint64", out_of_range&); +#else CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&); CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(int_big_negative)), "[json.exception.out_of_range.407] integer number -1267650600228229401496703205376 cannot be represented by BSON as it does not fit int64", out_of_range&); CHECK_THROWS_WITH_AS(_ = wide_json::to_bson(wrap(uint_big)), "[json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by BSON as it does not fit uint64", out_of_range&); +#endif CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(max_int64 + 1))), out_of_range&); CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(make_int(min_int64 - 1))), out_of_range&); diff --git a/tests/src/unit-msgpack-custom-types.cpp b/tests/src/unit-msgpack-custom-types.cpp new file mode 100644 index 000000000..5c4ab6173 --- /dev/null +++ b/tests/src/unit-msgpack-custom-types.cpp @@ -0,0 +1,296 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 + +// MessagePack tests with basic_json specializations other than nlohmann::json; +// they are kept apart from unit-msgpack.cpp because every specialization adds +// many sections to the object file, and MinGW cannot link objects with more +// than 65535 sections (see the clang job in .github/workflows/windows.yml) + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +#include // SIZE_MAX, UINT32_MAX +#include +#include +#include +#include +#include +#include +#include + +// the fake sizes below do not fit into a 32-bit std::size_t +// with clang and libstdc++ 10, the std::filesystem::path conversion that +// C++17 builds consider for every string type is ambiguous for a class +// derived from std::string, so the string case is not tested there +#if !(defined(__clang__) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11) + #define JSON_TEST_BEYOND_UINT32_STRING 1 +#endif + +#if SIZE_MAX > UINT32_MAX +template> +struct huge_array : std::vector +{ + using base = std::vector; + using base::base; + + bool fake_size = false; + + std::size_t size() const noexcept + { + if (fake_size) + { + return (std::numeric_limits::max)() + 1ULL; + } + + return base::size(); + } +}; + +using huge_array_json = nlohmann::json::with_array_t; + +TEST_CASE("MessagePack Size above uint32 for array") +{ + huge_array_json j = huge_array_json::array(); + + j.push_back(1); + j.push_back(2); + j.push_back(3); + + auto& array = j.get_ref(); + array.fake_size = true; + + // write into a caller-owned vector: to_msgpack(j) reserves space based on + // the (faked) element count, which fails with bad_alloc on Windows + std::vector result; + CHECK_THROWS_WITH_AS( + huge_array_json::to_msgpack(j, result), + "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", + json::out_of_range&); + + array.fake_size = false; +} + +template, + typename A = std::allocator>> + struct huge_map : std::map +{ + using base = std::map; + using base::base; + + bool fake_size = false; + + std::size_t size() const noexcept + { + if (fake_size) + { + return static_cast(UINT32_MAX) + 1ULL; + } + + return base::size(); + } +}; + +using huge_object_json = nlohmann::json::with_object_t; + +TEST_CASE("MessagePack Size above uint32 for object") +{ + + huge_object_json j = huge_object_json::object(); + + j["one"] = 1; + j["two"] = 2; + + auto& object = j.get_ref(); + object.fake_size = true; + + // write into a caller-owned vector: to_msgpack(j) reserves space based on + // the (faked) element count, which fails with bad_alloc on Windows + std::vector result; + CHECK_THROWS_WITH_AS( + huge_object_json::to_msgpack(j, result), + "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", + json::out_of_range&); + + object.fake_size = false; +} + +#ifdef JSON_TEST_BEYOND_UINT32_STRING +struct huge_string : std::string +{ + using std::string::string; + + std::size_t size() const noexcept // NOLINT(readability-convert-member-functions-to-static) + { + return static_cast(UINT32_MAX) + 1ULL; + } +}; + +using huge_string_json = nlohmann::json::with_string_t; + +TEST_CASE("MessagePack Size above uint32 for string") +{ + const huge_string_json j = "hello"; + + CHECK_THROWS_WITH_AS( + huge_string_json::to_msgpack(j), + "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", + json::out_of_range&); +} +#endif + +struct huge_binary : std::vector +{ + using std::vector::vector; + + std::size_t size() const noexcept // NOLINT(readability-convert-member-functions-to-static) + { + return static_cast(UINT32_MAX) + 1ULL; + } +}; + +using huge_binary_json = nlohmann::json::with_binary_t; + +TEST_CASE("MessagePack Size above uint32 for binary") +{ + + huge_binary_json j = huge_binary_json::binary(huge_binary{}); + + j.get_binary().push_back(0x01); + j.get_binary().push_back(0x02); + + CHECK_THROWS_WITH_AS( + huge_binary_json::to_msgpack(j), + "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", + json::out_of_range&); +} +#endif + +namespace +{ +// types that report a size beyond UINT32_MAX without allocating that much +// memory, so the MessagePack length limit can be tested cheaply; see the +// similar types in unit-bson.cpp +std::size_t beyond_uint32_size() +{ + return static_cast((std::numeric_limits::max)()) + 1; +} + +class beyond_uint32_binary_t : public std::vector +{ + public: + using std::vector::vector; + + size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static) + { + return beyond_uint32_size(); + } +}; + +#ifdef JSON_TEST_BEYOND_UINT32_STRING +class beyond_uint32_string_t : public std::string +{ + public: + using std::string::string; + + size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static) + { + return beyond_uint32_size(); + } +}; + +using beyond_uint32_string_json = nlohmann::json::with_string_t; +#endif + +using beyond_uint32_binary_json = nlohmann::json::with_binary_t; +} // namespace + +TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized") +{ + // MessagePack stores the length of a string, binary value, array, or + // object in at most 32 bits; a larger one used to be written without any + // length at all +#if SIZE_MAX > UINT32_MAX + { + const char* const expected = "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295"; + + const beyond_uint32_binary_json binary = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}); + CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(binary), expected, beyond_uint32_binary_json::out_of_range&); + + const beyond_uint32_binary_json ext = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}, 42); + CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(ext), expected, beyond_uint32_binary_json::out_of_range&); + +#ifdef JSON_TEST_BEYOND_UINT32_STRING + // created from its type rather than from a beyond_uint32_string_t: + // that would consider the std::filesystem::path conversion, which + // libstdc++ 10 cannot decide for a class derived from std::string + const beyond_uint32_string_json string(beyond_uint32_string_json::value_t::string); + CHECK_THROWS_WITH_AS(beyond_uint32_string_json::to_msgpack(string), expected, beyond_uint32_string_json::out_of_range&); +#endif + } +#endif +} + +TEST_CASE("MessagePack numbers use the active union member (see #5644)") +{ + // when number_integer_t is narrower than number_unsigned_t, to_msgpack() + // 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::json::with_integers_t; + using int16_json = nlohmann::json::with_integers_t; + + SECTION("number_integer_t = std::int32_t") + { + SECTION("6442450944 (uint 64; the low 32 bits used to be sign-extended)") + { + const int32_json j = 6442450944ULL; + CHECK(j.is_number_unsigned()); + + std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00}; + const auto result = int32_json::to_msgpack(j); + CHECK(result == expected); + CHECK(int32_json::from_msgpack(result) == j); + } + + SECTION("4294967496 (uint 64; the low 32 bits used to be the whole value)") + { + const int32_json j = 4294967496ULL; + CHECK(j.is_number_unsigned()); + + std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc8}; + const auto result = int32_json::to_msgpack(j); + CHECK(result == expected); + CHECK(int32_json::from_msgpack(result) == j); + } + } + + SECTION("number_integer_t = std::int16_t, 98304 (uint 32)") + { + const int16_json j = 98304ULL; + CHECK(j.is_number_unsigned()); + + std::vector const expected{0xce, 0x00, 0x01, 0x80, 0x00}; + const auto result = int16_json::to_msgpack(j); + CHECK(result == expected); + CHECK(int16_json::from_msgpack(result) == j); + } + + SECTION("default types (std::int64_t/std::uint64_t) are unaffected") + { + const json j = 4294967496ULL; + CHECK(j.is_number_unsigned()); + + std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc8}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(json::from_msgpack(result) == j); + } +} diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index aeb0116d3..2890a240c 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -2173,276 +2173,6 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip()) } } -// the fake sizes below do not fit into a 32-bit std::size_t -// with clang and libstdc++ 10, the std::filesystem::path conversion that -// C++17 builds consider for every string type is ambiguous for a class -// derived from std::string, so the string case is not tested there -#if !(defined(__clang__) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11) - #define JSON_TEST_BEYOND_UINT32_STRING 1 -#endif - -#if SIZE_MAX > UINT32_MAX -template> -struct huge_array : std::vector -{ - using base = std::vector; - using base::base; - - bool fake_size = false; - - std::size_t size() const noexcept - { - if (fake_size) - { - return (std::numeric_limits::max)() + 1ULL; - } - - return base::size(); - } -}; - -using huge_array_json = nlohmann::json::with_array_t; - -TEST_CASE("MessagePack Size above uint32 for array") -{ - huge_array_json j = huge_array_json::array(); - - j.push_back(1); - j.push_back(2); - j.push_back(3); - - auto& array = j.get_ref(); - array.fake_size = true; - - // write into a caller-owned vector: to_msgpack(j) reserves space based on - // the (faked) element count, which fails with bad_alloc on Windows - std::vector result; - CHECK_THROWS_WITH_AS( - huge_array_json::to_msgpack(j, result), - "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", - json::out_of_range&); - - array.fake_size = false; -} - -template, - typename A = std::allocator>> - struct huge_map : std::map -{ - using base = std::map; - using base::base; - - bool fake_size = false; - - std::size_t size() const noexcept - { - if (fake_size) - { - return static_cast(UINT32_MAX) + 1ULL; - } - - return base::size(); - } -}; - -using huge_object_json = nlohmann::json::with_object_t; - -TEST_CASE("MessagePack Size above uint32 for object") -{ - - huge_object_json j = huge_object_json::object(); - - j["one"] = 1; - j["two"] = 2; - - auto& object = j.get_ref(); - object.fake_size = true; - - // write into a caller-owned vector: to_msgpack(j) reserves space based on - // the (faked) element count, which fails with bad_alloc on Windows - std::vector result; - CHECK_THROWS_WITH_AS( - huge_object_json::to_msgpack(j, result), - "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", - json::out_of_range&); - - object.fake_size = false; -} - -#ifdef JSON_TEST_BEYOND_UINT32_STRING -struct huge_string : std::string -{ - using std::string::string; - - std::size_t size() const noexcept // NOLINT(readability-convert-member-functions-to-static) - { - return static_cast(UINT32_MAX) + 1ULL; - } -}; - -using huge_string_json = nlohmann::json::with_string_t; - -TEST_CASE("MessagePack Size above uint32 for string") -{ - const huge_string_json j = "hello"; - - CHECK_THROWS_WITH_AS( - huge_string_json::to_msgpack(j), - "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", - json::out_of_range&); -} -#endif - -struct huge_binary : std::vector -{ - using std::vector::vector; - - std::size_t size() const noexcept // NOLINT(readability-convert-member-functions-to-static) - { - return static_cast(UINT32_MAX) + 1ULL; - } -}; - -using huge_binary_json = nlohmann::json::with_binary_t; - -TEST_CASE("MessagePack Size above uint32 for binary") -{ - - huge_binary_json j = huge_binary_json::binary(huge_binary{}); - - j.get_binary().push_back(0x01); - j.get_binary().push_back(0x02); - - CHECK_THROWS_WITH_AS( - huge_binary_json::to_msgpack(j), - "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295", - json::out_of_range&); -} -#endif - -namespace -{ -// types that report a size beyond UINT32_MAX without allocating that much -// memory, so the MessagePack length limit can be tested cheaply; see the -// similar types in unit-bson.cpp -std::size_t beyond_uint32_size() -{ - return static_cast((std::numeric_limits::max)()) + 1; -} - -class beyond_uint32_binary_t : public std::vector -{ - public: - using std::vector::vector; - - size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static) - { - return beyond_uint32_size(); - } -}; - -#ifdef JSON_TEST_BEYOND_UINT32_STRING -class beyond_uint32_string_t : public std::string -{ - public: - using std::string::string; - - size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static) - { - return beyond_uint32_size(); - } -}; - -using beyond_uint32_string_json = nlohmann::json::with_string_t; -#endif - -using beyond_uint32_binary_json = nlohmann::json::with_binary_t; -} // namespace - -TEST_CASE("MessagePack lengths beyond UINT32_MAX cannot be serialized") -{ - // MessagePack stores the length of a string, binary value, array, or - // object in at most 32 bits; a larger one used to be written without any - // length at all -#if SIZE_MAX > UINT32_MAX - { - const char* const expected = "[json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295"; - - const beyond_uint32_binary_json binary = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}); - CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(binary), expected, beyond_uint32_binary_json::out_of_range&); - - const beyond_uint32_binary_json ext = beyond_uint32_binary_json::binary(beyond_uint32_binary_t{}, 42); - CHECK_THROWS_WITH_AS(beyond_uint32_binary_json::to_msgpack(ext), expected, beyond_uint32_binary_json::out_of_range&); - -#ifdef JSON_TEST_BEYOND_UINT32_STRING - // created from its type rather than from a beyond_uint32_string_t: - // that would consider the std::filesystem::path conversion, which - // libstdc++ 10 cannot decide for a class derived from std::string - const beyond_uint32_string_json string(beyond_uint32_string_json::value_t::string); - CHECK_THROWS_WITH_AS(beyond_uint32_string_json::to_msgpack(string), expected, beyond_uint32_string_json::out_of_range&); -#endif - } -#endif -} - -TEST_CASE("MessagePack numbers use the active union member (see #5644)") -{ - // when number_integer_t is narrower than number_unsigned_t, to_msgpack() - // 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::json::with_integers_t; - using int16_json = nlohmann::json::with_integers_t; - - SECTION("number_integer_t = std::int32_t") - { - SECTION("6442450944 (uint 64; the low 32 bits used to be sign-extended)") - { - const int32_json j = 6442450944ULL; - CHECK(j.is_number_unsigned()); - - std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00}; - const auto result = int32_json::to_msgpack(j); - CHECK(result == expected); - CHECK(int32_json::from_msgpack(result) == j); - } - - SECTION("4294967496 (uint 64; the low 32 bits used to be the whole value)") - { - const int32_json j = 4294967496ULL; - CHECK(j.is_number_unsigned()); - - std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc8}; - const auto result = int32_json::to_msgpack(j); - CHECK(result == expected); - CHECK(int32_json::from_msgpack(result) == j); - } - } - - SECTION("number_integer_t = std::int16_t, 98304 (uint 32)") - { - const int16_json j = 98304ULL; - CHECK(j.is_number_unsigned()); - - std::vector const expected{0xce, 0x00, 0x01, 0x80, 0x00}; - const auto result = int16_json::to_msgpack(j); - CHECK(result == expected); - CHECK(int16_json::from_msgpack(result) == j); - } - - SECTION("default types (std::int64_t/std::uint64_t) are unaffected") - { - const json j = 4294967496ULL; - CHECK(j.is_number_unsigned()); - - std::vector const expected{0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc8}; - const auto result = json::to_msgpack(j); - CHECK(result == expected); - CHECK(json::from_msgpack(result) == j); - } -} - TEST_CASE("MessagePack large strings and binaries (chunked reader)") { // get_msgpack_string()/get_msgpack_binary() both read through get_binary(),