From 94d77f8d8df369fafe3c92185cac69aa99e56dba Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 07:35:54 +0200 Subject: [PATCH 1/9] Fix CI failures on develop from the API performance stack - Mark the parameters of the new out-of-line throw helpers in json.hpp as used, as JSON_THROW drops its argument with JSON_NOEXCEPTION (clang -Wunused-parameter). - all_children_leaves() iterates with begin()/end() via std::all_of: a custom array_t such as the one in the custom_array_type example has no rbegin() (examples check, clang-tidy readability-use-anyofallof). - The rvalue to_json overloads (#5837) copy the elements of containers with const elements such as std::set instead of moving them: GCC 4.9's std::move_iterator cannot bind them. Object types are ruled out before std::ranges::view is checked, which needs an incomplete ordered_json for its object_t (GCC 11, C++20, unit-regression3 #3312). - NOLINT the rvalue to_json parameters that are deliberately not forwarded, and fix the other clang-tidy findings (modernize-use-auto in zmij.hpp, readability-math-missing-parentheses in json_pointer.hpp). - cpplint: NOLINT(readability/braces) on the new scalar fast-path macro like on JSON_IMPLEMENT_OPERATOR, and include in json.hpp. - Tests: drop a useless cast (GCC -Wuseless-cast) and a signed/unsigned comparison (MSVC C4018), wrap a vector comparison in parentheses so doctest does not try to print it (ambiguous operator<< on MSVC), and also strip JSON_DIAGNOSTIC_POSITIONS' "(bytes x-y) " from the patch messages in unit-throw_messages. - Windows clang (MinGW): link with LLD. unit-msgpack.cpp (C++11) and unit-regression2.cpp (C++20) have more than 32767 sections again (33363 and 33197), which GNU ld cannot link; LLD links them. Signed-off-by: Niels Lohmann --- .github/workflows/windows.yml | 18 ++-- .../nlohmann/detail/conversions/to_json.hpp | 47 +++++++++-- include/nlohmann/detail/conversions/zmij.hpp | 2 +- include/nlohmann/detail/json_pointer.hpp | 2 +- include/nlohmann/json.hpp | 32 +++---- single_include/nlohmann/json.hpp | 83 +++++++++++++------ tests/src/unit-constructor2.cpp | 2 +- tests/src/unit-json_pointer.cpp | 2 +- tests/src/unit-serialization.cpp | 2 +- tests/src/unit-throw_messages.cpp | 14 +++- 10 files changed, 137 insertions(+), 67 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bbc1ef48b8..6f1cf8caec 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 5b217d7fe1..020115531a 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 3820d891af..3b2e8e7a3e 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 e5fb3750b2..b19ec5757b 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 37447262e2..bc6988a20b 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // 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 bcc8e58319..3dcebc8812 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec 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 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // 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 4896bb1f02..5f5a5df9a5 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 39c4f790cd..46444f3f26 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 4913c30a6e..3315d58520 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 1c2289626b..5babe29014 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; } From ac17aad589515215e9f963fd7de72c652ef38c12 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 07:54:13 +0200 Subject: [PATCH 2/9] Fix the develop CI failures hidden behind cancelled jobs - ci_test_noexceptions: the JSON Patch section of unit-throw_messages and the strict dumps in unit-serialization catch exceptions, which JSON_NOEXCEPTION turns into std::abort(); skip them there. - ci_cppcheck: insert() and iter_impl::key() ended with a call to a noreturn throw helper, which Cppcheck reports as missingReturn; check the error case first and end with the return, like the other functions. - ci_single_binaries: ordered_map.hpp uses the Hedley macros since #5838, so include hedley.hpp (IWYU). - MSVC: also wrap the second vector comparison in unit-constructor2 in parentheses. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/iterators/iter_impl.hpp | 6 +- include/nlohmann/json.hpp | 58 ++++++++-------- include/nlohmann/ordered_map.hpp | 1 + single_include/nlohmann/json.hpp | 66 ++++++++++--------- tests/src/unit-constructor2.cpp | 2 +- tests/src/unit-serialization.cpp | 6 ++ tests/src/unit-throw_messages.cpp | 2 + 7 files changed, 76 insertions(+), 65 deletions(-) diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index b7c1df24a8..120c1b8937 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -680,12 +680,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - if (JSON_HEDLEY_LIKELY(m_object->is_object())) + if (JSON_HEDLEY_UNLIKELY(!m_object->is_object())) { - return m_it.object_iterator->first; + throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); } - throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); + return m_it.object_iterator->first; } /*! diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bc6988a20b..a05071e9d5 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4895,19 +4895,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // insert to array and return iterator - return insert_iterator(pos, val); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // insert to array and return iterator + return insert_iterator(pos, val); } /// @brief inserts element into array @@ -4915,21 +4915,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // moving into a local first keeps this safe even if val aliases - // an element of this array - basic_json tmp(std::move(val)); - return insert_iterator(pos, std::move(tmp)); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // moving into a local first keeps this safe even if val aliases + // an element of this array + basic_json tmp(std::move(val)); + return insert_iterator(pos, std::move(tmp)); } /// @brief inserts copies of element into array @@ -4937,19 +4937,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // insert to array and return iterator - return insert_iterator(pos, cnt, val); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // insert to array and return iterator + return insert_iterator(pos, cnt, val); } /// @brief inserts range of elements into array diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp index 10fc764ea5..e5f9164f6d 100644 --- a/include/nlohmann/ordered_map.hpp +++ b/include/nlohmann/ordered_map.hpp @@ -23,6 +23,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3dcebc8812..cae524061a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20745,12 +20745,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci { JSON_ASSERT(m_object != nullptr); - if (JSON_HEDLEY_LIKELY(m_object->is_object())) + if (JSON_HEDLEY_UNLIKELY(!m_object->is_object())) { - return m_it.object_iterator->first; + throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); } - throw_literal(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object); + return m_it.object_iterator->first; } /*! @@ -29734,6 +29734,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -34958,19 +34960,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // insert to array and return iterator - return insert_iterator(pos, val); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // insert to array and return iterator + return insert_iterator(pos, val); } /// @brief inserts element into array @@ -34978,21 +34980,21 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // moving into a local first keeps this safe even if val aliases - // an element of this array - basic_json tmp(std::move(val)); - return insert_iterator(pos, std::move(tmp)); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // moving into a local first keeps this safe even if val aliases + // an element of this array + basic_json tmp(std::move(val)); + return insert_iterator(pos, std::move(tmp)); } /// @brief inserts copies of element into array @@ -35000,19 +35002,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) + if (JSON_HEDLEY_UNLIKELY(!is_array())) { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); - } + detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + } - // insert to array and return iterator - return insert_iterator(pos, cnt, val); + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + detail::throw_literal(detail::exception_id::iterator_from_other_value, "iterator does not fit current value", this); } - detail::throw_cannot_use_with(detail::exception_id::insert_wrong_type, "insert()", *this); + // insert to array and return iterator + return insert_iterator(pos, cnt, val); } /// @brief inserts range of elements into array diff --git a/tests/src/unit-constructor2.cpp b/tests/src/unit-constructor2.cpp index 5f5a5df9a5..9b2fb3df60 100644 --- a/tests/src/unit-constructor2.cpp +++ b/tests/src/unit-constructor2.cpp @@ -305,7 +305,7 @@ TEST_CASE("copy constructor and destructor across value types") // mutating the copy leaves the original alone copy[5] = "changed"; copy[6].push_back(1); - CHECK(copy != v); + CHECK((copy != v)); CHECK(v[5] == std::string(5, 'x')); CHECK(v[6].size() == 3); diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 3315d58520..2ad4f2eda8 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -1374,7 +1374,13 @@ void check_dump_against_reference(const std::string& s, const bool with_strict = }; for (const auto error_handler : error_handlers) { +#if defined(JSON_NOEXCEPTION) + // a strict dump of invalid UTF-8 calls std::abort() instead of throwing + static_cast(with_strict); + if (error_handler == json::error_handler_t::strict) +#else if (!with_strict && error_handler == json::error_handler_t::strict) +#endif { continue; } diff --git a/tests/src/unit-throw_messages.cpp b/tests/src/unit-throw_messages.cpp index 5babe29014..f9f8a54451 100644 --- a/tests/src/unit-throw_messages.cpp +++ b/tests/src/unit-throw_messages.cpp @@ -94,6 +94,7 @@ TEST_CASE("exception messages of out-of-line throw sites") CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit"); } +#if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort() SECTION("patch") { const auto patch_msg = [](const char* doc, const char* patch) @@ -138,4 +139,5 @@ TEST_CASE("exception messages of out-of-line throw sites") CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}"); CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'"); } +#endif } From e5b8f453e5373adf2be63582fbd060dca6d941c5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 08:31:07 +0200 Subject: [PATCH 3/9] Fix MSVC 2015 failures on AppVeyor - object_capacity() (#5832) overloaded a static with a non-static member function template, which MSVC 2015 rejects (C2686); make all three overloads non-static. - The JSON pointer hash constants (#5836) truncate on 32-bit size_t (C4309 with /WX); mask them to size_t's range before the cast. - unit-diagnostics: MSVC 2015 cannot use-declare the base's Container typedef (C2886); use alias declarations instead. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 4 ++-- include/nlohmann/json.hpp | 4 ++-- single_include/nlohmann/json.hpp | 8 ++++---- tests/src/unit-diagnostics.cpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index b19ec5757b..232b49d24c 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -529,10 +529,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); } return h ^ (h >> 29u); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index a05071e9d5..595fe11e5a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1225,7 +1225,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements::value, int > = 0 > - static constexpr std::size_t object_capacity() noexcept + std::size_t object_capacity() const noexcept { return 0; } @@ -1237,7 +1237,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements::value && !detail::is_nlohmann_ordered_map::value, int > = 0 > - static constexpr std::size_t object_capacity() noexcept + std::size_t object_capacity() const noexcept { return detail::unknown_size(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index cae524061a..68d6b5becf 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21502,10 +21502,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); } return h ^ (h >> 29u); } @@ -31290,7 +31290,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } template < typename O = object_t, detail::enable_if_t < detail::has_stable_elements::value, int > = 0 > - static constexpr std::size_t object_capacity() noexcept + std::size_t object_capacity() const noexcept { return 0; } @@ -31302,7 +31302,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } template < typename O = object_t, detail::enable_if_t < !detail::has_stable_elements::value && !detail::is_nlohmann_ordered_map::value, int > = 0 > - static constexpr std::size_t object_capacity() noexcept + std::size_t object_capacity() const noexcept { return detail::unknown_size(); } diff --git a/tests/src/unit-diagnostics.cpp b/tests/src/unit-diagnostics.cpp index 1d7b45a973..504a953224 100644 --- a/tests/src/unit-diagnostics.cpp +++ b/tests/src/unit-diagnostics.cpp @@ -817,8 +817,8 @@ template, struct relocating_map : nlohmann::ordered_map { using base_type = nlohmann::ordered_map; - using typename base_type::iterator; - using typename base_type::Container; + using iterator = typename base_type::iterator; + using Container = typename base_type::Container; using base_type::base_type; template From a249c51b55c2c0331b4a23a823617270b19ed36f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 09:05:09 +0200 Subject: [PATCH 4/9] Fix the tests for disabled implicit conversions and strict NUL handling - unit-iterators1 (#5835) converted a json element to int implicitly, which does not compile with JSON_USE_IMPLICIT_CONVERSIONS=0. - unit-testsuites parses a number followed by a NUL byte, which JSON_STRICT_NUL_HANDLING rejects; leave the NUL out in strict mode. Signed-off-by: Niels Lohmann --- tests/src/unit-iterators1.cpp | 2 +- tests/src/unit-testsuites.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-iterators1.cpp b/tests/src/unit-iterators1.cpp index 629c6abd72..1fceaf8ff8 100644 --- a/tests/src/unit-iterators1.cpp +++ b/tests/src/unit-iterators1.cpp @@ -1661,7 +1661,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, seen.clear(); for (auto r = j.crbegin(); r != j.crend(); ++r) { - seen.push_back(*r); + seen.push_back(r->template get()); } CHECK(seen == std::vector {4, 3, 2, 1}); } diff --git a/tests/src/unit-testsuites.cpp b/tests/src/unit-testsuites.cpp index 6729819675..04df384a8a 100644 --- a/tests/src/unit-testsuites.cpp +++ b/tests/src/unit-testsuites.cpp @@ -218,7 +218,11 @@ TEST_CASE("compliance tests from nativejson-benchmark") n1e308[0] = '['; n1e308[1] = '1'; n1e308[310] = ']'; +#if JSON_STRICT_NUL_HANDLING + n1e308.resize(311); // a NUL byte after the value is a syntax error in strict mode +#else n1e308[311] = '\0'; +#endif TEST_DOUBLE(n1e308, 1E308); } From 8d597357c49af7f4b7431ff419bad68f6f7805a6 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 09:48:31 +0200 Subject: [PATCH 5/9] Use std::uint64_t instead of unsigned long long for cpplint Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 6 ++++-- single_include/nlohmann/json.hpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 232b49d24c..41d5acbdec 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -11,6 +11,8 @@ #include // all_of #include // isdigit #include // errno, ERANGE +#include // size_t +#include // uint64_t #include // strtoull #ifndef JSON_NO_IO #include // ostream @@ -529,10 +531,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); } return h ^ (h >> 29u); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 68d6b5becf..2b5a20f77a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20977,6 +20977,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // all_of #include // isdigit #include // errno, ERANGE +#include // size_t +#include // uint64_t #include // strtoull #ifndef JSON_NO_IO #include // ostream @@ -21502,10 +21504,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); } return h ^ (h >> 29u); } From c759a73cedf4566e537506e4e4aed4403970724d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 09:55:34 +0200 Subject: [PATCH 6/9] Mask the JSON pointer hash constants without a cast to uint64_t The cast is useless where size_t is uint64_t (GCC -Wuseless-cast); the mask alone already keeps the constants in size_t's range. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/json_pointer.hpp | 5 ++--- single_include/nlohmann/json.hpp | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 41d5acbdec..63ab49f7d5 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -12,7 +12,6 @@ #include // isdigit #include // errno, ERANGE #include // size_t -#include // uint64_t #include // strtoull #ifndef JSON_NO_IO #include // ostream @@ -531,10 +530,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & (std::numeric_limits::max)()); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & (std::numeric_limits::max)()); } return h ^ (h >> 29u); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2b5a20f77a..0db7d067ab 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20978,7 +20978,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // isdigit #include // errno, ERANGE #include // size_t -#include // uint64_t #include // strtoull #ifndef JSON_NO_IO #include // ostream @@ -21504,10 +21503,10 @@ class json_pointer static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & static_cast((std::numeric_limits::max)())); + std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & (std::numeric_limits::max)()); for (std::size_t i = 0; i < token.size(); ++i) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & static_cast((std::numeric_limits::max)())); + h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & (std::numeric_limits::max)()); } return h ^ (h >> 29u); } From cb9634301ef10c32a5892095760ea66002c70207 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 11:04:30 +0200 Subject: [PATCH 7/9] Fix the clang-tidy findings in the tests and headers of the API stack - make_move_iterator_if_mutable takes the iterator by const reference (performance-unnecessary-value-param). - throw_patch_missing_member builds its message without a local that is unused with JSON_NOEXCEPTION (bugprone-unused-local-non-trivial-variable). - Tests: NOLINT the copies that test the copy constructor and the constant mt19937 seed, NOLINT the deliberate "\xC3" "a" literal concatenation, use references for the const views in unit-throw_messages and return {} from its lambda. A full clang-tidy 22 run over all 107 test translation units is clean. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/conversions/to_json.hpp | 6 +++--- include/nlohmann/detail/exceptions.hpp | 3 +-- single_include/nlohmann/json.hpp | 9 ++++----- tests/src/unit-constructor2.cpp | 10 +++++----- tests/src/unit-json_pointer.cpp | 2 +- tests/src/unit-serialization.cpp | 2 +- tests/src/unit-throw_messages.cpp | 15 ++++++++------- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 020115531a..05ae823704 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -52,13 +52,13 @@ namespace detail // 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*/) +std::move_iterator make_move_iterator_if_mutable_impl(const 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*/) +Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/) { return it; } @@ -68,7 +68,7 @@ using is_mutable_iterator = std::integral_constant < bool, !std::is_const())>::type>::value >; template -auto make_move_iterator_if_mutable(Iterator it) +auto make_move_iterator_if_mutable(const Iterator& it) -> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator {})) { return make_move_iterator_if_mutable_impl(it, is_mutable_iterator {}); diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index b2b5f36dd8..7d7f4e038a 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -494,8 +494,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c static_cast(string_type); static_cast(val); // context-sensitive error message - const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\''); - JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val)); } template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0db7d067ab..4debe551fc 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5795,8 +5795,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(c static_cast(string_type); static_cast(val); // context-sensitive error message - const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\''); - JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val)); + JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat((op == "op") ? std::string("operation") : concat("operation '", op, '\''), string_type ? " must have string member '" : " must have member '", member, "'"), &val)); } template @@ -7209,13 +7208,13 @@ namespace detail // 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*/) +std::move_iterator make_move_iterator_if_mutable_impl(const 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*/) +Iterator make_move_iterator_if_mutable_impl(const Iterator& it, std::false_type /*mutable*/) { return it; } @@ -7225,7 +7224,7 @@ using is_mutable_iterator = std::integral_constant < bool, !std::is_const())>::type>::value >; template -auto make_move_iterator_if_mutable(Iterator it) +auto make_move_iterator_if_mutable(const Iterator& it) -> decltype(make_move_iterator_if_mutable_impl(it, is_mutable_iterator {})) { return make_move_iterator_if_mutable_impl(it, is_mutable_iterator {}); diff --git a/tests/src/unit-constructor2.cpp b/tests/src/unit-constructor2.cpp index 9b2fb3df60..f17d0ad3ae 100644 --- a/tests/src/unit-constructor2.cpp +++ b/tests/src/unit-constructor2.cpp @@ -198,11 +198,11 @@ TEST_CASE("copy constructor and destructor across value types") const json src_uint = 7u; const json src_float = 2.5; - json c_null(src_null); - json c_bool(src_bool); - json c_int(src_int); - json c_uint(src_uint); - json c_float(src_float); + json c_null(src_null); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor + json c_bool(src_bool); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor + json c_int(src_int); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor + json c_uint(src_uint); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor + json c_float(src_float); // NOLINT(performance-unnecessary-copy-initialization): tests the copy constructor CHECK(c_null.is_null()); CHECK(c_bool == true); diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 46444f3f26..7748a375ab 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -1230,7 +1230,7 @@ TEST_CASE("JSON pointer array index tokens match strtoull semantics") SECTION("random tokens") { - std::mt19937 gen(42); + std::mt19937 gen(42); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible const std::string alphabet = "0123456789012345678901234567890123456789-+ ex"; for (int i = 0; i < 5000; ++i) { diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 2ad4f2eda8..b6608a49ad 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -1435,7 +1435,7 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference") "\xF4\x90\x80\x80", // sequences cut short by ASCII, a control character, a quote, a // backslash, DEL, or the lead byte of a well-formed character - "\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", + "\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80", // NOLINT(bugprone-suspicious-missing-comma): "\xC3" "a" ends the hex escape // truncated sequences "\xC3", "\xE2\x82", "\xF0\x9F\x98" }; diff --git a/tests/src/unit-throw_messages.cpp b/tests/src/unit-throw_messages.cpp index f9f8a54451..d7546cc93a 100644 --- a/tests/src/unit-throw_messages.cpp +++ b/tests/src/unit-throw_messages.cpp @@ -54,8 +54,8 @@ TEST_CASE("exception messages of out-of-line throw sites") SECTION("at") { - const json carr = arr; - const json cobj = obj; + const json& carr = arr; + const json& cobj = obj; CHECK_MSG((void)arr.at(static_cast(10)), "[json.exception.out_of_range.401] array index 10 is out of range"); CHECK_MSG((void)carr.at(static_cast(3)), "[json.exception.out_of_range.401] array index 3 is out of range"); CHECK_MSG((void)num.at(static_cast(0)), "[json.exception.type_error.304] cannot use at() with number"); @@ -65,7 +65,7 @@ TEST_CASE("exception messages of out-of-line throw sites") CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number"); nlohmann::ordered_json oobj = {{"a", 1}}; - const nlohmann::ordered_json coobj = oobj; + const nlohmann::ordered_json& coobj = oobj; CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found"); CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found"); @@ -97,7 +97,7 @@ TEST_CASE("exception messages of out-of-line throw sites") #if !defined(JSON_NOEXCEPTION) // patch_msg() catches the exceptions, which JSON_NOEXCEPTION turns into std::abort() SECTION("patch") { - const auto patch_msg = [](const char* doc, const char* patch) + const auto patch_msg = [](const char* doc, const char* patch) -> std::string { try { @@ -110,8 +110,9 @@ TEST_CASE("exception messages of out-of-line throw sites") // and with JSON_DIAGNOSTIC_POSITIONS its position like "(bytes 0-2) "; drop both std::string msg = e.what(); for (const char* marker : - {" (/", " (bytes " - }) + { + " (/", " (bytes " + }) { const auto pos = msg.find(marker); if (pos != std::string::npos) @@ -121,7 +122,7 @@ TEST_CASE("exception messages of out-of-line throw sites") } return msg; } - return std::string(); + return {}; }; CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects"); From 845ad8c52bd75f20311a39de7904e6c0386005a9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 13:12:19 +0200 Subject: [PATCH 8/9] Name the expected vector in the unit-iterators1 reverse test for MSVC 2017 MSVC 2017 (/permissive- /std:c++17) rejects the braced std::vector temporary inside CHECK() in this templated test case (C2440). Signed-off-by: Niels Lohmann --- tests/src/unit-iterators1.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/src/unit-iterators1.cpp b/tests/src/unit-iterators1.cpp index 1fceaf8ff8..1f0a6ba4bb 100644 --- a/tests/src/unit-iterators1.cpp +++ b/tests/src/unit-iterators1.cpp @@ -1637,6 +1637,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, { Json j = {1, 2, 3, 4}; + const std::vector reversed = {4, 3, 2, 1}; auto it = j.end(); std::vector seen; while (it != j.begin()) @@ -1644,7 +1645,7 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, --it; seen.push_back(it->template get()); } - CHECK(seen == std::vector {4, 3, 2, 1}); + CHECK(seen == reversed); auto it2 = j.end(); auto old = it2--; @@ -1656,14 +1657,14 @@ TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, { seen.push_back(r->template get()); } - CHECK(seen == std::vector {4, 3, 2, 1}); + CHECK(seen == reversed); seen.clear(); for (auto r = j.crbegin(); r != j.crend(); ++r) { seen.push_back(r->template get()); } - CHECK(seen == std::vector {4, 3, 2, 1}); + CHECK(seen == reversed); } SECTION("object: -- and rbegin/rend") From 291dc5e012dbac7f5d4988337201d0f56a90729c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 17:26:29 +0200 Subject: [PATCH 9/9] Drop the trailing NUL with pop_back() in strict mode Signed-off-by: Niels Lohmann --- tests/src/unit-testsuites.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/unit-testsuites.cpp b/tests/src/unit-testsuites.cpp index 04df384a8a..52d1894572 100644 --- a/tests/src/unit-testsuites.cpp +++ b/tests/src/unit-testsuites.cpp @@ -219,7 +219,7 @@ TEST_CASE("compliance tests from nativejson-benchmark") n1e308[1] = '1'; n1e308[310] = ']'; #if JSON_STRICT_NUL_HANDLING - n1e308.resize(311); // a NUL byte after the value is a syntax error in strict mode + n1e308.pop_back(); // a NUL byte after the value is a syntax error in strict mode #else n1e308[311] = '\0'; #endif