diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index deccec8e4..1c965f78f 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -393,7 +393,7 @@ struct depth_counter /*! @brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w < -10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position) +10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position) dump() writes the shortest decimal in the rounding interval of x (the closest one if there are several, see zmij::to_shortest()). Its digits are those of a @@ -1258,13 +1258,15 @@ class view_serializer } for (; p != stop && batch.count != float_batch::capacity(); ++p) { - // a token of the source: 16 or 17 digits, at least 8 after the - // point, no exponent, 32 bytes to read + // a token of the source: 16 or 17 digits, 8 to 16 after the + // point, no exponent, 32 bytes to read (17 after the point, with + // no digit before it, only in a damaged image loaded with + // image_check::bounds: beyond the range of locate_token()) const unsigned frac_digits = p->extra >> 8u; const unsigned int_digits = p->extra & 0xFFu; const unsigned count = int_digits + frac_digits; if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0 - && count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33) + && count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33) { const auto* const token = reinterpret_cast(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const unsigned sign = token[0] == '-' ? 1 : 0; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 22c72d982..075544088 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -8841,7 +8841,7 @@ struct depth_counter /*! @brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w < -10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position) +10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position) dump() writes the shortest decimal in the rounding interval of x (the closest one if there are several, see zmij::to_shortest()). Its digits are those of a @@ -9706,13 +9706,15 @@ class view_serializer } for (; p != stop && batch.count != float_batch::capacity(); ++p) { - // a token of the source: 16 or 17 digits, at least 8 after the - // point, no exponent, 32 bytes to read + // a token of the source: 16 or 17 digits, 8 to 16 after the + // point, no exponent, 32 bytes to read (17 after the point, with + // no digit before it, only in a damaged image loaded with + // image_check::bounds: beyond the range of locate_token()) const unsigned frac_digits = p->extra >> 8u; const unsigned int_digits = p->extra & 0xFFu; const unsigned count = int_digits + frac_digits; if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0 - && count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33) + && count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33) { const auto* const token = reinterpret_cast(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const unsigned sign = token[0] == '-' ? 1 : 0; diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index 499f85342..a4e282639 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -1165,6 +1165,44 @@ TEST_CASE("json_view images: check") } } + SECTION("float tokens with 17 digits after the point and none before it") + { + // No JSON number has this layout. dump() batches tokens of 16 or 17 + // digits, but only those with 8 to 16 fraction digits are in its + // range: 17 read past the table of powers of five (caught by a + // bounds-checked std::array), and on x86-64 wrote 18 digits + // (the assertion of write_decimal()). + const std::vector img = json_document::parse("[1.2345678901234567]" + std::string(40, ' ')).save(); + node n = node_at(img, 1); + REQUIRE(n.len == 18); + n.extra = 17u << 8u; + std::vector b = img; + set_node(b, 1, n); + std::memcpy(b.data() + text_at(img) + n.off, ".99999999999999999", 18); + const json_document d = json_document::load(b, image_check::bounds); + const std::string dumped = d.root().dump(); + CAPTURE(dumped) + CHECK(json::parse(dumped)[0].is_number()); + CHECK(dumped == d.root().materialize().dump()); + + // (the input of OSS-Fuzz issue 583898810: the token is " " and + // 15 nines, then "1{") + const std::vector fuzzed = + { + 0x4E, 0x4A, 0x56, 0x49, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x31, 0x7B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x00, 0x00 + }; + CHECK(load_result(fuzzed, image_check::full) == check_failed); + const json_document f = json_document::load(fuzzed, image_check::bounds); + CHECK_NOTHROW(f.root().dump()); + } + SECTION("nodes that share a range") { // [big string, then n strings made to point to the big string]: every