From c314cf8bd401289ecce0389dcb199303b59defec Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 22:25:47 +0200 Subject: [PATCH 1/3] Fix the indentation of the destroy comment in json.hpp (#5857) The comment block about destroying the tree without recursion was indented one level too deep since #5835. make amalgamate reformatted it with astyle, leaving an unrelated diff in every branch. single_include is unchanged. Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 2ca6b2d3a..fe45dcc2a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -943,30 +943,30 @@ public: free_container(v); } - // Destroy the tree below top without recursing per nesting level and - // without any heap allocation: a heap-allocated flattening - // stack (the previous implementation) can itself throw - // bad_alloc, which would escape this noexcept destructor and - // terminate the program (#5135). - // - // Instead, walk down a chain of children (always the one - // walk_child() picks), reversing links as we go: cur is the - // container currently being emptied, and prev is its parent - // (value_t::null when there is none). Each parent's - // walk_child() slot doubles as storage for that parent's own - // parent link while we are below it, so no extra memory is - // needed; the parent is not modified meanwhile, so - // walk_child() finds that same slot again on the way up. We - // only ever remove a child once it is a scalar or an empty - // array/object, which neither allocates nor recurses more - // than one level deep. - // - // This json_value is not itself a basic_json, so the - // top-level container is first moved into a local stand-in - // ("cur"); a default-constructed basic_json has a null - // pointer in its m_value (see data::m_value's initializer), - // so swapping it with *this leaves this union's own pointer - // null, and it is never looked at or freed a second time. + // Destroy the tree below top without recursing per nesting level and + // without any heap allocation: a heap-allocated flattening + // stack (the previous implementation) can itself throw + // bad_alloc, which would escape this noexcept destructor and + // terminate the program (#5135). + // + // Instead, walk down a chain of children (always the one + // walk_child() picks), reversing links as we go: cur is the + // container currently being emptied, and prev is its parent + // (value_t::null when there is none). Each parent's + // walk_child() slot doubles as storage for that parent's own + // parent link while we are below it, so no extra memory is + // needed; the parent is not modified meanwhile, so + // walk_child() finds that same slot again on the way up. We + // only ever remove a child once it is a scalar or an empty + // array/object, which neither allocates nor recurses more + // than one level deep. + // + // This json_value is not itself a basic_json, so the + // top-level container is first moved into a local stand-in + // ("cur"); a default-constructed basic_json has a null + // pointer in its m_value (see data::m_value's initializer), + // so swapping it with *this leaves this union's own pointer + // null, and it is never looked at or freed a second time. static void destroy_walk(basic_json& top) noexcept { basic_json cur; From 4da73dc5ff3dfe97832e10e3a25ab5e950586338 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 22:42:46 +0200 Subject: [PATCH 2/3] Fix compiler warnings on develop from the API performance stack (#5858) These PRs were merged while their CI runs were cancelled, so the warnings went unnoticed. All of them fail the -Werror builds: - json_pointer.hpp (#5836): size_t += int under GCC -Wsign-conversion - binary_reader.hpp (#5839): char_int_type passed as char under GCC -Wconversion - json.hpp (#5838): @throw without a description under clang -Wdocumentation - string_escape.hpp (#5836): escape_into() was inserted between escape() and its documentation, so the @return was attached to a void function - unit-diagnostics.cpp (#5832): clang -Wnrvo in a lambda without an explicit return type - unit-to_json-rvalue.cpp (#5837): GCC -Wnoexcept (C++20) and clang -Wunneeded-member-function on the test type Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 2 +- include/nlohmann/detail/json_pointer.hpp | 2 +- include/nlohmann/detail/string_escape.hpp | 25 +++++++------- include/nlohmann/json.hpp | 4 +-- single_include/nlohmann/json.hpp | 33 ++++++++++--------- tests/src/unit-diagnostics.cpp | 2 +- tests/src/unit-to_json-rvalue.cpp | 11 ++----- 7 files changed, 38 insertions(+), 41 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index a584ee5e3..fef2c0f93 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -4272,7 +4272,7 @@ class binary_reader } JSON_HEDLEY_NEVER_INLINE - bool bson_unsupported_type_error(const std::size_t position, const char element_type) const + bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const { const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(position, cr_str, diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index a752deb08..e5fb3750b 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -1077,7 +1077,7 @@ class json_pointer std::size_t token_count = 0; for (std::size_t i = 0; i < size; ++i) { - token_count += (reference_string[i] == '/') ? 1 : 0; + token_count += static_cast(reference_string[i] == '/'); } result.reserve(token_count); diff --git a/include/nlohmann/detail/string_escape.hpp b/include/nlohmann/detail/string_escape.hpp index b3308b5c3..1a4b6c501 100644 --- a/include/nlohmann/detail/string_escape.hpp +++ b/include/nlohmann/detail/string_escape.hpp @@ -17,18 +17,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -/*! - * @brief string escaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to escape - * @return escaped string - * - * Note the order of escaping "~" to "~0" and "/" to "~1" is important. - * - * The string is rebuilt in a single pass, appending whole runs between the - * characters that need escaping, while repeated replace() calls would move - * the tail of the string once per escaped character. @ref escape_into appends - * the escaped string to an existing one. - */ +/// append @a s to @a out, escaped like @ref escape does template inline void escape_into(StringType& out, const StringType& s) { @@ -47,6 +36,18 @@ inline void escape_into(StringType& out, const StringType& s) out.append(s.data() + run, size - run); } +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + * + * The string is rebuilt in a single pass, appending whole runs between the + * characters that need escaping, while repeated replace() calls would move + * the tail of the string once per escaped character. @ref escape_into appends + * the escaped string to an existing one. + */ template inline StringType escape(const StringType& s) { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index fe45dcc2a..366a8beed 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3058,14 +3058,14 @@ public: JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); } - /// @throw out_of_range.401 + /// @throw out_of_range.401 always, as @a idx is out of range of @a j JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_out_of_range(size_type idx, const basic_json& 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)); } - /// @throw out_of_range.403 + /// @throw out_of_range.403 always, as @a key is not found in @a j template JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_key_not_found(const KeyType& key, const basic_json& j) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 429199ae3..f44422cd6 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3452,18 +3452,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -/*! - * @brief string escaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to escape - * @return escaped string - * - * Note the order of escaping "~" to "~0" and "/" to "~1" is important. - * - * The string is rebuilt in a single pass, appending whole runs between the - * characters that need escaping, while repeated replace() calls would move - * the tail of the string once per escaped character. @ref escape_into appends - * the escaped string to an existing one. - */ +/// append @a s to @a out, escaped like @ref escape does template inline void escape_into(StringType& out, const StringType& s) { @@ -3482,6 +3471,18 @@ inline void escape_into(StringType& out, const StringType& s) out.append(s.data() + run, size - run); } +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + * + * The string is rebuilt in a single pass, appending whole runs between the + * characters that need escaping, while repeated replace() calls would move + * the tail of the string once per escaped character. @ref escape_into appends + * the escaped string to an existing one. + */ template inline StringType escape(const StringType& s) { @@ -19066,7 +19067,7 @@ class binary_reader } JSON_HEDLEY_NEVER_INLINE - bool bson_unsupported_type_error(const std::size_t position, const char element_type) const + bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const { const std::string cr_str = hex_byte(static_cast(element_type)); return sax->parse_error(position, cr_str, @@ -22019,7 +22020,7 @@ class json_pointer std::size_t token_count = 0; for (std::size_t i = 0; i < size; ++i) { - token_count += (reference_string[i] == '/') ? 1 : 0; + token_count += static_cast(reference_string[i] == '/'); } result.reserve(token_count); @@ -33091,14 +33092,14 @@ public: JSON_THROW(std::length_error(detail::concat("array index ", std::to_string(idx), " exceeds size_type"))); } - /// @throw out_of_range.401 + /// @throw out_of_range.401 always, as @a idx is out of range of @a j JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_array_index_out_of_range(size_type idx, const basic_json& 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)); } - /// @throw out_of_range.403 + /// @throw out_of_range.403 always, as @a key is not found in @a j template JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_key_not_found(const KeyType& key, const basic_json& j) diff --git a/tests/src/unit-diagnostics.cpp b/tests/src/unit-diagnostics.cpp index f791509b4..1d7b45a97 100644 --- a/tests/src/unit-diagnostics.cpp +++ b/tests/src/unit-diagnostics.cpp @@ -733,7 +733,7 @@ TEST_CASE_TEMPLATE("Parent pointers stay consistent", J, json, ordered_json) TEST_CASE_TEMPLATE("update() and merge_patch() keep parent pointers and diagnostic paths", J, json, ordered_json) { // enough keys to make the vector behind ordered_json reallocate several times - const auto make_object = [](int first, int last) + const auto make_object = [](int first, int last) -> J { J o = J::object(); for (int i = first; i < last; ++i) diff --git a/tests/src/unit-to_json-rvalue.cpp b/tests/src/unit-to_json-rvalue.cpp index 261493f7a..4aabda5a1 100644 --- a/tests/src/unit-to_json-rvalue.cpp +++ b/tests/src/unit-to_json-rvalue.cpp @@ -39,20 +39,15 @@ struct tracked std::string payload; bool moved_from = false; - explicit tracked(std::string p) : payload(std::move(p)) {} + explicit tracked(std::string p) noexcept : payload(std::move(p)) {} tracked(const tracked&) = default; tracked& operator=(const tracked&) = default; tracked(tracked&& other) noexcept : payload(std::move(other.payload)), moved_from(other.moved_from) { other.moved_from = true; } - tracked& operator=(tracked&& other) noexcept - { - payload = std::move(other.payload); - moved_from = other.moved_from; - other.moved_from = true; - return *this; - } + // not needed by the tests (and an unused definition fails -Wunneeded-member-function) + tracked& operator=(tracked&&) = delete; ~tracked() = default; }; From 5683f5e024549ee95cabf0298602ff142186408f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 22:45:56 +0200 Subject: [PATCH 3/3] Define less_general() only without three-way comparison (#5859) less_general() (#5840) calls operator<(value_t, value_t), which exists in C++20 only for GCC. Explicit instantiation of basic_json compiles every member, so clang-cl and MSVC failed on unit-explicit_instantiation and unit-regression2 in C++20 mode. Its only caller, operator<, is defined only without three-way comparison, as it was before #5840. Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 20 ++++++++++---------- single_include/nlohmann/json.hpp | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 366a8beed..37447262e 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -5518,16 +5518,6 @@ public: #endif } - JSON_HEDLEY_NEVER_INLINE - static bool less_general(const_reference lhs, const_reference rhs) noexcept - { - // default_result is used if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type), - compare_iteratively(lhs, rhs, true) == compare_result::less, false) - } - #if JSON_HAS_THREE_WAY_COMPARISON JSON_HEDLEY_NEVER_INLINE static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD* @@ -5540,6 +5530,16 @@ public: lhs_type <=> rhs_type, // *NOPAD* to_partial_ordering(compare_iteratively(lhs, rhs, false)), true) } +#else + JSON_HEDLEY_NEVER_INLINE + static bool less_general(const_reference lhs, const_reference rhs) noexcept + { + // default_result is used if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type), + compare_iteratively(lhs, rhs, true) == compare_result::less, false) + } #endif /// @brief compare two leaves of the same type inside equal_structured() diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f44422cd6..bcc8e5831 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -35552,16 +35552,6 @@ public: #endif } - JSON_HEDLEY_NEVER_INLINE - static bool less_general(const_reference lhs, const_reference rhs) noexcept - { - // default_result is used if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type), - compare_iteratively(lhs, rhs, true) == compare_result::less, false) - } - #if JSON_HAS_THREE_WAY_COMPARISON JSON_HEDLEY_NEVER_INLINE static std::partial_ordering three_way_general(const_reference lhs, const_reference rhs) noexcept // *NOPAD* @@ -35574,6 +35564,16 @@ public: lhs_type <=> rhs_type, // *NOPAD* to_partial_ordering(compare_iteratively(lhs, rhs, false)), true) } +#else + JSON_HEDLEY_NEVER_INLINE + static bool less_general(const_reference lhs, const_reference rhs) noexcept + { + // default_result is used if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type), + compare_iteratively(lhs, rhs, true) == compare_result::less, false) + } #endif /// @brief compare two leaves of the same type inside equal_structured()