From a6210414b11b36e4cf8b8312160ba94986f031bc Mon Sep 17 00:00:00 2001 From: Xiretza Date: Thu, 9 Jun 2022 16:39:43 +0200 Subject: [PATCH 1/2] fix(Unstructured)!: don't produce meaningless data if exhausted If the Unstructured is completely exhausted, returning a buffer completely filled with zeroes instead of indicating failure does not make sense. It also causes issues for any users of Arbitrary implementations for integers (which are based on this method) who rely on eventually getting a nonzero result to e.g. break recursion. --- src/lib.rs | 6 ++++-- src/unstructured.rs | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2de041d..184f21e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1166,7 +1166,7 @@ mod test { #[test] fn finite_buffer_fill_buffer() { - let x = [1, 2, 3, 4]; + let x = [1, 2, 3, 4, 5]; let mut rb = Unstructured::new(&x); let mut z = [0; 2]; rb.fill_buffer(&mut z).unwrap(); @@ -1174,7 +1174,9 @@ mod test { rb.fill_buffer(&mut z).unwrap(); assert_eq!(z, [3, 4]); rb.fill_buffer(&mut z).unwrap(); - assert_eq!(z, [0, 0]); + assert_eq!(z, [5, 0]); + + assert!(rb.fill_buffer(&mut z).is_err()); } #[test] diff --git a/src/unstructured.rs b/src/unstructured.rs index 07ba932..5d111b6 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -468,15 +468,16 @@ impl<'a> Unstructured<'a> { /// `Arbitrary` implementations like `>::arbitrary` and /// `String::arbitrary` over using this method directly. /// - /// If this `Unstructured` does not have enough underlying data to fill the - /// whole `buffer`, it pads the buffer out with zeros. + /// If this `Unstructured` only has enough underlying data to fill only part of + /// the whole `buffer`, it pads the rest of the buffer with zeros. If the + /// `Unstructured` is completely exhausted, an error is returned. /// /// # Example /// /// ``` /// use arbitrary::Unstructured; /// - /// let mut u = Unstructured::new(&[1, 2, 3, 4]); + /// let mut u = Unstructured::new(&[1, 2, 3]); /// /// let mut buf = [0; 2]; /// @@ -484,12 +485,15 @@ impl<'a> Unstructured<'a> { /// assert_eq!(buf, [1, 2]); /// /// assert!(u.fill_buffer(&mut buf).is_ok()); - /// assert_eq!(buf, [3, 4]); + /// assert_eq!(buf, [3, 0]); /// - /// assert!(u.fill_buffer(&mut buf).is_ok()); - /// assert_eq!(buf, [0, 0]); + /// assert!(u.fill_buffer(&mut buf).is_err()); /// ``` pub fn fill_buffer(&mut self, buffer: &mut [u8]) -> Result<()> { + if self.is_empty() { + return Err(Error::NotEnoughData); + } + let n = std::cmp::min(buffer.len(), self.data.len()); buffer[..n].copy_from_slice(&self.data[..n]); for byte in buffer[n..].iter_mut() { From 7bfd6847c3738104905174d2e9488375f7d82452 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Thu, 9 Jun 2022 17:12:57 +0200 Subject: [PATCH 2/2] tests: add test for unbounded recursion on exhaustion Because the enum variant is decided based on an integer, and integers' Arbitrary implementations always returned 0 if the Unstructured was exhausted, an enum with a recursing first variant would lead to unbounded recursion. --- tests/derive.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/derive.rs b/tests/derive.rs index f107688..e97965e 100755 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -135,6 +135,17 @@ fn recursive() { ); } +#[test] +fn recursive_first_variant() { + #[derive(PartialEq, Eq, Debug, Arbitrary)] + enum Nat { + Succ(Box), + Zero, + } + + assert!(Nat::arbitrary(&mut Unstructured::new(&[])).is_err()); +} + #[derive(Arbitrary, Debug)] struct Generic { inner: T,