From c79fc641b2ed89835c653946727442527e307749 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 15:43:11 +0000 Subject: [PATCH] fix: write_float writes null for NaN and infinity `BaseGenerator::write_float` passed every f64 to `ryu::Buffer:: format_finite`, whose output for non-finite input is unspecified. NaN came out as `2.696539702293474e308` and infinity as `1.797693134862316e308`, so simd-json serialized simd_json::to_string(&f64::NAN) "2.696539702293474e308" simd_json::to_string(&f64::INFINITY) "1.797693134862316e308" (and the same from `Value::encode`). Both are valid JSON numbers but not the value: the first reads back as infinity. JSON has no NaN or infinity; serde_json writes `null`. `write_float` now does the same. Test: `test_write_float_non_finite` (fails before this change). Found by a differential fuzzer (serde_json, simd-json, sonic-rs, jiter) extended to typed serialization. --- src/generator.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/generator.rs b/src/generator.rs index 20895e3..b8e4ef1 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -166,6 +166,12 @@ pub trait BaseGenerator { /// if the write fails #[inline] fn write_float(&mut self, num: f64) -> io::Result<()> { + // JSON has no NaN or infinity, and `format_finite` gives an unspecified string for them + // (`2.696539702293474e308` for NaN, which reads back as infinity). Write `null`, as + // serde_json does. + if !num.is_finite() { + return self.get_writer().write_all(b"null"); + } let mut buffer = ryu::Buffer::new(); let s = buffer.format_finite(num); self.get_writer().write_all(s.as_bytes()) @@ -888,6 +894,25 @@ pub(crate) fn extend_from_slice(dst: &mut Vec, src: &[u8]) { #[cfg(test)] mod tests { + #[test] + fn test_write_float_non_finite() { + // NaN and infinity are not JSON numbers: they are written as `null` (as serde_json does), + // not as the unspecified output of `format_finite` (`2.696539702293474e308` for NaN). + use super::{BaseGenerator, DumpGenerator}; + for (f, want) in [ + (f64::NAN, "null"), + (f64::INFINITY, "null"), + (f64::NEG_INFINITY, "null"), + (1.5, "1.5"), + (-0.0, "-0.0"), + (f64::MAX, "1.7976931348623157e308"), + ] { + let mut g = DumpGenerator::new(); + g.write_float(f).expect("write"); + assert_eq!(g.consume(), want, "{f:?}"); + } + } + #[test] fn test_write_string_rust() { let mut writer = Vec::new();