From 7374730aedf18da2b50a778a170d07207d5f39e2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 22 Jul 2026 08:47:24 +0000 Subject: [PATCH] Fix CI failures from binary_writer output-sink change Four CI jobs failed on the initial commit; all are addressed here without changing any output (binary encodings remain byte-for-byte identical to develop across the differential corpus): 1. ci_test_gcc / cuda (-Werror=duplicated-branches): for number_float_t == float, static_cast(n) is the identity, so write_compact_float's two branches are intentionally identical. Once the concrete vector sink is inlined, GCC constant-folds and diagnoses this (the type-erased path hid it behind a non-inlined virtual call). Silence -Wduplicated-branches for GCC (clang has no such warning) alongside the existing -Wfloat-equal pragma. 2. ci_static_analysis_clang (UBSan nonnull-attribute): binary_writer passes a null pointer with length 0 for empty strings/binary. output_vector_sink / output_adapter_sink declared write_characters JSON_HEDLEY_NON_NULL, so the sanitizer flagged the (harmless) zero-length call once the sink was called directly rather than through the attribute-free virtual base. Drop the attribute from both sinks, matching the pre-existing behavior. 3. ci_cpplint (build/include_what_you_use): output_adapter_sink uses std::move; add #include . 4. ci_cuda_example (nvcc 11.8): NVCC's front end rejects the default template argument on the binary_writer alias template. Revert the alias to its original single-parameter form (relying on binary_writer's own defaulted OutputSinkType) and spell out the full type in the vector-sink convenience functions. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XAYM1qhSA2FDaDcGfPW3fG Signed-off-by: Niels Lohmann --- .../nlohmann/detail/output/binary_writer.hpp | 7 +++++ .../detail/output/output_adapters.hpp | 9 ++++-- include/nlohmann/json.hpp | 13 ++++----- single_include/nlohmann/json.hpp | 29 +++++++++++++------ 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index ed9248051..de6bf3349 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -1790,6 +1790,13 @@ class binary_writer #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + // When number_float_t is float, static_cast(n) is the identity and + // both branches below are intentionally identical (the "compact" float + // representation is the value itself). Only GCC diagnoses this, and only + // when the sink calls are inlined; clang has no such warning. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wduplicated-branches" #endif if (!std::isfinite(n) || ((static_cast(n) >= static_cast(std::numeric_limits::lowest()) && static_cast(n) <= static_cast((std::numeric_limits::max)()) && diff --git a/include/nlohmann/detail/output/output_adapters.hpp b/include/nlohmann/detail/output/output_adapters.hpp index e2390e4e5..497bde768 100644 --- a/include/nlohmann/detail/output/output_adapters.hpp +++ b/include/nlohmann/detail/output/output_adapters.hpp @@ -13,6 +13,7 @@ #include // back_inserter #include // shared_ptr, make_shared #include // basic_string +#include // move #include // vector #ifndef JSON_NO_IO @@ -138,7 +139,10 @@ class output_vector_sink v.push_back(c); } - JSON_HEDLEY_NON_NULL(2) + // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null + // pointer with length 0 for empty strings/binary values. Appending an empty + // range is a no-op; the type-erased path tolerates this via the (unattributed) + // virtual base, and the concrete sink must do the same. void write_characters(const CharType* s, std::size_t length) { v.insert(v.end(), s, s + length); @@ -170,7 +174,8 @@ class output_adapter_sink oa->write_character(c); } - JSON_HEDLEY_NON_NULL(2) + // no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as + // the type-erased path already did before this sink existed void write_characters(const CharType* s, std::size_t length) { oa->write_characters(s, length); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3c0043553..551bcdee8 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -186,8 +186,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template using binary_reader = ::nlohmann::detail::binary_reader; - template> - using binary_writer = ::nlohmann::detail::binary_writer; + template using binary_writer = ::nlohmann::detail::binary_writer; JSON_PRIVATE_UNLESS_TESTED: using serializer = ::nlohmann::detail::serializer; @@ -4328,7 +4327,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_cbor(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_cbor(j); return result; } @@ -4352,7 +4351,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_msgpack(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_msgpack(j); return result; } @@ -4378,7 +4377,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool use_type = false) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_ubjson(j, use_size, use_type); return result; } @@ -4407,7 +4406,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_ubjson(j, use_size, use_type, true, true, version); return result; } @@ -4435,7 +4434,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_bson(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_bson(j); return result; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index bc14f8c85..23edd0707 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -16634,6 +16634,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // back_inserter #include // shared_ptr, make_shared #include // basic_string +#include // move #include // vector #ifndef JSON_NO_IO @@ -16760,7 +16761,10 @@ class output_vector_sink v.push_back(c); } - JSON_HEDLEY_NON_NULL(2) + // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null + // pointer with length 0 for empty strings/binary values. Appending an empty + // range is a no-op; the type-erased path tolerates this via the (unattributed) + // virtual base, and the concrete sink must do the same. void write_characters(const CharType* s, std::size_t length) { v.insert(v.end(), s, s + length); @@ -16792,7 +16796,8 @@ class output_adapter_sink oa->write_character(c); } - JSON_HEDLEY_NON_NULL(2) + // no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as + // the type-erased path already did before this sink existed void write_characters(const CharType* s, std::size_t length) { oa->write_characters(s, length); @@ -18599,6 +18604,13 @@ class binary_writer #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + // When number_float_t is float, static_cast(n) is the identity and + // both branches below are intentionally identical (the "compact" float + // representation is the value itself). Only GCC diagnoses this, and only + // when the sink calls are inlined; clang has no such warning. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wduplicated-branches" #endif if (!std::isfinite(n) || ((static_cast(n) >= static_cast(std::numeric_limits::lowest()) && static_cast(n) <= static_cast((std::numeric_limits::max)()) && @@ -21353,8 +21365,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template using binary_reader = ::nlohmann::detail::binary_reader; - template> - using binary_writer = ::nlohmann::detail::binary_writer; + template using binary_writer = ::nlohmann::detail::binary_writer; JSON_PRIVATE_UNLESS_TESTED: using serializer = ::nlohmann::detail::serializer; @@ -25495,7 +25506,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_cbor(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_cbor(j); return result; } @@ -25519,7 +25530,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_msgpack(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_msgpack(j); return result; } @@ -25545,7 +25556,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool use_type = false) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_ubjson(j, use_size, use_type); return result; } @@ -25574,7 +25585,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_ubjson(j, use_size, use_type, true, true, version); return result; } @@ -25602,7 +25613,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static std::vector to_bson(const basic_json& j) { std::vector result; - binary_writer>( + detail::binary_writer>( detail::output_vector_sink(result)).write_bson(j); return result; }