diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bbc1ef48b..6f1cf8cae 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -166,19 +166,21 @@ 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. + # The tests link with LLD: GNU ld fails on objects with more than 32767 + # sections with "relocation truncated to fit: IMAGE_REL_AMD64_REL32 + # against `.rdata'", as it stores the section an associative COMDAT + # section belongs to in 16 bits (x_associated in binutils' + # include/coff/internal.h) and so discards the jump tables of inline + # functions together with the wrong function. Each basic_json + # specialization and input type a test file instantiates adds many + # sections, so unit-msgpack.cpp and unit-regression2.cpp kept crossing + # that limit as the library grew. - name: Run CMake run: cmake -S . -B build ^ -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^ -DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^ -DCMAKE_CXX_FLAGS_DEBUG="-g0" ^ - -DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^ + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld -lwinpthread" ^ -G"MinGW Makefiles" ^ -DCMAKE_BUILD_TYPE=Debug ^ -DJSON_BuildTests=On diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 5b217d7fe..020115531 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -13,7 +13,7 @@ #include // optional #endif -#include // begin, end +#include // begin, end, make_move_iterator, move_iterator #include // allocator_traits #include // basic_string, char_traits #include // tuple, get @@ -48,6 +48,32 @@ namespace detail * leave j with the type of the new value, but the pointer to the destroyed old one. */ +// a move iterator over the elements of an rvalue container, unless they are +// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a +// std::move_iterator as value_type&&, which cannot bind to a const element +template +std::move_iterator make_move_iterator_if_mutable_impl(Iterator it, std::true_type /*mutable*/) +{ + return std::make_move_iterator(it); +} + +template +Iterator make_move_iterator_if_mutable_impl(Iterator it, std::false_type /*mutable*/) +{ + return it; +} + +template +using is_mutable_iterator = std::integral_constant < bool, + !std::is_const())>::type>::value >; + +template +auto make_move_iterator_if_mutable(Iterator it) +-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator {})) +{ + return make_move_iterator_if_mutable_impl(it, is_mutable_iterator {}); +} + template struct external_constructor; template<> @@ -174,7 +200,7 @@ struct external_constructor using std::begin; using std::end; - const typename BasicJsonType::json_value value(j.template create(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr)))); + const typename BasicJsonType::json_value value(j.template create(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr)))); j.replace_value(value_t::array, value); } @@ -235,7 +261,7 @@ struct external_constructor using std::begin; using std::end; - const typename BasicJsonType::json_value value(j.template create(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj)))); + const typename BasicJsonType::json_value value(j.template create(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj)))); j.replace_value(value_t::object, value); } @@ -413,25 +439,27 @@ using is_movable_container_rvalue = negation, has_dedicated_to_json, is_basic_json, - is_range_view_type, is_enum_keyed_map_type, std::is_same>>>; -// rvalue array-like containers +// rvalue array-like containers; object types are ruled out before views are +// checked, as std::ranges::view for the object_t of an ordered_json would +// need that ordered_json to be complete (GCC 11, see #3312) template using is_rvalue_array_to_json = conjunction, + negation>, + negation>, is_compatible_array_type, negation, std::is_same, - is_compatible_object_type, is_compatible_string_type, is_compatible_binary_type>>>; // rvalue containers: their elements are moved instead of copied template::value, int> = 0> -inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) +inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward) { external_constructor::construct_moving(j, arr); } @@ -509,7 +537,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) } #endif -// rvalue object-like containers +// rvalue object-like containers (views have no key_type and mapped_type, so +// they are no compatible object type) template using is_rvalue_object_to_json = conjunction, negation>, @@ -517,7 +546,7 @@ using is_rvalue_object_to_json = conjunction, template::value, int> = 0> -inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) +inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward) { external_constructor::construct_moving(j, obj); } diff --git a/include/nlohmann/detail/conversions/zmij.hpp b/include/nlohmann/detail/conversions/zmij.hpp index 3820d891a..3b2e8e7a3 100644 --- a/include/nlohmann/detail/conversions/zmij.hpp +++ b/include/nlohmann/detail/conversions/zmij.hpp @@ -185,7 +185,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast(shift)); std::uint64_t integral = p.high >> static_cast(extra_shift); // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) - constexpr unsigned fractional_low_shift = static_cast(extra_shift); + constexpr auto fractional_low_shift = static_cast(extra_shift); constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); std::uint64_t digit = 0; diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index e5fb3750b..b19ec5757 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -334,7 +334,7 @@ class json_pointer { break; // not a plain number: let strtoull decide } - acc = static_cast(acc * 10 + static_cast(c - '0')); + acc = static_cast((acc * 10) + static_cast(c - '0')); if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) { idx = acc; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 37447262e..bc6988a20 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -29,6 +29,7 @@ #endif // JSON_NO_IO #include // make_move_iterator, random_access_iterator_tag #include // numeric_limits +#include // map #include // unique_ptr #include // swap, operator!= #include // length_error @@ -754,26 +755,20 @@ private: // one level deep); stops at the first structured child it meets static bool all_children_leaves(const basic_json& v) noexcept { + // array_t only needs forward iterators (see custom_array_type) if (v.m_data.m_type == value_t::array) { const auto& a = *v.m_data.m_value.array; - for (auto it = a.rbegin(); it != a.rend(); ++it) + return std::all_of(a.begin(), a.end(), [](const basic_json & el) { - if (!has_no_children(*it)) - { - return false; - } - } - return true; + return has_no_children(el); + }); } - for (const auto& el : *v.m_data.m_value.object) + const auto& o = *v.m_data.m_value.object; + return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el) { - if (!has_no_children(el.second)) - { - return false; - } - } - return true; + return has_no_children(el.second); + }); } // The walk in destroy_container() may take the children of a @@ -3043,18 +3038,21 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_iterator_range_of_null(const basic_json* obj) { + static_cast(obj); // unused when JSON_NOEXCEPTION is defined JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); } JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_incompatible_reference_type(const basic_json& obj) { + static_cast(obj); // unused when JSON_NOEXCEPTION is defined JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_exceeds_size_type(size_type idx) { + static_cast(idx); // unused when JSON_NOEXCEPTION is defined JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); } @@ -3062,6 +3060,8 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_out_of_range(size_type idx, const basic_json& j) { + static_cast(idx); // unused when JSON_NOEXCEPTION is defined + static_cast(j); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } @@ -3070,6 +3070,8 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_key_not_found(const KeyType& key, const basic_json& j) { + static_cast(key); // unused when JSON_NOEXCEPTION is defined + static_cast(j); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); } @@ -5453,7 +5455,7 @@ public: // inline fast path for two values of the same non-owning scalar type; anything // else falls through to the out-of-line general comparison #define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \ - if (lhs.m_data.m_type == rhs.m_data.m_type) \ + if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \ { \ switch (lhs.m_data.m_type) \ { \ diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index bcc8e5831..3dcebc881 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -29,6 +29,7 @@ #endif // JSON_NO_IO #include // make_move_iterator, random_access_iterator_tag #include // numeric_limits +#include // map #include // unique_ptr #include // swap, operator!= #include // length_error @@ -6555,7 +6556,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // optional #endif -#include // begin, end +#include // begin, end, make_move_iterator, move_iterator #include // allocator_traits #include // basic_string, char_traits #include // tuple, get @@ -7204,6 +7205,32 @@ namespace detail * leave j with the type of the new value, but the pointer to the destroyed old one. */ +// a move iterator over the elements of an rvalue container, unless they are +// const (e.g., std::set): libstdc++ before GCC 5 defines the reference of a +// std::move_iterator as value_type&&, which cannot bind to a const element +template +std::move_iterator make_move_iterator_if_mutable_impl(Iterator it, std::true_type /*mutable*/) +{ + return std::make_move_iterator(it); +} + +template +Iterator make_move_iterator_if_mutable_impl(Iterator it, std::false_type /*mutable*/) +{ + return it; +} + +template +using is_mutable_iterator = std::integral_constant < bool, + !std::is_const())>::type>::value >; + +template +auto make_move_iterator_if_mutable(Iterator it) +-> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator {})) +{ + return make_move_iterator_if_mutable_impl(it, is_mutable_iterator {}); +} + template struct external_constructor; template<> @@ -7330,7 +7357,7 @@ struct external_constructor using std::begin; using std::end; - const typename BasicJsonType::json_value value(j.template create(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr)))); + const typename BasicJsonType::json_value value(j.template create(make_move_iterator_if_mutable(begin(arr)), make_move_iterator_if_mutable(end(arr)))); j.replace_value(value_t::array, value); } @@ -7391,7 +7418,7 @@ struct external_constructor using std::begin; using std::end; - const typename BasicJsonType::json_value value(j.template create(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj)))); + const typename BasicJsonType::json_value value(j.template create(make_move_iterator_if_mutable(begin(obj)), make_move_iterator_if_mutable(end(obj)))); j.replace_value(value_t::object, value); } @@ -7569,25 +7596,27 @@ using is_movable_container_rvalue = negation, has_dedicated_to_json, is_basic_json, - is_range_view_type, is_enum_keyed_map_type, std::is_same>>>; -// rvalue array-like containers +// rvalue array-like containers; object types are ruled out before views are +// checked, as std::ranges::view for the object_t of an ordered_json would +// need that ordered_json to be complete (GCC 11, see #3312) template using is_rvalue_array_to_json = conjunction, + negation>, + negation>, is_compatible_array_type, negation, std::is_same, - is_compatible_object_type, is_compatible_string_type, is_compatible_binary_type>>>; // rvalue containers: their elements are moved instead of copied template::value, int> = 0> -inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) +inline void to_json(BasicJsonType& j, CompatibleArrayType && arr) // NOLINT(cppcoreguidelines-missing-std-forward) { external_constructor::construct_moving(j, arr); } @@ -7665,7 +7694,8 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map) } #endif -// rvalue object-like containers +// rvalue object-like containers (views have no key_type and mapped_type, so +// they are no compatible object type) template using is_rvalue_object_to_json = conjunction, negation>, @@ -7673,7 +7703,7 @@ using is_rvalue_object_to_json = conjunction, template::value, int> = 0> -inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) +inline void to_json(BasicJsonType& j, CompatibleObjectType && obj) // NOLINT(cppcoreguidelines-missing-std-forward) { external_constructor::construct_moving(j, obj); } @@ -21277,7 +21307,7 @@ class json_pointer { break; // not a plain number: let strtoull decide } - acc = static_cast(acc * 10 + static_cast(c - '0')); + acc = static_cast((acc * 10) + static_cast(c - '0')); if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1)) { idx = acc; @@ -26454,7 +26484,7 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast(shift)); std::uint64_t integral = p.high >> static_cast(extra_shift); // (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses) - constexpr unsigned fractional_low_shift = static_cast(extra_shift); + constexpr auto fractional_low_shift = static_cast(extra_shift); constexpr unsigned fractional_high_shift = 64u - fractional_low_shift; const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift); std::uint64_t digit = 0; @@ -30788,26 +30818,20 @@ private: // one level deep); stops at the first structured child it meets static bool all_children_leaves(const basic_json& v) noexcept { + // array_t only needs forward iterators (see custom_array_type) if (v.m_data.m_type == value_t::array) { const auto& a = *v.m_data.m_value.array; - for (auto it = a.rbegin(); it != a.rend(); ++it) + return std::all_of(a.begin(), a.end(), [](const basic_json & el) { - if (!has_no_children(*it)) - { - return false; - } - } - return true; + return has_no_children(el); + }); } - for (const auto& el : *v.m_data.m_value.object) + const auto& o = *v.m_data.m_value.object; + return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el) { - if (!has_no_children(el.second)) - { - return false; - } - } - return true; + return has_no_children(el.second); + }); } // The walk in destroy_container() may take the children of a @@ -33077,18 +33101,21 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_iterator_range_of_null(const basic_json* obj) { + static_cast(obj); // unused when JSON_NOEXCEPTION is defined JSON_THROW(invalid_iterator::create(detail::exception_id::iterator_range_of_null, detail::concat("cannot construct with iterators from ", obj->type_name()), obj)); } JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_incompatible_reference_type(const basic_json& obj) { + static_cast(obj); // unused when JSON_NOEXCEPTION is defined JSON_THROW(type_error::create(detail::exception_id::incompatible_reference_type, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); } JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_exceeds_size_type(size_type idx) { + static_cast(idx); // unused when JSON_NOEXCEPTION is defined JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); } @@ -33096,6 +33123,8 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_out_of_range(size_type idx, const basic_json& j) { + static_cast(idx); // unused when JSON_NOEXCEPTION is defined + static_cast(j); JSON_THROW(out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat("array index ", std::to_string(idx), " is out of range"), &j)); } @@ -33104,6 +33133,8 @@ public: JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_key_not_found(const KeyType& key, const basic_json& j) { + static_cast(key); // unused when JSON_NOEXCEPTION is defined + static_cast(j); JSON_THROW(out_of_range::create(detail::exception_id::key_not_found, detail::concat("key '", key, "' not found"), &j)); } @@ -35487,7 +35518,7 @@ public: // inline fast path for two values of the same non-owning scalar type; anything // else falls through to the out-of-line general comparison #define JSON_IMPLEMENT_SCALAR_FAST(op, null_result) \ - if (lhs.m_data.m_type == rhs.m_data.m_type) \ + if (lhs.m_data.m_type == rhs.m_data.m_type) /* NOLINT(readability/braces) */ \ { \ switch (lhs.m_data.m_type) \ { \ diff --git a/tests/src/unit-constructor2.cpp b/tests/src/unit-constructor2.cpp index 4896bb1f0..5f5a5df9a 100644 --- a/tests/src/unit-constructor2.cpp +++ b/tests/src/unit-constructor2.cpp @@ -300,7 +300,7 @@ TEST_CASE("copy constructor and destructor across value types") REQUIRE(v.size() == 70); std::vector copy(v); - CHECK(copy == v); + CHECK((copy == v)); // mutating the copy leaves the original alone copy[5] = "changed"; diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 39c4f790c..46444f3f2 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -1234,7 +1234,7 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics") const std::string alphabet = "0123456789012345678901234567890123456789-+ ex"; for (int i = 0; i < 5000; ++i) { - std::string token(static_cast(gen() % 23), '0'); + std::string token(gen() % 23, '0'); for (auto& c : token) { c = alphabet[gen() % alphabet.size()]; diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 4913c30a6..3315d5852 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -1489,7 +1489,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference") { for (const auto& pattern : patterns) { - check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8)); + check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17u : 8u)); } } } diff --git a/tests/src/unit-throw_messages.cpp b/tests/src/unit-throw_messages.cpp index 1c2289626..5babe2901 100644 --- a/tests/src/unit-throw_messages.cpp +++ b/tests/src/unit-throw_messages.cpp @@ -105,12 +105,18 @@ TEST_CASE("exception messages of out-of-line throw sites") } catch (const json::exception& e) { - // with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it + // with JSON_DIAGNOSTICS, what() names the offending value like "(/0) ", + // and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both std::string msg = e.what(); - const auto pos = msg.find(" (/"); - if (pos != std::string::npos) + for (const char* marker : + {" (/", " (bytes " + }) { - msg.erase(pos + 1, msg.find(") ", pos) - pos + 1); + const auto pos = msg.find(marker); + if (pos != std::string::npos) + { + msg.erase(pos + 1, msg.find(") ", pos) - pos + 1); + } } return msg; }