From 4fa95d98105ac30c02293675e75d0651fea2d60a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 27 Sep 2026 14:18:46 +0200 Subject: [PATCH] Remove unreachable branches from the binary writer (#5583) Coverage reported conditions in the binary writer that can never be false, and marked the code behind them with LCOV_EXCL. Remove them instead of excluding them: - CBOR writes the length of a string, binary value, array, or object exactly like an unsigned integer, only with another major type. One function, write_cbor_head(), now writes both, so the integer tests cover every width and the four excluded 64-bit length branches are gone. - A last `else if` whose condition holds for every remaining value (an unsigned value at most UINT64_MAX, a signed one in the range of int64_t) is now a plain `else`. - Whether a signed integer fits into an int64 for UBJSON and BJData is decided by its type at compile time. Only an integer type wider than 64 bits gets a range check and the high-precision fallback. - The private get_impl(boolean_t*) was never called. The UBJSON type prefix 'H' of an optimized container of unsigned integers beyond the range of int64 was reachable although excluded; it is tested now. The output is unchanged. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/output/binary_writer.hpp | 329 ++++++----------- include/nlohmann/json.hpp | 11 - single_include/nlohmann/json.hpp | 340 ++++++------------ tests/src/unit-ubjson.cpp | 15 + 4 files changed, 235 insertions(+), 460 deletions(-) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 42ee60389..9c41e2962 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -168,92 +168,20 @@ class binary_writer if (j.m_data.m_value.number_integer >= 0) { // CBOR does not differentiate between positive signed - // integers and unsigned integers. Therefore, we used the - // code from the value_t::number_unsigned case here. - if (j.m_data.m_value.number_integer <= 0x17) - { - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x18)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x19)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else - { - oa.write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } + // integers and unsigned integers + write_cbor_head(0x00, static_cast(j.m_data.m_value.number_integer)); } else { - // The conversions below encode the sign in the first - // byte, and the value is converted to a positive number. - const auto positive_number = -1 - j.m_data.m_value.number_integer; - if (j.m_data.m_value.number_integer >= -24) - { - write_number(static_cast(0x20 + positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x38)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x39)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x3A)); - write_number(static_cast(positive_number)); - } - else - { - oa.write_character(to_char_type(0x3B)); - write_number(static_cast(positive_number)); - } + // a negative integer n is encoded as -1 - n + write_cbor_head(0x20, static_cast(-1 - j.m_data.m_value.number_integer)); } break; } case value_t::number_unsigned: { - if (j.m_data.m_value.number_unsigned <= 0x17) - { - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x18)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x19)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else - { - oa.write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } + write_cbor_head(0x00, j.m_data.m_value.number_unsigned); break; } @@ -283,33 +211,7 @@ class binary_writer case value_t::string: { // step 1: write control byte and the string length - const auto N = j.m_data.m_value.string->size(); - if (N <= 0x17) - { - write_number(static_cast(0x60 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x78)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x79)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x7A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x7B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x60, j.m_data.m_value.string->size()); // step 2: write the string oa.write_characters( @@ -321,33 +223,7 @@ class binary_writer case value_t::array: { // step 1: write control byte and the array size - const auto N = j.m_data.m_value.array->size(); - if (N <= 0x17) - { - write_number(static_cast(0x80 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x98)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x99)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x9A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x9B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x80, j.m_data.m_value.array->size()); // step 2: write each element for (const auto& el : *j.m_data.m_value.array) @@ -376,7 +252,7 @@ class binary_writer write_number(static_cast(0xda)); write_number(static_cast(j.m_data.m_value.binary->subtype())); } - else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + else { write_number(static_cast(0xdb)); write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -385,32 +261,7 @@ class binary_writer // step 1: write control byte and the binary array size const auto N = j.m_data.m_value.binary->size(); - if (N <= 0x17) - { - write_number(static_cast(0x40 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x58)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x59)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x5A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x5B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x40, N); // step 2: write each element oa.write_characters( @@ -423,33 +274,7 @@ class binary_writer case value_t::object: { // step 1: write control byte and the object size - const auto N = j.m_data.m_value.object->size(); - if (N <= 0x17) - { - write_number(static_cast(0xA0 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xB8)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xB9)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xBA)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xBB)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0xA0, j.m_data.m_value.object->size()); // step 2: write each element for (const auto& el : *j.m_data.m_value.object) @@ -517,7 +342,7 @@ class binary_writer oa.write_character(to_char_type(0xCE)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + else { // uint 64 oa.write_character(to_char_type(0xCF)); @@ -552,8 +377,7 @@ class binary_writer oa.write_character(to_char_type(0xD2)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + else { // int 64 oa.write_character(to_char_type(0xD3)); @@ -588,7 +412,7 @@ class binary_writer oa.write_character(to_char_type(0xCE)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + else { // uint 64 oa.write_character(to_char_type(0xCF)); @@ -1546,6 +1370,46 @@ class binary_writer // CBOR // ////////// + /*! + @brief write the head of a CBOR data item + + The head is the major type in the upper three bits of the first byte and + an argument - an unsigned integer, the length of a string, the number of + elements of a container - in the shortest of its encodings: in the lower + five bits of the first byte itself if it is at most 23, otherwise in the + 1, 2, 4, or 8 bytes that follow (RFC 8949, section 3). + + @param[in] major_type the major type, shifted into the upper three bits + @param[in] argument the argument of the data item + */ + void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument) + { + if (argument <= 0x17) + { + write_number(static_cast(major_type + argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x18))); + write_number(static_cast(argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x19))); + write_number(static_cast(argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x1A))); + write_number(static_cast(argument)); + } + else + { + oa.write_character(to_char_type(static_cast(major_type + 0x1B))); + write_number(argument); + } + } + static constexpr CharType get_cbor_float_prefix(float /*unused*/) { return to_char_type(0xFA); // Single-Precision Float @@ -1651,7 +1515,7 @@ class binary_writer } write_number(static_cast(n), use_bjdata); } - else if (use_bjdata && n <= (std::numeric_limits::max)()) + else if (use_bjdata) { if (add_prefix) { @@ -1731,30 +1595,59 @@ class binary_writer } write_number(static_cast(n), use_bjdata); } - else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa.write_character(to_char_type('L')); // int64 - } - write_number(static_cast(n), use_bjdata); - } - // LCOV_EXCL_START else { - if (add_prefix) - { - oa.write_character(to_char_type('H')); // high-precision number - } - - const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true, use_bjdata); - for (std::size_t i = 0; i < number.size(); ++i) - { - oa.write_character(to_char_type(static_cast(number[i]))); - } + // every value of an integer type of at most 64 bits fits into an + // int64; only a wider type needs a range check + write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, + std::integral_constant < bool, std::numeric_limits::digits <= std::numeric_limits::digits > {}); } - // LCOV_EXCL_STOP + } + + template + void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/) + { + if (add_prefix) + { + oa.write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n), use_bjdata); + } + + template + void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {}); + return; + } + + if (add_prefix) + { + oa.write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa.write_character(to_char_type(static_cast(number[i]))); + } + } + + template + static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept + { + return 'L'; + } + + template + static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept + { + // anything outside of the range of an int64 is treated as a + // high-precision number + return ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) ? 'L' : 'H'; } /*! @@ -1796,12 +1689,10 @@ class binary_writer { return 'm'; } - if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'L'; - } - // anything else is treated as a high-precision number - return 'H'; // LCOV_EXCL_LINE + // every value of an integer type of at most 64 bits fits into + // an int64; only a wider type needs a range check + return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer, + std::integral_constant < bool, std::numeric_limits::digits <= std::numeric_limits::digits > {}); } case value_t::number_unsigned: @@ -1834,12 +1725,12 @@ class binary_writer { return 'L'; } - if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + if (use_bjdata) { return 'M'; } // anything else is treated as a high-precision number - return 'H'; // LCOV_EXCL_LINE + return 'H'; } case value_t::number_float: diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index c578fd11a..d18baf599 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -2157,17 +2157,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value access // ////////////////// - /// get a boolean (explicit) - boolean_t get_impl(boolean_t* /*unused*/) const - { - if (JSON_HEDLEY_LIKELY(is_boolean())) - { - return m_data.m_value.boolean; - } - - JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this)); - } - /// get a pointer to the value (object) object_t* get_impl_ptr(object_t* /*unused*/) noexcept { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 814b8f411..b5938be0e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -19633,92 +19633,20 @@ class binary_writer if (j.m_data.m_value.number_integer >= 0) { // CBOR does not differentiate between positive signed - // integers and unsigned integers. Therefore, we used the - // code from the value_t::number_unsigned case here. - if (j.m_data.m_value.number_integer <= 0x17) - { - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x18)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x19)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else - { - oa.write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } + // integers and unsigned integers + write_cbor_head(0x00, static_cast(j.m_data.m_value.number_integer)); } else { - // The conversions below encode the sign in the first - // byte, and the value is converted to a positive number. - const auto positive_number = -1 - j.m_data.m_value.number_integer; - if (j.m_data.m_value.number_integer >= -24) - { - write_number(static_cast(0x20 + positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x38)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x39)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x3A)); - write_number(static_cast(positive_number)); - } - else - { - oa.write_character(to_char_type(0x3B)); - write_number(static_cast(positive_number)); - } + // a negative integer n is encoded as -1 - n + write_cbor_head(0x20, static_cast(-1 - j.m_data.m_value.number_integer)); } break; } case value_t::number_unsigned: { - if (j.m_data.m_value.number_unsigned <= 0x17) - { - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x18)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x19)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } - else - { - oa.write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_data.m_value.number_unsigned)); - } + write_cbor_head(0x00, j.m_data.m_value.number_unsigned); break; } @@ -19748,33 +19676,7 @@ class binary_writer case value_t::string: { // step 1: write control byte and the string length - const auto N = j.m_data.m_value.string->size(); - if (N <= 0x17) - { - write_number(static_cast(0x60 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x78)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x79)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x7A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x7B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x60, j.m_data.m_value.string->size()); // step 2: write the string oa.write_characters( @@ -19786,33 +19688,7 @@ class binary_writer case value_t::array: { // step 1: write control byte and the array size - const auto N = j.m_data.m_value.array->size(); - if (N <= 0x17) - { - write_number(static_cast(0x80 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x98)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x99)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x9A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x9B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x80, j.m_data.m_value.array->size()); // step 2: write each element for (const auto& el : *j.m_data.m_value.array) @@ -19841,7 +19717,7 @@ class binary_writer write_number(static_cast(0xda)); write_number(static_cast(j.m_data.m_value.binary->subtype())); } - else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + else { write_number(static_cast(0xdb)); write_number(static_cast(j.m_data.m_value.binary->subtype())); @@ -19850,32 +19726,7 @@ class binary_writer // step 1: write control byte and the binary array size const auto N = j.m_data.m_value.binary->size(); - if (N <= 0x17) - { - write_number(static_cast(0x40 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x58)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x59)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x5A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0x5B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0x40, N); // step 2: write each element oa.write_characters( @@ -19888,33 +19739,7 @@ class binary_writer case value_t::object: { // step 1: write control byte and the object size - const auto N = j.m_data.m_value.object->size(); - if (N <= 0x17) - { - write_number(static_cast(0xA0 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xB8)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xB9)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xBA)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa.write_character(to_char_type(0xBB)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP + write_cbor_head(0xA0, j.m_data.m_value.object->size()); // step 2: write each element for (const auto& el : *j.m_data.m_value.object) @@ -19982,7 +19807,7 @@ class binary_writer oa.write_character(to_char_type(0xCE)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + else { // uint 64 oa.write_character(to_char_type(0xCF)); @@ -20017,8 +19842,7 @@ class binary_writer oa.write_character(to_char_type(0xD2)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + else { // int 64 oa.write_character(to_char_type(0xD3)); @@ -20053,7 +19877,7 @@ class binary_writer oa.write_character(to_char_type(0xCE)); write_number(static_cast(j.m_data.m_value.number_integer)); } - else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + else { // uint 64 oa.write_character(to_char_type(0xCF)); @@ -21011,6 +20835,46 @@ class binary_writer // CBOR // ////////// + /*! + @brief write the head of a CBOR data item + + The head is the major type in the upper three bits of the first byte and + an argument - an unsigned integer, the length of a string, the number of + elements of a container - in the shortest of its encodings: in the lower + five bits of the first byte itself if it is at most 23, otherwise in the + 1, 2, 4, or 8 bytes that follow (RFC 8949, section 3). + + @param[in] major_type the major type, shifted into the upper three bits + @param[in] argument the argument of the data item + */ + void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument) + { + if (argument <= 0x17) + { + write_number(static_cast(major_type + argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x18))); + write_number(static_cast(argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x19))); + write_number(static_cast(argument)); + } + else if (argument <= (std::numeric_limits::max)()) + { + oa.write_character(to_char_type(static_cast(major_type + 0x1A))); + write_number(static_cast(argument)); + } + else + { + oa.write_character(to_char_type(static_cast(major_type + 0x1B))); + write_number(argument); + } + } + static constexpr CharType get_cbor_float_prefix(float /*unused*/) { return to_char_type(0xFA); // Single-Precision Float @@ -21116,7 +20980,7 @@ class binary_writer } write_number(static_cast(n), use_bjdata); } - else if (use_bjdata && n <= (std::numeric_limits::max)()) + else if (use_bjdata) { if (add_prefix) { @@ -21196,30 +21060,59 @@ class binary_writer } write_number(static_cast(n), use_bjdata); } - else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa.write_character(to_char_type('L')); // int64 - } - write_number(static_cast(n), use_bjdata); - } - // LCOV_EXCL_START else { - if (add_prefix) - { - oa.write_character(to_char_type('H')); // high-precision number - } - - const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true, use_bjdata); - for (std::size_t i = 0; i < number.size(); ++i) - { - oa.write_character(to_char_type(static_cast(number[i]))); - } + // every value of an integer type of at most 64 bits fits into an + // int64; only a wider type needs a range check + write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, + std::integral_constant < bool, std::numeric_limits::digits <= std::numeric_limits::digits > {}); } - // LCOV_EXCL_STOP + } + + template + void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/) + { + if (add_prefix) + { + oa.write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n), use_bjdata); + } + + template + void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {}); + return; + } + + if (add_prefix) + { + oa.write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa.write_character(to_char_type(static_cast(number[i]))); + } + } + + template + static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept + { + return 'L'; + } + + template + static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept + { + // anything outside of the range of an int64 is treated as a + // high-precision number + return ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) ? 'L' : 'H'; } /*! @@ -21261,12 +21154,10 @@ class binary_writer { return 'm'; } - if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'L'; - } - // anything else is treated as a high-precision number - return 'H'; // LCOV_EXCL_LINE + // every value of an integer type of at most 64 bits fits into + // an int64; only a wider type needs a range check + return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer, + std::integral_constant < bool, std::numeric_limits::digits <= std::numeric_limits::digits > {}); } case value_t::number_unsigned: @@ -21299,12 +21190,12 @@ class binary_writer { return 'L'; } - if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + if (use_bjdata) { return 'M'; } // anything else is treated as a high-precision number - return 'H'; // LCOV_EXCL_LINE + return 'H'; } case value_t::number_float: @@ -27135,17 +27026,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // value access // ////////////////// - /// get a boolean (explicit) - boolean_t get_impl(boolean_t* /*unused*/) const - { - if (JSON_HEDLEY_LIKELY(is_boolean())) - { - return m_data.m_value.boolean; - } - - JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this)); - } - /// get a pointer to the value (object) object_t* get_impl_ptr(object_t* /*unused*/) noexcept { diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index c8458c44d..9b3b4be17 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2981,3 +2981,18 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip()) } } } + +TEST_CASE("UBJSON optimized array of unsigned integers beyond int64") +{ + // UBJSON has no unsigned 64-bit type, so such values are written as + // high-precision numbers - also as the type of an optimized container + const json j = {18446744073709551615ULL, 9223372036854775808ULL}; + const std::vector expected = + { + '[', '$', 'H', '#', 'i', 2, + 'i', 20, '1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', '5', + 'i', 19, '9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5', '4', '7', '7', '5', '8', '0', '8' + }; + CHECK(json::to_ubjson(j, true, true) == expected); + CHECK(json::from_ubjson(expected) == j); +}