From 6d54bacef0938ee4226f46480742d96e7bf8c2c1 Mon Sep 17 00:00:00 2001 From: baishen Date: Wed, 12 Nov 2025 19:27:15 +0800 Subject: [PATCH] fix: fix `serde_json` infinite Number convert to Jsonb Value unwrap panic --- Cargo.toml | 6 +- src/from.rs | 212 +++++++++++++++++++++++++++++++++++--- src/functions/operator.rs | 2 +- src/functions/scalar.rs | 7 +- src/number.rs | 59 +++++------ src/value.rs | 2 +- 6 files changed, 228 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c65c38..fc20dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,14 +27,14 @@ rust-version = "1.80" [dependencies] byteorder = "1.5.0" -ethnum = "1.5.1" +ethnum = "1.5.2" fast-float2 = "0.2.3" itoa = "1.0" jiff = "0.2.10" nom = "8.0.0" num-traits = "0.2.19" -ordered-float = { version = "5.0", default-features = false } -rand = { version = "0.9.0", features = ["small_rng"] } +ordered-float = { version = "5.1.0", default-features = false } +rand = { version = "0.9.2", features = ["small_rng"] } ryu = "1.0" serde = "1.0" serde_json = { version = "1.0", default-features = false, features = ["std"] } diff --git a/src/from.rs b/src/from.rs index e34cc3a..259ef97 100644 --- a/src/from.rs +++ b/src/from.rs @@ -15,14 +15,24 @@ use core::iter::FromIterator; use std::borrow::Cow; +#[cfg(feature = "arbitrary_precision")] +use ethnum::i256; use ordered_float::OrderedFloat; use serde_json::Map as JsonMap; use serde_json::Number as JsonNumber; use serde_json::Value as JsonValue; -use super::number::Number; -use super::value::Object; -use super::value::Value; +#[cfg(feature = "arbitrary_precision")] +use crate::constants::DECIMAL128_MAX; +#[cfg(feature = "arbitrary_precision")] +use crate::constants::DECIMAL128_MIN; +use crate::value::Object; +use crate::value::Value; +#[cfg(feature = "arbitrary_precision")] +use crate::Decimal128; +#[cfg(feature = "arbitrary_precision")] +use crate::Decimal256; +use crate::Number; macro_rules! from_signed_integer { ($($ty:ident)*) => { @@ -154,12 +164,37 @@ impl From<&JsonValue> for Value<'_> { JsonValue::Null => Value::Null, JsonValue::Bool(v) => Value::Bool(*v), JsonValue::Number(v) => { - if v.is_u64() { - Value::Number(Number::UInt64(v.as_u64().unwrap())) - } else if v.is_i64() { - Value::Number(Number::Int64(v.as_i64().unwrap())) + if let Some(n) = v.as_u64() { + return Value::Number(Number::UInt64(n)); + } else if let Some(n) = v.as_i64() { + return Value::Number(Number::Int64(n)); + } + #[cfg(feature = "arbitrary_precision")] + { + if let Some(n) = v.as_i128() { + if (DECIMAL128_MIN..=DECIMAL128_MAX).contains(&n) { + return Value::Number(Number::Decimal128(Decimal128 { + value: n, + scale: 0, + })); + } else { + return Value::Number(Number::Decimal256(Decimal256 { + value: n.into(), + scale: 0, + })); + } + } else if let Some(n) = v.as_u128() { + return Value::Number(Number::Decimal256(Decimal256 { + value: n.into(), + scale: 0, + })); + } + } + if let Some(n) = v.as_f64() { + Value::Number(Number::Float64(n)) } else { - Value::Number(Number::Float64(v.as_f64().unwrap())) + // If the value is NaN or Infinity, fallback to NULL + Value::Null } } JsonValue::String(v) => Value::String(v.clone().into()), @@ -194,17 +229,43 @@ impl<'a> From> for JsonValue { Value::Null => JsonValue::Null, Value::Bool(v) => JsonValue::Bool(v), Value::Number(v) => match v { - Number::Int64(v) => JsonValue::Number(v.into()), - Number::UInt64(v) => JsonValue::Number(v.into()), - Number::Float64(v) => JsonValue::Number(JsonNumber::from_f64(v).unwrap()), - Number::Decimal64(v) => { - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) + Number::Int64(n) => JsonValue::Number(n.into()), + Number::UInt64(n) => JsonValue::Number(n.into()), + Number::Decimal64(d) if d.scale == 0 => JsonValue::Number(d.value.into()), + #[cfg(feature = "arbitrary_precision")] + Number::Decimal128(ref d) if d.scale == 0 => { + if let Some(n) = JsonNumber::from_i128(d.value) { + JsonValue::Number(n) + } else if let Some(n) = JsonNumber::from_f64(v.as_f64()) { + JsonValue::Number(n) + } else { + JsonValue::Null + } } - Number::Decimal128(v) => { - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) + #[cfg(feature = "arbitrary_precision")] + Number::Decimal256(ref d) if d.scale == 0 => { + if d.value >= i256::ZERO && d.value <= i256::from(u128::MAX) { + if let Some(n) = JsonNumber::from_u128(d.value.as_u128()) { + return JsonValue::Number(n); + } + } else if d.value >= i256::from(i128::MIN) && d.value < i256::ZERO { + if let Some(n) = JsonNumber::from_i128(d.value.as_i128()) { + return JsonValue::Number(n); + } + } + if let Some(n) = JsonNumber::from_f64(v.as_f64()) { + JsonValue::Number(n) + } else { + JsonValue::Null + } } - Number::Decimal256(v) => { - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) + _ => { + if let Some(n) = JsonNumber::from_f64(v.as_f64()) { + JsonValue::Number(n) + } else { + // If the value is NaN or Infinity, fallback to NULL + JsonValue::Null + } } }, Value::String(v) => JsonValue::String(v.to_string()), @@ -249,3 +310,120 @@ impl<'a> From> for JsonValue { } } } + +#[cfg(test)] +mod tests { + #[cfg(feature = "arbitrary_precision")] + use super::i256; + use super::*; + use serde_json::json; + #[cfg(feature = "arbitrary_precision")] + use serde_json::Number as JsonNumber; + + fn run_float_conversion_suite() { + let finite_samples = [0.0, -1.5, 42.4242, 1.0e-10, 9_007_199_254_740_992.0]; + + for sample in finite_samples { + let json_from_value = JsonValue::from(Value::from(sample)); + match &json_from_value { + JsonValue::Number(num) => { + assert_eq!(num.as_f64(), Some(sample), "failed for {sample}"); + } + other => panic!("expected number for {sample}, got {other:?}"), + } + + match Value::from(&json_from_value) { + Value::Number(Number::Float64(value)) => { + assert_eq!(value, sample, "round-trip mismatch for {sample}"); + } + other => panic!("expected float number for {sample}, got {other:?}"), + } + + // Cover the direct JsonValue -> Value path using serde_json's json! macro. + match Value::from(&json!(sample)) { + Value::Number(Number::Float64(value)) => { + assert_eq!(value, sample, "json! conversion mismatch for {sample}"); + } + other => panic!("expected float number for {sample}, got {other:?}"), + } + } + + for edge in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] { + let json_value = JsonValue::from(Value::from(edge)); + assert_eq!( + json_value, + JsonValue::Null, + "non-finite value should map to null" + ); + } + } + + #[test] + #[cfg(feature = "arbitrary_precision")] + fn float_conversions_with_arbitrary_precision() { + run_float_conversion_suite(); + } + + #[test] + #[cfg(not(feature = "arbitrary_precision"))] + fn float_conversions_without_arbitrary_precision() { + run_float_conversion_suite(); + } + + #[test] + #[cfg(feature = "arbitrary_precision")] + fn big_integer_conversion_suite() { + let i128_samples = [DECIMAL128_MIN, DECIMAL128_MAX]; + for sample in i128_samples { + let json_value = JsonValue::Number(JsonNumber::from_i128(sample).unwrap()); + match Value::from(&json_value) { + Value::Number(Number::Decimal128(decimal)) => { + assert_eq!( + decimal.value, sample, + "Decimal128 value mismatch for {sample}" + ); + assert_eq!(decimal.scale, 0, "Decimal128 scale mismatch for {sample}"); + } + other => panic!("expected Decimal128 for {sample}, got {other:?}"), + } + + let json_from_value = JsonValue::from(Value::Number(Number::Decimal128(Decimal128 { + value: sample, + scale: 0, + }))); + + assert_eq!( + json_from_value.to_string(), + sample.to_string(), + "precise JSON mismatch for {sample}" + ); + } + + let u128_samples = [i128::MAX as u128, u128::MAX]; + for sample in u128_samples { + let json_value = JsonValue::Number(JsonNumber::from_u128(sample).unwrap()); + match Value::from(&json_value) { + Value::Number(Number::Decimal256(decimal)) => { + assert_eq!( + decimal.value, + i256::from(sample), + "Decimal256 value mismatch for {sample}" + ); + assert_eq!(decimal.scale, 0, "Decimal256 scale mismatch for {sample}"); + } + other => panic!("expected Decimal256 for {sample}, got {other:?}"), + } + + let json_from_value = JsonValue::from(Value::Number(Number::Decimal256(Decimal256 { + value: i256::from(sample), + scale: 0, + }))); + + assert_eq!( + json_from_value.to_string(), + sample.to_string(), + "precise JSON mismatch for {sample}" + ); + } + } +} diff --git a/src/functions/operator.rs b/src/functions/operator.rs index 00008ee..8d8c486 100644 --- a/src/functions/operator.rs +++ b/src/functions/operator.rs @@ -694,7 +694,7 @@ impl RawJsonb<'_> { buf.push(depth); buf.push(NUMBER_LEVEL); let num = num_item.as_number()?; - let n = num.as_f64().unwrap(); + let n = num.as_f64(); // https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260 let s = n.to_bits() as i64; let v = s ^ (((s >> 63) as u64) >> 1) as i64; diff --git a/src/functions/scalar.rs b/src/functions/scalar.rs index 8c90734..a79e079 100644 --- a/src/functions/scalar.rs +++ b/src/functions/scalar.rs @@ -1023,7 +1023,7 @@ impl RawJsonb<'_> { match jsonb_item { JsonbItem::Number(num) => { let value = num.as_number()?; - Ok(value.as_f64()) + Ok(Some(value.as_f64())) } _ => Ok(None), } @@ -1106,9 +1106,8 @@ impl RawJsonb<'_> { } JsonbItem::Number(num) => { let value = num.as_number()?; - if let Some(v) = value.as_f64() { - return Ok(v); - } + let v = value.as_f64(); + return Ok(v); } JsonbItem::String(s) => { if let Ok(v) = s.parse::() { diff --git a/src/number.rs b/src/number.rs index 470fefe..93b11e3 100644 --- a/src/number.rs +++ b/src/number.rs @@ -518,23 +518,14 @@ impl Number { /// Returns the f64 representation of the number. /// /// This method always returns a value, but may lose precision for very large numbers. - pub fn as_f64(&self) -> Option { + pub fn as_f64(&self) -> f64 { match self { - Number::Int64(v) => Some(*v as f64), - Number::UInt64(v) => Some(*v as f64), - Number::Float64(v) => Some(*v), - Number::Decimal64(v) => { - let val = v.to_float64(); - Some(val) - } - Number::Decimal128(v) => { - let val = v.to_float64(); - Some(val) - } - Number::Decimal256(v) => { - let val = v.to_float64(); - Some(val) - } + Number::Int64(v) => *v as f64, + Number::UInt64(v) => *v as f64, + Number::Float64(v) => *v, + Number::Decimal64(v) => v.to_float64(), + Number::Decimal128(v) => v.to_float64(), + Number::Decimal256(v) => v.to_float64(), } } @@ -623,8 +614,8 @@ impl Number { } (Number::Float64(a), Number::Float64(b)) => Ok(Number::Float64(a + b)), (a, b) => { - let a_float = a.as_f64().unwrap(); - let b_float = b.as_f64().unwrap(); + let a_float = a.as_f64(); + let b_float = b.as_f64(); Ok(Number::Float64(a_float + b_float)) } } @@ -653,8 +644,8 @@ impl Number { .ok_or(Error::Message("Int64 overflow".to_string())), (Number::Float64(a), Number::Float64(b)) => Ok(Number::Float64(a - b)), (a, b) => { - let a_float = a.as_f64().unwrap(); - let b_float = b.as_f64().unwrap(); + let a_float = a.as_f64(); + let b_float = b.as_f64(); Ok(Number::Float64(a_float - b_float)) } } @@ -699,8 +690,8 @@ impl Number { } (Number::Float64(a), Number::Float64(b)) => Ok(Number::Float64(a * b)), (a, b) => { - let a_float = a.as_f64().unwrap(); - let b_float = b.as_f64().unwrap(); + let a_float = a.as_f64(); + let b_float = b.as_f64(); Ok(Number::Float64(a_float * b_float)) } } @@ -710,8 +701,8 @@ impl Number { /// /// This method returns an error if the divisor is zero. pub fn div(&self, other: Number) -> Result { - let a_float = self.as_f64().unwrap(); - let b_float = other.as_f64().unwrap(); + let a_float = self.as_f64(); + let b_float = other.as_f64(); if b_float == 0.0 { return Err(Error::Message("Division by zero".to_string())); } @@ -776,8 +767,8 @@ impl Number { Ok(Number::Float64(a % b)) } (a, b) => { - let a_float = a.as_f64().unwrap(); - let b_float = b.as_f64().unwrap(); + let a_float = a.as_f64(); + let b_float = b.as_f64(); if b_float == 0.0 { return Err(Error::Message("Division by zero".to_string())); } @@ -886,8 +877,8 @@ impl Ord for Number { { l_val.cmp(&r_val) } else { - let l = OrderedFloat(self.as_f64().unwrap()); - let r = OrderedFloat(other.as_f64().unwrap()); + let l = OrderedFloat(self.as_f64()); + let r = OrderedFloat(other.as_f64()); l.cmp(&r) } } @@ -903,8 +894,8 @@ impl Ord for Number { { l_val.cmp(&r_val) } else { - let l = OrderedFloat(self.as_f64().unwrap()); - let r = OrderedFloat(other.as_f64().unwrap()); + let l = OrderedFloat(self.as_f64()); + let r = OrderedFloat(other.as_f64()); l.cmp(&r) } } @@ -930,8 +921,8 @@ impl Ord for Number { } } // multiply overflow, fallback to used float compare - let l = OrderedFloat(self.as_f64().unwrap()); - let r = OrderedFloat(other.as_f64().unwrap()); + let l = OrderedFloat(self.as_f64()); + let r = OrderedFloat(other.as_f64()); l.cmp(&r) } } @@ -1126,8 +1117,8 @@ impl Ord for Number { // Fall back to float comparison for any other combinations (_, _) => { - let l = OrderedFloat(self.as_f64().unwrap()); - let r = OrderedFloat(other.as_f64().unwrap()); + let l = OrderedFloat(self.as_f64()); + let r = OrderedFloat(other.as_f64()); l.cmp(&r) } } diff --git a/src/value.rs b/src/value.rs index a39fa72..0e69a0b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -308,7 +308,7 @@ impl<'a> Value<'a> { pub fn as_f64(&self) -> Option { match self { - Value::Number(n) => n.as_f64(), + Value::Number(n) => Some(n.as_f64()), _ => None, } }