From f8080feba53752b981b1dcfbc5a05d01c7689beb Mon Sep 17 00:00:00 2001 From: yuxi-liu-wired <33951560+yuxi-liu-wired@users.noreply.github.com> Date: Thu, 1 Oct 2026 20:23:42 +0000 Subject: [PATCH] fix: as_array/as_object on an unparsed OwnedLazyValue panicked on use `OwnedLazyValue::as_array` and `as_object` (the `&self` versions from `JsonContainerTrait`) return a `LazyArray` / `LazyObject` view of a value that is still `LazyPacked::Raw`; the parse is cached in `LazyRaw::parsed`. Their `Deref` impls only handled `Parsed`, so the first use of the view panicked: let lv: OwnedLazyValue = sonic_rs::from_str("[1,2]")?; lv.as_array().unwrap().len() panicked: internal error: entered unreachable code: must be a lazy array (The `_mut` versions replace `Raw` with `Parsed` first, so they work, which is the path the existing tests use.) `Deref` now also takes the `Raw` case and returns the cached parse from `LazyRaw::load`. Test: `test_owned_as_array_as_object_on_raw` (fails before this change); `cargo miri test --all-features --lib lazyvalue::owned` is clean. Found by a differential fuzzer (serde_json, simd-json, sonic-rs, jiter). --- src/lazyvalue/owned.rs | 56 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/lazyvalue/owned.rs b/src/lazyvalue/owned.rs index edacc55..de79c1f 100644 --- a/src/lazyvalue/owned.rs +++ b/src/lazyvalue/owned.rs @@ -640,10 +640,15 @@ pub struct LazyObject(OwnedLazyValue); impl std::ops::Deref for LazyObject { type Target = Vec<(FastStr, OwnedLazyValue)>; fn deref(&self) -> &Self::Target { - if let LazyPacked::Parsed(Parsed::LazyObject(obj)) = &self.0 .0 { - obj - } else { - unreachable!("must be a lazy object"); + // `as_object(&self)` hands out a `LazyObject` for a value that is still `Raw` (its parse + // is cached in `LazyRaw::parsed`), so both representations reach here. + match &self.0 .0 { + LazyPacked::Parsed(Parsed::LazyObject(obj)) => obj, + LazyPacked::Raw(raw) => match raw.load() { + Ok(Parsed::LazyObject(obj)) => obj, + _ => unreachable!("must be a lazy object"), + }, + _ => unreachable!("must be a lazy object"), } } } @@ -727,10 +732,15 @@ impl std::ops::DerefMut for LazyArray { impl std::ops::Deref for LazyArray { type Target = Vec; fn deref(&self) -> &Self::Target { - if let LazyPacked::Parsed(Parsed::LazyArray(obj)) = &self.0 .0 { - obj - } else { - unreachable!("must be a lazy array"); + // `as_array(&self)` hands out a `LazyArray` for a value that is still `Raw` (its parse is + // cached in `LazyRaw::parsed`), so both representations reach here. + match &self.0 .0 { + LazyPacked::Parsed(Parsed::LazyArray(arr)) => arr, + LazyPacked::Raw(raw) => match raw.load() { + Ok(Parsed::LazyArray(arr)) => arr, + _ => unreachable!("must be a lazy array"), + }, + _ => unreachable!("must be a lazy array"), } } } @@ -795,6 +805,36 @@ mod test { assert!(own_c.is_array()); } + #[test] + fn test_owned_as_array_as_object_on_raw() { + // `as_array`/`as_object` (the `&self` versions) return a view of a value that is still + // unparsed; using it (len, iter, index) used to panic with "must be a lazy array". + let lv: OwnedLazyValue = crate::from_str(r#"[1, "a", [2]]"#).unwrap(); + let arr = lv.as_array().unwrap(); + assert_eq!(arr.len(), 3); + assert_eq!(arr[1].as_str(), Some("a")); + assert_eq!(arr.iter().count(), 3); + assert_eq!(arr[2].as_array().unwrap().len(), 1); + + let lv: OwnedLazyValue = crate::from_str(r#"{"a": 1, "b": {"c": [true]}}"#).unwrap(); + let obj = lv.as_object().unwrap(); + assert_eq!(obj.len(), 2); + let keys: Vec<&str> = obj.iter().map(|(k, _)| k.as_str()).collect(); + assert_eq!(keys, ["a", "b"]); + let inner = obj[1].1.as_object().unwrap(); + assert_eq!(inner[0].1.as_array().unwrap()[0].as_bool(), Some(true)); + + let empty: OwnedLazyValue = crate::from_str("[]").unwrap(); + assert!(empty.as_array().unwrap().is_empty()); + let empty: OwnedLazyValue = crate::from_str("{}").unwrap(); + assert!(empty.as_object().unwrap().is_empty()); + + // wrong kind is still None + assert!(lv.as_array().is_none()); + let n: OwnedLazyValue = crate::from_str("1").unwrap(); + assert!(n.as_array().is_none() && n.as_object().is_none()); + } + #[test] fn test_owned_array() { let mut lv: OwnedLazyValue =