From c0b97309953cc944830a6e9d0238413fed3525a5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 06:34:35 +0000 Subject: [PATCH] fix: as_i128 accepts u64 values above i64::MAX The default `ValueAsScalar::as_i128` went through `as_i64`, so every u64 above `i64::MAX` gave `None`, although each one fits in an i128. `as_u128` (through `as_u64`) has no such gap, and the `128bit` implementation on `StaticNode` already handles it. User-visible through simd-json without the `128bit` feature: simd_json::serde::from_slice::(b"9223372036854775808") before: Err(ExpectedSigned) now: Ok(9223372036854775808) (serde_json and sonic-rs accept it.) Test: `node::tests::as_i128_covers_every_integer` (fails before this change). Found by a three-way differential fuzzer (serde_json, simd-json, sonic-rs). --- src/base.rs | 5 ++++- src/node.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/base.rs b/src/base.rs index 9f2ee2d..6ede3bf 100644 --- a/src/base.rs +++ b/src/base.rs @@ -32,7 +32,10 @@ pub trait ValueAsScalar { #[inline] #[must_use] fn as_i128(&self) -> Option { - self.as_i64().map(Into::into) + // every u64 fits in an i128, including the ones above `i64::MAX` + self.as_i64() + .map(Into::into) + .or_else(|| self.as_u64().map(Into::into)) } /// Tries to represent the value as an i64 diff --git a/src/node.rs b/src/node.rs index a11ccac..a1206ff 100644 --- a/src/node.rs +++ b/src/node.rs @@ -312,3 +312,35 @@ impl PartialEq for StaticNode { } } } + +#[cfg(test)] +mod tests { + use super::StaticNode; + use crate::base::ValueAsScalar; + + #[test] + fn as_i128_covers_every_integer() { + // u64 values above i64::MAX fit in an i128 as well + assert_eq!( + StaticNode::U64(u64::MAX).as_i128(), + Some(i128::from(u64::MAX)) + ); + assert_eq!( + StaticNode::U64(1 << 63).as_i128(), + Some(i128::from(1_u64 << 63)) + ); + assert_eq!(StaticNode::U64(7).as_i128(), Some(7)); + assert_eq!( + StaticNode::I64(i64::MIN).as_i128(), + Some(i128::from(i64::MIN)) + ); + assert_eq!(StaticNode::F64(1.0).as_i128(), None); + assert_eq!(StaticNode::Null.as_i128(), None); + // and as_u128 (already right) still matches + assert_eq!( + StaticNode::U64(u64::MAX).as_u128(), + Some(u128::from(u64::MAX)) + ); + assert_eq!(StaticNode::I64(-1).as_u128(), None); + } +}