diff --git a/.github/external_ci/appveyor.yml b/.github/external_ci/appveyor.yml index 805195742..4f0d3f5e3 100644 --- a/.github/external_ci/appveyor.yml +++ b/.github/external_ci/appveyor.yml @@ -16,6 +16,9 @@ only_commits: environment: matrix: + # The Visual Studio 2017 jobs compile everything with /std:c++17, so they + # only build the C++17 variant of each test, split into two jobs each to + # stay below AppVeyor's 60-minute limit per job. - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 configuration: Debug platform: x86 @@ -34,7 +37,13 @@ environment: configuration: Release platform: x86 CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX" - CMAKE_OPTIONS: "" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2" + GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + configuration: Release + platform: x86 + CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2" GENERATOR: Visual Studio 15 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 @@ -55,7 +64,13 @@ environment: configuration: Release platform: x64 CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX" - CMAKE_OPTIONS: "" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2" + GENERATOR: Visual Studio 15 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + configuration: Release + platform: x64 + CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX" + CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2" GENERATOR: Visual Studio 15 2017 init: @@ -66,7 +81,7 @@ install: - if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32 before_build: - - cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%" + - cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS% build_script: - cmake --build . --config "%configuration%" --parallel 2 diff --git a/cmake/ci.cmake b/cmake/ci.cmake index b9d246bbf..c4bced5ba 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -376,7 +376,7 @@ add_custom_target(ci_test_coverage # Sanitizers. ############################################################################### -set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base") +set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt") add_custom_target(ci_test_clang_sanitizer COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND} diff --git a/cmake/clang_sanitizer_ignorelist.txt b/cmake/clang_sanitizer_ignorelist.txt new file mode 100644 index 000000000..153b6a76d --- /dev/null +++ b/cmake/clang_sanitizer_ignorelist.txt @@ -0,0 +1,8 @@ +# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist). +# +# libstdc++ 14's declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format +# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only are +# excluded, so implicit sign changes in the library and the tests are still reported. +[implicit-integer-sign-change] +src:*/include/c++/*/format diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 9fc5f7e3d..8e956413c 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -250,6 +250,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html'); diff --git a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md index a84d83129..ca4139c48 100644 --- a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md +++ b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md @@ -69,7 +69,7 @@ The default value is `0` (disabled — existing behavior is preserved). ## Examples -??? example "Default behavior (macro not defined)" +??? example "Example: default behavior (macro not defined)" Without the macro, a map with enum keys is stored as an array of pairs: @@ -96,7 +96,7 @@ The default value is `0` (disabled — existing behavior is preserved). } ``` -??? example "Objects for enum-keyed maps (macro defined to 1)" +??? example "Example: objects for enum-keyed maps (macro defined to 1)" With the macro, the same map is stored as an object: diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 53f6d01cf..884ff014a 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) typename BasicJsonType::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); - e = static_cast(static_cast(val)); + e = static_cast(bool_aware_static_cast(val)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 5b332d4e9..f3b4994f1 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -287,9 +287,20 @@ struct external_constructor ///////////// #ifdef JSON_HAS_CPP_17 +// whether storing the value of a std::optional cannot throw; MSVC 2017 +// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so +// the exception would call std::terminate (#5642) +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 + template + using is_nothrow_optional_to_json = std::false_type; +#else + template + using is_nothrow_optional_to_json = std::is_nothrow_assignable; +#endif + template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(is_nothrow_optional_to_json::value) { if (opt.has_value()) { @@ -365,7 +376,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; - external_constructor::construct(j, static_cast(e)); + external_constructor::construct(j, bool_aware_static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 369e274fb..e6c698fa0 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -662,10 +662,11 @@ class binary_reader } // the value is -1 - number, which fits into number_integer_t - // whenever number does + // whenever number does; the outer cast undoes the integral promotion + // for number_integer_t types narrower than int if (JSON_HEDLEY_LIKELY(value_in_range_of(number))) { - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } // like the lexer does for JSON text, store a value too small for @@ -4003,7 +4004,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -4026,7 +4028,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -4037,7 +4040,8 @@ class binary_reader and NaN in the input are passed on unchanged. Integers only overflow if number_float_t cannot represent 2^64, e.g., a half-precision type. - @tparam NumberType a floating-point or integer type + @tparam NumberType a floating-point type (emit_signed and emit_unsigned + convert integers to long double first) @param[in] number the number @return whether the SAX parser accepted the value diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 69ab05f4e..4c90c68a3 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -906,6 +906,21 @@ T conditional_static_cast(U value) return value; } +// like conditional_static_cast, but converts to bool by comparing with zero, +// because MSVC 2015 warns about any conversion to bool (C4800), even with an +// explicit cast; used for enums whose underlying type is bool +template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > +T bool_aware_static_cast(U value) +{ + return conditional_static_cast(value); +} + +template::value, int> = 0> +bool bool_aware_static_cast(U value) +{ + return value != U(); +} + template using all_integral = conjunction...>; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index e1eeac12c..6f6bb8d04 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5098,7 +5098,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -5107,7 +5107,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -5128,11 +5128,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif } + // The friend comparisons with a scalar name the JSON type via decltype of + // their parameter in noexcept, because older MSVC versions do not see the + // class scope there: MSVC 2015 and 2017 take basic_json as the template, + // and MSVC 2019 16.0 rejects member types and template parameters. + /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs == basic_json(rhs); } @@ -5141,7 +5146,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) == rhs; } @@ -5157,7 +5162,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs != basic_json(rhs); } @@ -5166,7 +5171,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) != rhs; } @@ -5186,7 +5191,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs < basic_json(rhs); } @@ -5195,7 +5200,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) < rhs; } @@ -5215,7 +5220,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs <= basic_json(rhs); } @@ -5224,7 +5229,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -5245,7 +5250,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs > basic_json(rhs); } @@ -5254,7 +5259,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) > rhs; } @@ -5274,7 +5279,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs >= basic_json(rhs); } @@ -5283,7 +5288,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -5319,6 +5324,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return o; } +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in @@ -5327,9 +5335,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// replace calls like `j >> o;` with `o << j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) friend std::ostream& operator>>(const basic_json& j, std::ostream& o) -#if JSON_DELETE_DEPRECATED_FUNCTIONS - = delete; -#else { return o << j; } @@ -5507,6 +5512,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma clang diagnostic pop #endif #ifndef JSON_NO_IO +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in @@ -5515,9 +5523,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// replace calls like `j << i;` with `i >> j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) friend std::istream& operator<<(basic_json& j, std::istream& i) -#if JSON_DELETE_DEPRECATED_FUNCTIONS - = delete; -#else { return operator>>(i, j); } @@ -7178,6 +7183,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO) + /// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_ltlt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete; + + /// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_gtgt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete; +#endif + NLOHMANN_JSON_NAMESPACE_END /////////////////////// diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index b427b247e..d0a651093 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4905,6 +4905,21 @@ T conditional_static_cast(U value) return value; } +// like conditional_static_cast, but converts to bool by comparing with zero, +// because MSVC 2015 warns about any conversion to bool (C4800), even with an +// explicit cast; used for enums whose underlying type is bool +template < typename T, typename U, enable_if_t < !std::is_same::value, int > = 0 > +T bool_aware_static_cast(U value) +{ + return conditional_static_cast(value); +} + +template::value, int> = 0> +bool bool_aware_static_cast(U value) +{ + return value != U(); +} + template using all_integral = conjunction...>; @@ -5700,7 +5715,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) typename BasicJsonType::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); - e = static_cast(static_cast(val)); + e = static_cast(bool_aware_static_cast(val)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -7066,9 +7081,20 @@ struct external_constructor ///////////// #ifdef JSON_HAS_CPP_17 +// whether storing the value of a std::optional cannot throw; MSVC 2017 +// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so +// the exception would call std::terminate (#5642) +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 + template + using is_nothrow_optional_to_json = std::false_type; +#else + template + using is_nothrow_optional_to_json = std::is_nothrow_assignable; +#endif + template::value, int> = 0> -void to_json(BasicJsonType& j, const std::optional& opt) noexcept(std::is_nothrow_assignable::value) +void to_json(BasicJsonType& j, const std::optional& opt) noexcept(is_nothrow_optional_to_json::value) { if (opt.has_value()) { @@ -7144,7 +7170,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; - external_constructor::construct(j, static_cast(e)); + external_constructor::construct(j, bool_aware_static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -14353,10 +14379,11 @@ class binary_reader } // the value is -1 - number, which fits into number_integer_t - // whenever number does + // whenever number does; the outer cast undoes the integral promotion + // for number_integer_t types narrower than int if (JSON_HEDLEY_LIKELY(value_in_range_of(number))) { - return sax->number_integer(static_cast(-1) - static_cast(number)); + return sax->number_integer(conditional_static_cast(static_cast(-1) - static_cast(number))); } // like the lexer does for JSON text, store a value too small for @@ -17694,7 +17721,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -17717,7 +17745,8 @@ class binary_reader { return sax->number_unsigned(static_cast(number)); } - return emit_float(number); + // std::isfinite has no integer overloads in MSVC's + return emit_float(static_cast(number)); } /*! @@ -17728,7 +17757,8 @@ class binary_reader and NaN in the input are passed on unchanged. Integers only overflow if number_float_t cannot represent 2^64, e.g., a half-precision type. - @tparam NumberType a floating-point or integer type + @tparam NumberType a floating-point type (emit_signed and emit_unsigned + convert integers to long double first) @param[in] number the number @return whether the SAX parser accepted the value @@ -32248,7 +32278,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template requires std::is_scalar_v - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -32257,7 +32287,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template requires std::is_scalar_v - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -32278,11 +32308,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #endif } + // The friend comparisons with a scalar name the JSON type via decltype of + // their parameter in noexcept, because older MSVC versions do not see the + // class scope there: MSVC 2015 and 2017 take basic_json as the template, + // and MSVC 2019 16.0 rejects member types and template parameters. + /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs == basic_json(rhs); } @@ -32291,7 +32326,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ template::value, int>::type = 0> - friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) == rhs; } @@ -32307,7 +32342,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs != basic_json(rhs); } @@ -32316,7 +32351,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ template::value, int>::type = 0> - friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) != rhs; } @@ -32336,7 +32371,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs < basic_json(rhs); } @@ -32345,7 +32380,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ template::value, int>::type = 0> - friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) < rhs; } @@ -32365,7 +32400,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs <= basic_json(rhs); } @@ -32374,7 +32409,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ template::value, int>::type = 0> - friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) <= rhs; } @@ -32395,7 +32430,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs > basic_json(rhs); } @@ -32404,7 +32439,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ template::value, int>::type = 0> - friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) > rhs; } @@ -32424,7 +32459,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return lhs >= basic_json(rhs); } @@ -32433,7 +32468,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ template::value, int>::type = 0> - friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible::value) + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible, ScalarType>::value) { return basic_json(lhs) >= rhs; } @@ -32469,6 +32504,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return o; } +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief serialize to stream /// @sa https://json.nlohmann.me/api/operator_ltlt/ /// @deprecated This function is deprecated since 3.0.0 and will be removed in @@ -32477,9 +32515,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// replace calls like `j >> o;` with `o << j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) friend std::ostream& operator>>(const basic_json& j, std::ostream& o) -#if JSON_DELETE_DEPRECATED_FUNCTIONS - = delete; -#else { return o << j; } @@ -32657,6 +32692,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma clang diagnostic pop #endif #ifndef JSON_NO_IO +#if !JSON_DELETE_DEPRECATED_FUNCTIONS + // the deleted version is a function template after the class, because + // GCC < 5 and Clang < 10 reject deleted friend functions in class templates /// @brief deserialize from stream /// @sa https://json.nlohmann.me/api/operator_gtgt/ /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in @@ -32665,9 +32703,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// replace calls like `j << i;` with `i >> j;`. JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) friend std::istream& operator<<(basic_json& j, std::istream& i) -#if JSON_DELETE_DEPRECATED_FUNCTIONS - = delete; -#else { return operator>>(i, j); } @@ -34328,6 +34363,18 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j) return j.dump(); } +#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO) + /// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_ltlt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete; + + /// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&)) + /// @sa https://json.nlohmann.me/api/operator_gtgt/ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete; +#endif + NLOHMANN_JSON_NAMESPACE_END /////////////////////// diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aac6fee5e..8735934de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).") set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.") +set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as / (e.g. 0/2), to split them across CI jobs with a time limit.") # using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default") if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}") @@ -288,6 +289,33 @@ elseif(NOT json_32bit_test) list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp) endif() +# with JSON_TestShard=/, keep every -th unit test file, +# starting at (the glob is sorted, so the split is stable) +set(test_shard_index 0) +if(NOT "${JSON_TestShard}" STREQUAL "") + if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$") + message(FATAL_ERROR "JSON_TestShard must be /, e.g. 0/2, not '${JSON_TestShard}'.") + endif() + set(test_shard_index ${CMAKE_MATCH_1}) + set(test_shard_count ${CMAKE_MATCH_2}) + if(NOT test_shard_index LESS test_shard_count) + message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.") + endif() + list(LENGTH files test_file_count) + set(shard_files "") + set(file_position 0) + foreach(file ${files}) + math(EXPR file_shard "${file_position} % ${test_shard_count}") + if(file_shard EQUAL test_shard_index) + list(APPEND shard_files ${file}) + endif() + math(EXPR file_position "${file_position} + 1") + endforeach() + set(files ${shard_files}) + list(LENGTH files shard_file_count) + message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files") +endif() + foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() @@ -305,6 +333,9 @@ if(json_32bit_test_only) return() endif() +# the following variants of single test files are only built in the first shard +if(test_shard_index EQUAL 0) + # test legacy comparison of discarded values json_test_set_test_options(test-comparison_legacy COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 @@ -332,6 +363,8 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} ) +endif() + # *DO NOT* use json_test_set_test_options() below this line ############################################################################# diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index bc283ab28..a358e75f9 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -521,6 +521,10 @@ struct countdown_allocator : std::allocator TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)") { + // MSVC 2015's debug STL constructs the containers' debug proxies through + // the allocator in noexcept constructors, so a failing construction crashes + // the program there instead of throwing std::bad_alloc. Nothing to check. +#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) using countdown_json = nlohmann::basic_json 0); +#endif } namespace diff --git a/tests/src/unit-conversions2.cpp b/tests/src/unit-conversions2.cpp index eca482b83..c543e4ff8 100644 --- a/tests/src/unit-conversions2.cpp +++ b/tests/src/unit-conversions2.cpp @@ -191,8 +191,10 @@ TEST_CASE("value conversion") { enum class bool_enum : bool { off, on }; - CHECK(json(bool_enum::off).get() == bool_enum::off); - CHECK(json(bool_enum::on).get() == bool_enum::on); + // the extra parentheses keep doctest from printing the enum via its + // underlying type, which MSVC 2015 reports as C4800 + CHECK((json(bool_enum::off).get() == bool_enum::off)); + CHECK((json(bool_enum::on).get() == bool_enum::on)); } #endif @@ -851,8 +853,11 @@ TEST_CASE("std::optional") CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&); // the conversion is noexcept exactly when converting the contained value is + // (except with MSVC 2017, where it is never noexcept, see to_json.hpp) +#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920 static_assert(!std::is_nothrow_constructible&>::value); static_assert(std::is_nothrow_constructible&>::value); +#endif } #endif } diff --git a/tests/src/unit-delete_deprecated_functions.cpp b/tests/src/unit-delete_deprecated_functions.cpp index f54e9966a..a1d895370 100644 --- a/tests/src/unit-delete_deprecated_functions.cpp +++ b/tests/src/unit-delete_deprecated_functions.cpp @@ -22,6 +22,7 @@ using nlohmann::json; #include #include #include +#include #include #include #include @@ -109,6 +110,10 @@ JSON_TEST_DETECT(ptr_at, std::declval().at(std::declval::value); } +#endif + SECTION("the non-deprecated functions still work") { const json j = {{"a", {1, 2}}}; @@ -210,5 +217,13 @@ TEST_CASE("JSON_DELETE_DEPRECATED_FUNCTIONS") CHECK(j.at(ptr) == 2); CHECK(j.value(ptr, 0) == 2); CHECK(j.contains(ptr)); + + std::ostringstream os; + os << j; + CHECK(os.str() == R"({"a":[1,2]})"); + std::istringstream is(os.str()); + json parsed; + is >> parsed; + CHECK(parsed == j); } } diff --git a/tests/src/unit-element_access2.cpp b/tests/src/unit-element_access2.cpp index 9a7a58e6b..1b59d8add 100644 --- a/tests/src/unit-element_access2.cpp +++ b/tests/src/unit-element_access2.cpp @@ -1551,6 +1551,10 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j // count(0) used to compile and then crash instead of failing to compile using nlohmann::detail::is_detected; + // MSVC 2015 does not treat selecting a deleted function in decltype as + // a substitution failure, so it detects the deleted overloads as + // callable; calling them still fails to compile +#if !(defined(_MSC_VER) && _MSC_VER < 1910) CHECK_FALSE(is_detected::value); CHECK_FALSE(is_detected::value); CHECK_FALSE(is_detected::value); @@ -1562,6 +1566,7 @@ TEST_CASE_TEMPLATE("element access 2", Json, nlohmann::json, nlohmann::ordered_j // another integral literal type must be rejected as well, not just int CHECK_FALSE(is_detected::value); +#endif // the valid overloads must remain callable CHECK(is_detected::value);