From a4e09fa5b743b92c570c0ce9ce53954fc190b7e9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 31 Aug 2026 19:52:26 +0000 Subject: [PATCH] Diagnose two silently violated template parameter requirements Both were on the list of requirements that are not caught at compile time and corrupt values rather than failing, and both are a plain size comparison: - A BinaryType whose value_type is wider than one byte, which the readers and writers reinterpret as raw bytes anyway. - A NumberUnsignedType too narrow to hold the absolute value of every NumberIntegerType value, which makes basic_json(INT64_MIN).dump() yield -0 for std::int64_t with std::uint32_t. Neither static_assert rejects a configuration that worked before: both only fire where the result was already wrong. Also add the two comments the review asked for, in write_bson_string() and calc_bson_array_size(), matching the ones their counterparts already carry. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/output/binary_writer.hpp | 5 +++++ include/nlohmann/json.hpp | 12 ++++++++++++ single_include/nlohmann/json.hpp | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index a388e5d61..c059c2602 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -1060,6 +1060,8 @@ class binary_writer oa->write_characters( reinterpret_cast(value.data()), value.size()); + // the terminating null byte is written explicitly rather than taken + // from the buffer, so that string_t::data() need not be null-terminated oa->write_character(to_char_type(0x00)); } @@ -1151,6 +1153,9 @@ class binary_writer const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) { + // the index is built as a std::string, while calc_bson_element_size + // takes a string_t; convert explicitly, as the two are only + // implicitly convertible for some string types const auto key = std::to_string(array_index++); return result + calc_bson_element_size(string_t(key.data(), key.size()), el); }); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 81c309e63..00ad4f7b9 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -403,6 +403,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} + // Two template parameter requirements that would otherwise be silently + // violated: neither produces a diagnostic of its own, and both corrupt + // values rather than failing. + + static_assert(sizeof(typename BinaryType::value_type) == 1, + "BinaryType::value_type must be exactly one byte wide, " + "because the binary readers and writers reinterpret the container's storage as raw bytes"); + + static_assert(sizeof(NumberUnsignedType) >= sizeof(NumberIntegerType), + "NumberUnsignedType must be at least as wide as NumberIntegerType, " + "because it has to hold the absolute value of every NumberIntegerType value"); + private: /// helper for exception-safe object creation diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 32825843e..4696a43a7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18109,6 +18109,8 @@ class binary_writer oa->write_characters( reinterpret_cast(value.data()), value.size()); + // the terminating null byte is written explicitly rather than taken + // from the buffer, so that string_t::data() need not be null-terminated oa->write_character(to_char_type(0x00)); } @@ -18200,6 +18202,9 @@ class binary_writer const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) { + // the index is built as a std::string, while calc_bson_element_size + // takes a string_t; convert explicitly, as the two are only + // implicitly convertible for some string types const auto key = std::to_string(array_index++); return result + calc_bson_element_size(string_t(key.data(), key.size()), el); }); @@ -21886,6 +21891,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} + // Two template parameter requirements that would otherwise be silently + // violated: neither produces a diagnostic of its own, and both corrupt + // values rather than failing. + + static_assert(sizeof(typename BinaryType::value_type) == 1, + "BinaryType::value_type must be exactly one byte wide, " + "because the binary readers and writers reinterpret the container's storage as raw bytes"); + + static_assert(sizeof(NumberUnsignedType) >= sizeof(NumberIntegerType), + "NumberUnsignedType must be at least as wide as NumberIntegerType, " + "because it has to hold the absolute value of every NumberIntegerType value"); + private: /// helper for exception-safe object creation