Skip to content

Commit b685502

Browse files
authored
fix: fix serde_json infinite Number convert to Jsonb Value unwrap panic (#96)
1 parent 425aae9 commit b685502

File tree

6 files changed

+228
-60
lines changed

6 files changed

+228
-60
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ rust-version = "1.80"
2727

2828
[dependencies]
2929
byteorder = "1.5.0"
30-
ethnum = "1.5.1"
30+
ethnum = "1.5.2"
3131
fast-float2 = "0.2.3"
3232
itoa = "1.0"
3333
jiff = "0.2.10"
3434
nom = "8.0.0"
3535
num-traits = "0.2.19"
36-
ordered-float = { version = "5.0", default-features = false }
37-
rand = { version = "0.9.0", features = ["small_rng"] }
36+
ordered-float = { version = "5.1.0", default-features = false }
37+
rand = { version = "0.9.2", features = ["small_rng"] }
3838
ryu = "1.0"
3939
serde = "1.0"
4040
serde_json = { version = "1.0", default-features = false, features = ["std"] }

src/from.rs

Lines changed: 195 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,24 @@
1515
use core::iter::FromIterator;
1616
use std::borrow::Cow;
1717

18+
#[cfg(feature = "arbitrary_precision")]
19+
use ethnum::i256;
1820
use ordered_float::OrderedFloat;
1921
use serde_json::Map as JsonMap;
2022
use serde_json::Number as JsonNumber;
2123
use serde_json::Value as JsonValue;
2224

23-
use super::number::Number;
24-
use super::value::Object;
25-
use super::value::Value;
25+
#[cfg(feature = "arbitrary_precision")]
26+
use crate::constants::DECIMAL128_MAX;
27+
#[cfg(feature = "arbitrary_precision")]
28+
use crate::constants::DECIMAL128_MIN;
29+
use crate::value::Object;
30+
use crate::value::Value;
31+
#[cfg(feature = "arbitrary_precision")]
32+
use crate::Decimal128;
33+
#[cfg(feature = "arbitrary_precision")]
34+
use crate::Decimal256;
35+
use crate::Number;
2636

2737
macro_rules! from_signed_integer {
2838
($($ty:ident)*) => {
@@ -154,12 +164,37 @@ impl From<&JsonValue> for Value<'_> {
154164
JsonValue::Null => Value::Null,
155165
JsonValue::Bool(v) => Value::Bool(*v),
156166
JsonValue::Number(v) => {
157-
if v.is_u64() {
158-
Value::Number(Number::UInt64(v.as_u64().unwrap()))
159-
} else if v.is_i64() {
160-
Value::Number(Number::Int64(v.as_i64().unwrap()))
167+
if let Some(n) = v.as_u64() {
168+
return Value::Number(Number::UInt64(n));
169+
} else if let Some(n) = v.as_i64() {
170+
return Value::Number(Number::Int64(n));
171+
}
172+
#[cfg(feature = "arbitrary_precision")]
173+
{
174+
if let Some(n) = v.as_i128() {
175+
if (DECIMAL128_MIN..=DECIMAL128_MAX).contains(&n) {
176+
return Value::Number(Number::Decimal128(Decimal128 {
177+
value: n,
178+
scale: 0,
179+
}));
180+
} else {
181+
return Value::Number(Number::Decimal256(Decimal256 {
182+
value: n.into(),
183+
scale: 0,
184+
}));
185+
}
186+
} else if let Some(n) = v.as_u128() {
187+
return Value::Number(Number::Decimal256(Decimal256 {
188+
value: n.into(),
189+
scale: 0,
190+
}));
191+
}
192+
}
193+
if let Some(n) = v.as_f64() {
194+
Value::Number(Number::Float64(n))
161195
} else {
162-
Value::Number(Number::Float64(v.as_f64().unwrap()))
196+
// If the value is NaN or Infinity, fallback to NULL
197+
Value::Null
163198
}
164199
}
165200
JsonValue::String(v) => Value::String(v.clone().into()),
@@ -194,17 +229,43 @@ impl<'a> From<Value<'a>> for JsonValue {
194229
Value::Null => JsonValue::Null,
195230
Value::Bool(v) => JsonValue::Bool(v),
196231
Value::Number(v) => match v {
197-
Number::Int64(v) => JsonValue::Number(v.into()),
198-
Number::UInt64(v) => JsonValue::Number(v.into()),
199-
Number::Float64(v) => JsonValue::Number(JsonNumber::from_f64(v).unwrap()),
200-
Number::Decimal64(v) => {
201-
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
232+
Number::Int64(n) => JsonValue::Number(n.into()),
233+
Number::UInt64(n) => JsonValue::Number(n.into()),
234+
Number::Decimal64(d) if d.scale == 0 => JsonValue::Number(d.value.into()),
235+
#[cfg(feature = "arbitrary_precision")]
236+
Number::Decimal128(ref d) if d.scale == 0 => {
237+
if let Some(n) = JsonNumber::from_i128(d.value) {
238+
JsonValue::Number(n)
239+
} else if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
240+
JsonValue::Number(n)
241+
} else {
242+
JsonValue::Null
243+
}
202244
}
203-
Number::Decimal128(v) => {
204-
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
245+
#[cfg(feature = "arbitrary_precision")]
246+
Number::Decimal256(ref d) if d.scale == 0 => {
247+
if d.value >= i256::ZERO && d.value <= i256::from(u128::MAX) {
248+
if let Some(n) = JsonNumber::from_u128(d.value.as_u128()) {
249+
return JsonValue::Number(n);
250+
}
251+
} else if d.value >= i256::from(i128::MIN) && d.value < i256::ZERO {
252+
if let Some(n) = JsonNumber::from_i128(d.value.as_i128()) {
253+
return JsonValue::Number(n);
254+
}
255+
}
256+
if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
257+
JsonValue::Number(n)
258+
} else {
259+
JsonValue::Null
260+
}
205261
}
206-
Number::Decimal256(v) => {
207-
JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap())
262+
_ => {
263+
if let Some(n) = JsonNumber::from_f64(v.as_f64()) {
264+
JsonValue::Number(n)
265+
} else {
266+
// If the value is NaN or Infinity, fallback to NULL
267+
JsonValue::Null
268+
}
208269
}
209270
},
210271
Value::String(v) => JsonValue::String(v.to_string()),
@@ -249,3 +310,120 @@ impl<'a> From<Value<'a>> for JsonValue {
249310
}
250311
}
251312
}
313+
314+
#[cfg(test)]
315+
mod tests {
316+
#[cfg(feature = "arbitrary_precision")]
317+
use super::i256;
318+
use super::*;
319+
use serde_json::json;
320+
#[cfg(feature = "arbitrary_precision")]
321+
use serde_json::Number as JsonNumber;
322+
323+
fn run_float_conversion_suite() {
324+
let finite_samples = [0.0, -1.5, 42.4242, 1.0e-10, 9_007_199_254_740_992.0];
325+
326+
for sample in finite_samples {
327+
let json_from_value = JsonValue::from(Value::from(sample));
328+
match &json_from_value {
329+
JsonValue::Number(num) => {
330+
assert_eq!(num.as_f64(), Some(sample), "failed for {sample}");
331+
}
332+
other => panic!("expected number for {sample}, got {other:?}"),
333+
}
334+
335+
match Value::from(&json_from_value) {
336+
Value::Number(Number::Float64(value)) => {
337+
assert_eq!(value, sample, "round-trip mismatch for {sample}");
338+
}
339+
other => panic!("expected float number for {sample}, got {other:?}"),
340+
}
341+
342+
// Cover the direct JsonValue -> Value path using serde_json's json! macro.
343+
match Value::from(&json!(sample)) {
344+
Value::Number(Number::Float64(value)) => {
345+
assert_eq!(value, sample, "json! conversion mismatch for {sample}");
346+
}
347+
other => panic!("expected float number for {sample}, got {other:?}"),
348+
}
349+
}
350+
351+
for edge in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
352+
let json_value = JsonValue::from(Value::from(edge));
353+
assert_eq!(
354+
json_value,
355+
JsonValue::Null,
356+
"non-finite value should map to null"
357+
);
358+
}
359+
}
360+
361+
#[test]
362+
#[cfg(feature = "arbitrary_precision")]
363+
fn float_conversions_with_arbitrary_precision() {
364+
run_float_conversion_suite();
365+
}
366+
367+
#[test]
368+
#[cfg(not(feature = "arbitrary_precision"))]
369+
fn float_conversions_without_arbitrary_precision() {
370+
run_float_conversion_suite();
371+
}
372+
373+
#[test]
374+
#[cfg(feature = "arbitrary_precision")]
375+
fn big_integer_conversion_suite() {
376+
let i128_samples = [DECIMAL128_MIN, DECIMAL128_MAX];
377+
for sample in i128_samples {
378+
let json_value = JsonValue::Number(JsonNumber::from_i128(sample).unwrap());
379+
match Value::from(&json_value) {
380+
Value::Number(Number::Decimal128(decimal)) => {
381+
assert_eq!(
382+
decimal.value, sample,
383+
"Decimal128 value mismatch for {sample}"
384+
);
385+
assert_eq!(decimal.scale, 0, "Decimal128 scale mismatch for {sample}");
386+
}
387+
other => panic!("expected Decimal128 for {sample}, got {other:?}"),
388+
}
389+
390+
let json_from_value = JsonValue::from(Value::Number(Number::Decimal128(Decimal128 {
391+
value: sample,
392+
scale: 0,
393+
})));
394+
395+
assert_eq!(
396+
json_from_value.to_string(),
397+
sample.to_string(),
398+
"precise JSON mismatch for {sample}"
399+
);
400+
}
401+
402+
let u128_samples = [i128::MAX as u128, u128::MAX];
403+
for sample in u128_samples {
404+
let json_value = JsonValue::Number(JsonNumber::from_u128(sample).unwrap());
405+
match Value::from(&json_value) {
406+
Value::Number(Number::Decimal256(decimal)) => {
407+
assert_eq!(
408+
decimal.value,
409+
i256::from(sample),
410+
"Decimal256 value mismatch for {sample}"
411+
);
412+
assert_eq!(decimal.scale, 0, "Decimal256 scale mismatch for {sample}");
413+
}
414+
other => panic!("expected Decimal256 for {sample}, got {other:?}"),
415+
}
416+
417+
let json_from_value = JsonValue::from(Value::Number(Number::Decimal256(Decimal256 {
418+
value: i256::from(sample),
419+
scale: 0,
420+
})));
421+
422+
assert_eq!(
423+
json_from_value.to_string(),
424+
sample.to_string(),
425+
"precise JSON mismatch for {sample}"
426+
);
427+
}
428+
}
429+
}

src/functions/operator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ impl RawJsonb<'_> {
694694
buf.push(depth);
695695
buf.push(NUMBER_LEVEL);
696696
let num = num_item.as_number()?;
697-
let n = num.as_f64().unwrap();
697+
let n = num.as_f64();
698698
// https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260
699699
let s = n.to_bits() as i64;
700700
let v = s ^ (((s >> 63) as u64) >> 1) as i64;

src/functions/scalar.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ impl RawJsonb<'_> {
10231023
match jsonb_item {
10241024
JsonbItem::Number(num) => {
10251025
let value = num.as_number()?;
1026-
Ok(value.as_f64())
1026+
Ok(Some(value.as_f64()))
10271027
}
10281028
_ => Ok(None),
10291029
}
@@ -1106,9 +1106,8 @@ impl RawJsonb<'_> {
11061106
}
11071107
JsonbItem::Number(num) => {
11081108
let value = num.as_number()?;
1109-
if let Some(v) = value.as_f64() {
1110-
return Ok(v);
1111-
}
1109+
let v = value.as_f64();
1110+
return Ok(v);
11121111
}
11131112
JsonbItem::String(s) => {
11141113
if let Ok(v) = s.parse::<f64>() {

0 commit comments

Comments
 (0)