diff --git a/CHANGES.md b/CHANGES.md index 7c295099..6cdb43fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ### Unreleased +* Fix parsing of out of range floats (very large exponents that lead ot either `0.0` or `Inf`). + ### 2026-03-25 (2.19.3) * Fix handling of unescaped control characters preceeded by a backslash. diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index 38f3a48c..976fbbe7 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -855,7 +855,7 @@ NOINLINE(static) VALUE json_decode_large_float(const char *start, long len) /* Ruby JSON optimized float decoder using vendored Ryu algorithm * Accepts pre-extracted mantissa and exponent from first-pass validation */ -static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int32_t exponent, bool negative, +static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative, const char *start, const char *end) { if (RB_UNLIKELY(config->decimal_class)) { @@ -863,13 +863,21 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text); } + if (RB_UNLIKELY(exponent > INT32_MAX)) { + return CInfinity; + } + + if (RB_UNLIKELY(exponent < INT32_MIN)) { + return rb_float_new(0.0); + } + // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case) // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) { return json_decode_large_float(start, end - start); } - return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, exponent, negative)); + return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative)); } static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) @@ -1144,7 +1152,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig const char first_digit = *state->cursor; // Variables for Ryu optimization - extract digits during parsing - int32_t exponent = 0; + int64_t exponent = 0; int decimal_point_pos = -1; uint64_t mantissa = 0; @@ -1188,7 +1196,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig raise_parse_error_at("invalid number: %s", state, start); } - exponent = negative_exponent ? -((int32_t)abs_exponent) : ((int32_t)abs_exponent); + exponent = negative_exponent ? -abs_exponent : abs_exponent; } if (integer) { diff --git a/test/json/json_ryu_fallback_test.rb b/test/json/json_ryu_fallback_test.rb index 59ba76d3..dc60067b 100644 --- a/test/json/json_ryu_fallback_test.rb +++ b/test/json/json_ryu_fallback_test.rb @@ -166,4 +166,12 @@ def test_invalid_numbers_rejected end end end + + def test_large_exponent_numbers + assert_equal Float::INFINITY, JSON.parse("1e4294967296") + assert_equal 0.0, JSON.parse("1e-4294967296") + assert_equal 0.0, JSON.parse("99999999999999999e-4294967296") + assert_equal Float::INFINITY, JSON.parse("1e4294967295") + assert_equal Float::INFINITY, JSON.parse("1e4294967297") + end end