diff options
-rw-r--r-- | .cargo_vcs_info.json | 7 | ||||
-rw-r--r-- | Android.bp | 62 | ||||
-rw-r--r-- | CHANGELOG.md | 54 | ||||
-rw-r--r-- | Cargo.toml | 28 | ||||
-rw-r--r-- | Cargo.toml.orig | 4 | ||||
-rw-r--r-- | METADATA | 8 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | TEST_MAPPING | 19 | ||||
-rw-r--r-- | cargo2android.json | 12 | ||||
-rw-r--r-- | patches/Android.bp.patch | 20 | ||||
-rw-r--r-- | src/lib.rs | 192 | ||||
-rw-r--r-- | src/unstructured.rs | 165 | ||||
-rwxr-xr-x[-rw-r--r--] | tests/derive.rs | 4 |
13 files changed, 468 insertions, 109 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index cfe44cd..508adbd 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,6 @@ { "git": { - "sha1": "83b852c9bba47359ad33fc5b1f81f29425bc97e8" - } -} + "sha1": "d0d238d880276fd617c38f7e4712bf40db58aad6" + }, + "path_in_vcs": "" +}
\ No newline at end of file @@ -1,4 +1,4 @@ -// This file is generated by cargo2android.py --run --dependencies --device --features derive --patch patches/Android.bp.patch. +// This file is generated by cargo2android.py --config cargo2android.json. // Do not modify this file as changes will be overridden on upgrade. package { @@ -37,21 +37,67 @@ license { ], } +rust_test { + name: "arbitrary_test_src_lib", + // has rustc warnings + host_supported: true, + crate_name: "arbitrary", + cargo_env_compat: true, + cargo_pkg_version: "1.1.0", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + features: [ + "derive", + "derive_arbitrary", + ], + proc_macros: ["libderive_arbitrary"], +} + +rust_test { + name: "arbitrary_test_tests_derive", + // has rustc warnings + host_supported: true, + crate_name: "arbitrary", + cargo_env_compat: true, + cargo_pkg_version: "1.1.0", + srcs: ["tests/derive.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + features: [ + "derive", + "derive_arbitrary", + ], + rustlibs: [ + "libarbitrary", + ], + proc_macros: ["libderive_arbitrary"], +} + rust_library_rlib { name: "libarbitrary", + // has rustc warnings host_supported: true, crate_name: "arbitrary", + cargo_env_compat: true, + cargo_pkg_version: "1.1.0", srcs: ["src/lib.rs"], edition: "2018", features: [ "derive", + "derive_arbitrary", ], proc_macros: ["libderive_arbitrary"], + apex_available: [ + "com.android.uwb", + ], + min_sdk_version: "Tiramisu", } - -// dependent_library ["feature_list"] -// derive_arbitrary-1.0.0 -// proc-macro2-1.0.26 "default,proc-macro" -// quote-1.0.9 "default,proc-macro" -// syn-1.0.68 "clone-impls,default,derive,parsing,printing,proc-macro,quote" -// unicode-xid-0.2.1 "default" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6542d53..2a7c179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,60 @@ Released YYYY-MM-DD. -------------------------------------------------------------------------------- +## 1.1.0 + +Released 2022-02-09. + +### Added + +* Added the `Unstructured::ratio` method to generate a boolean that is `true` at + the given rate. + +* Added the `Unstructured::arbitrary_loop` method to call a function an + arbitrary number of times. + +-------------------------------------------------------------------------------- + +## 1.0.3 + +Released 2021-11-20. + +### Fixed + +* Fixed documentation for `Unstructured::fill_bytes`. We forgot to update this + way back in [#53](https://github.com/rust-fuzz/arbitrary/pull/53) when the + behavior changed. + +-------------------------------------------------------------------------------- + +## 1.0.2 + +Released 2021-08-25. + +### Added + +* `Arbitrary` impls for `HashMap`s and `HashSet`s with custom `Hasher`s + [#87](https://github.com/rust-fuzz/arbitrary/pull/87) + +-------------------------------------------------------------------------------- + +## 1.0.1 + +Released 2021-05-20. + +### Added + +* `Arbitrary` impls for `NonZeroX` types [#79](https://github.com/rust-fuzz/arbitrary/pull/79) +* `Arbitrary` impls for all arrays using const generics [#55](https://github.com/rust-fuzz/arbitrary/pull/55) +* `Arbitrary` impls for `Ipv4Addr` and `Ipv6Addr` [#84](https://github.com/rust-fuzz/arbitrary/pull/84) + +### Fixed + +* Use fewer bytes for `Unstructured::int_in_range()` [#80](https://github.com/rust-fuzz/arbitrary/pull/80) +* Use correct range for `char` generation [#83](https://github.com/rust-fuzz/arbitrary/pull/83) + +-------------------------------------------------------------------------------- + ## 1.0.0 Released 2020-02-24. @@ -3,24 +3,33 @@ # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "arbitrary" -version = "1.0.0" -authors = ["The Rust-Fuzz Project Developers", "Nick Fitzgerald <fitzgen@gmail.com>", "Manish Goregaokar <manishsmail@gmail.com>", "Simonas Kazlauskas <arbitrary@kazlauskas.me>", "Brian L. Troutwine <brian@troutwine.us>", "Corey Farwell <coreyf@rwell.org>"] +version = "1.1.0" +authors = [ + "The Rust-Fuzz Project Developers", + "Nick Fitzgerald <fitzgen@gmail.com>", + "Manish Goregaokar <manishsmail@gmail.com>", + "Simonas Kazlauskas <arbitrary@kazlauskas.me>", + "Brian L. Troutwine <brian@troutwine.us>", + "Corey Farwell <coreyf@rwell.org>", +] description = "The trait for generating structured data from unstructured data" documentation = "https://docs.rs/arbitrary/" readme = "README.md" -keywords = ["arbitrary", "testing"] +keywords = [ + "arbitrary", + "testing", +] categories = ["development-tools::testing"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/rust-fuzz/arbitrary/" [[example]] @@ -31,6 +40,7 @@ required-features = ["derive"] name = "derive" path = "./tests/derive.rs" required-features = ["derive"] + [dependencies.derive_arbitrary] version = "1.0.0" optional = true diff --git a/Cargo.toml.orig b/Cargo.toml.orig index f177ed4..df4e2a1 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "arbitrary" -version = "1.0.0" # Make sure this matches the derive crate version +version = "1.1.0" # Make sure this matches the derive crate version authors = [ "The Rust-Fuzz Project Developers", "Nick Fitzgerald <fitzgen@gmail.com>", @@ -14,7 +14,7 @@ edition = "2018" keywords = ["arbitrary", "testing"] readme = "README.md" description = "The trait for generating structured data from unstructured data" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/rust-fuzz/arbitrary/" documentation = "https://docs.rs/arbitrary/" @@ -7,13 +7,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/arbitrary/arbitrary-1.0.0.crate" + value: "https://static.crates.io/crates/arbitrary/arbitrary-1.1.0.crate" } - version: "1.0.0" + version: "1.1.0" license_type: NOTICE last_upgrade_date { - year: 2021 - month: 4 + year: 2022 + month: 3 day: 1 } } @@ -10,7 +10,7 @@ ## About -The `Arbitrary` crate lets you construct arbitrary instance of a type. +The `Arbitrary` crate lets you construct arbitrary instances of a type. This crate is primarily intended to be combined with a fuzzer like [libFuzzer and `cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) or diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000..707daa8 --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,19 @@ +// Generated by update_crate_tests.py for tests that depend on this crate. +{ + "presubmit": [ + { + "name": "arbitrary_test_src_lib" + }, + { + "name": "arbitrary_test_tests_derive" + } + ], + "presubmit-rust": [ + { + "name": "arbitrary_test_src_lib" + }, + { + "name": "arbitrary_test_tests_derive" + } + ] +} diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..47d3147 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,12 @@ +{ + "apex_available": [ + "com.android.uwb" + ], + "dependencies": true, + "device": true, + "features": "derive", + "force-rlib": true, + "min_sdk_version": "Tiramisu", + "run": true, + "tests": true +}
\ No newline at end of file diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch deleted file mode 100644 index f6b7c1e..0000000 --- a/patches/Android.bp.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/Android.bp b/Android.bp -index fa383fd..7c098db 100644 ---- a/Android.bp -+++ b/Android.bp -@@ -1,6 +1,6 @@ - // This file is generated by cargo2android.py --run --dependencies --device --features derive --patch patches/Android.bp.patch. - --rust_library { -+rust_library_rlib { - name: "libarbitrary", - host_supported: true, - crate_name: "arbitrary", -@@ -8,7 +8,6 @@ rust_library { - edition: "2018", - features: [ - "derive", -- "derive_arbitrary", - ], - proc_macros: ["libderive_arbitrary"], - } @@ -37,12 +37,16 @@ pub mod size_hint; use core::cell::{Cell, RefCell, UnsafeCell}; use core::iter; use core::mem; +use core::num::{NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize}; +use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; use core::ops::{Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; use core::str; use core::time::Duration; use std::borrow::{Cow, ToOwned}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; use std::ffi::{CString, OsString}; +use std::hash::BuildHasher; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize}; @@ -95,7 +99,7 @@ use std::sync::{Arc, Mutex}; /// Implementing `Arbitrary` mostly involves nested calls to other `Arbitrary` /// arbitrary implementations for each of your `struct` or `enum`'s members. But /// sometimes you need some amount of raw data, or you need to generate a -/// variably-sized collection type, or you something of that sort. The +/// variably-sized collection type, or something of that sort. The /// [`Unstructured`][crate::Unstructured] type helps you with these tasks. /// /// ``` @@ -204,7 +208,7 @@ pub trait Arbitrary<'a>: Sized { /// not a recursive type, or your implementation is not transitively calling /// any other `size_hint` methods, you can ignore the `depth` parameter. /// Note that if you are implementing `Arbitrary` for a generic type, you - /// cannot guarantee the lack of type recrusion! + /// cannot guarantee the lack of type recursion! /// /// Otherwise, you need to use /// [`arbitrary::size_hint::recursion_guard(depth)`][crate::size_hint::recursion_guard] @@ -345,12 +349,13 @@ impl_arbitrary_for_floats! { impl<'a> Arbitrary<'a> for char { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { use std::char; - const CHAR_END: u32 = 0x0011_000; + // The highest unicode code point is 0x11_FFFF + const CHAR_END: u32 = 0x11_0000; // The size of the surrogate blocks const SURROGATES_START: u32 = 0xD800; let mut c = <u32 as Arbitrary<'a>>::arbitrary(u)? % CHAR_END; if let Some(c) = char::from_u32(c) { - return Ok(c); + Ok(c) } else { // We found a surrogate, wrap and try again c -= SURROGATES_START; @@ -571,61 +576,91 @@ macro_rules! arbitrary_tuple { } arbitrary_tuple!(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z); -macro_rules! arbitrary_array { - {$n:expr, ($t:ident, $a:ident) $(($ts:ident, $as:ident))*} => { - arbitrary_array!{($n - 1), $(($ts, $as))*} - - impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for [T; $n] { - fn arbitrary(u: &mut Unstructured<'a>) -> Result<[T; $n]> { - Ok([ - Arbitrary::arbitrary(u)?, - $(<$ts as Arbitrary>::arbitrary(u)?),* - ]) - } - - #[allow(unused_mut)] - fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<[T; $n]> { - $(let $as = $ts::arbitrary(&mut u)?;)* - let last = Arbitrary::arbitrary_take_rest(u)?; +// Helper to safely create arrays since the standard library doesn't +// provide one yet. Shouldn't be necessary in the future. +struct ArrayGuard<T, const N: usize> { + dst: *mut T, + initialized: usize, +} - Ok([ - $($as,)* last - ]) - } +impl<T, const N: usize> Drop for ArrayGuard<T, N> { + fn drop(&mut self) { + debug_assert!(self.initialized <= N); + let initialized_part = core::ptr::slice_from_raw_parts_mut(self.dst, self.initialized); + unsafe { + core::ptr::drop_in_place(initialized_part); + } + } +} - #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and_all(&[ - <$t as Arbitrary>::size_hint(depth), - $( <$ts as Arbitrary>::size_hint(depth) ),* - ]) - } +fn create_array<F, T, const N: usize>(mut cb: F) -> [T; N] +where + F: FnMut(usize) -> T, +{ + let mut array: mem::MaybeUninit<[T; N]> = mem::MaybeUninit::uninit(); + let array_ptr = array.as_mut_ptr(); + let dst = array_ptr as _; + let mut guard: ArrayGuard<T, N> = ArrayGuard { + dst, + initialized: 0, + }; + unsafe { + for (idx, value_ptr) in (&mut *array.as_mut_ptr()).iter_mut().enumerate() { + core::ptr::write(value_ptr, cb(idx)); + guard.initialized += 1; } + mem::forget(guard); + array.assume_init() + } +} + +fn try_create_array<F, T, const N: usize>(mut cb: F) -> Result<[T; N]> +where + F: FnMut(usize) -> Result<T>, +{ + let mut array: mem::MaybeUninit<[T; N]> = mem::MaybeUninit::uninit(); + let array_ptr = array.as_mut_ptr(); + let dst = array_ptr as _; + let mut guard: ArrayGuard<T, N> = ArrayGuard { + dst, + initialized: 0, }; - ($n: expr,) => {}; + unsafe { + for (idx, value_ptr) in (&mut *array.as_mut_ptr()).iter_mut().enumerate() { + core::ptr::write(value_ptr, cb(idx)?); + guard.initialized += 1; + } + mem::forget(guard); + Ok(array.assume_init()) + } } -impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for [T; 0] { - fn arbitrary(_: &mut Unstructured<'a>) -> Result<[T; 0]> { - Ok([]) +impl<'a, T, const N: usize> Arbitrary<'a> for [T; N] +where + T: Arbitrary<'a>, +{ + #[inline] + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + try_create_array(|_| <T as Arbitrary<'a>>::arbitrary(u)) } - fn arbitrary_take_rest(_: Unstructured<'a>) -> Result<[T; 0]> { - Ok([]) + #[inline] + fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<Self> { + let mut array = Self::arbitrary(&mut u)?; + if let Some(last) = array.last_mut() { + *last = Arbitrary::arbitrary_take_rest(u)?; + } + Ok(array) } #[inline] - fn size_hint(_: usize) -> (usize, Option<usize>) { - crate::size_hint::and_all(&[]) + fn size_hint(d: usize) -> (usize, Option<usize>) { + crate::size_hint::and_all(&create_array::<_, (usize, Option<usize>), N>(|_| { + <T as Arbitrary>::size_hint(d) + })) } } -arbitrary_array! { 32, (T, a) (T, b) (T, c) (T, d) (T, e) (T, f) (T, g) (T, h) -(T, i) (T, j) (T, k) (T, l) (T, m) (T, n) (T, o) (T, p) -(T, q) (T, r) (T, s) (T, u) (T, v) (T, w) (T, x) (T, y) -(T, z) (T, aa) (T, ab) (T, ac) (T, ad) (T, ae) (T, af) -(T, ag) } - impl<'a> Arbitrary<'a> for &'a [u8] { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { let len = u.arbitrary_len::<u8>()?; @@ -702,8 +737,8 @@ impl<'a, A: Arbitrary<'a> + Ord> Arbitrary<'a> for BinaryHeap<A> { } } -impl<'a, K: Arbitrary<'a> + Eq + ::std::hash::Hash, V: Arbitrary<'a>> Arbitrary<'a> - for HashMap<K, V> +impl<'a, K: Arbitrary<'a> + Eq + ::std::hash::Hash, V: Arbitrary<'a>, S: BuildHasher + Default> + Arbitrary<'a> for HashMap<K, V, S> { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { u.arbitrary_iter()?.collect() @@ -719,7 +754,9 @@ impl<'a, K: Arbitrary<'a> + Eq + ::std::hash::Hash, V: Arbitrary<'a>> Arbitrary< } } -impl<'a, A: Arbitrary<'a> + Eq + ::std::hash::Hash> Arbitrary<'a> for HashSet<A> { +impl<'a, A: Arbitrary<'a> + Eq + ::std::hash::Hash, S: BuildHasher + Default> Arbitrary<'a> + for HashSet<A, S> +{ fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { u.arbitrary_iter()?.collect() } @@ -872,7 +909,7 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Box<A> { #[inline] fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::recursion_guard(depth, |depth| <A as Arbitrary>::size_hint(depth)) + crate::size_hint::recursion_guard(depth, <A as Arbitrary>::size_hint) } } @@ -918,7 +955,7 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Arc<A> { #[inline] fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::recursion_guard(depth, |depth| <A as Arbitrary>::size_hint(depth)) + crate::size_hint::recursion_guard(depth, <A as Arbitrary>::size_hint) } } @@ -929,7 +966,7 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Rc<A> { #[inline] fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::recursion_guard(depth, |depth| <A as Arbitrary>::size_hint(depth)) + crate::size_hint::recursion_guard(depth, <A as Arbitrary>::size_hint) } } @@ -1010,6 +1047,59 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for ::std::num::Wrapping<A> { } } +macro_rules! implement_nonzero_int { + ($nonzero:ty, $int:ty) => { + impl<'a> Arbitrary<'a> for $nonzero { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + match Self::new(<$int as Arbitrary<'a>>::arbitrary(u)?) { + Some(n) => Ok(n), + None => Err(Error::IncorrectFormat), + } + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option<usize>) { + <$int as Arbitrary<'a>>::size_hint(depth) + } + } + }; +} + +implement_nonzero_int! { NonZeroI8, i8 } +implement_nonzero_int! { NonZeroI16, i16 } +implement_nonzero_int! { NonZeroI32, i32 } +implement_nonzero_int! { NonZeroI64, i64 } +implement_nonzero_int! { NonZeroI128, i128 } +implement_nonzero_int! { NonZeroIsize, isize } +implement_nonzero_int! { NonZeroU8, u8 } +implement_nonzero_int! { NonZeroU16, u16 } +implement_nonzero_int! { NonZeroU32, u32 } +implement_nonzero_int! { NonZeroU64, u64 } +implement_nonzero_int! { NonZeroU128, u128 } +implement_nonzero_int! { NonZeroUsize, usize } + +impl<'a> Arbitrary<'a> for Ipv4Addr { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + Ok(Ipv4Addr::from(u32::arbitrary(u)?)) + } + + #[inline] + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (4, Some(4)) + } +} + +impl<'a> Arbitrary<'a> for Ipv6Addr { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + Ok(Ipv6Addr::from(u128::arbitrary(u)?)) + } + + #[inline] + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (16, Some(16)) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/unstructured.rs b/src/unstructured.rs index 9e2f497..ff3e1f3 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -10,6 +10,7 @@ use crate::{Arbitrary, Error, Result}; use std::marker::PhantomData; +use std::ops::ControlFlow; use std::{mem, ops}; /// A source of unstructured data. @@ -221,7 +222,7 @@ impl<'a> Unstructured<'a> { } fn arbitrary_byte_size(&mut self) -> Result<usize> { - if self.data.len() == 0 { + if self.data.is_empty() { Ok(0) } else if self.data.len() == 1 { self.data = &[]; @@ -322,7 +323,7 @@ impl<'a> Unstructured<'a> { let mut offset: usize = 0; while offset < mem::size_of::<T>() - && (range >> T::Widest::from_usize(offset)) > T::Widest::ZERO + && (range >> T::Widest::from_usize(offset * 8)) > T::Widest::ZERO { let byte = bytes.next().ok_or(Error::NotEnoughData)?; result = (result << 8) | T::Widest::from_u8(byte); @@ -382,6 +383,41 @@ impl<'a> Unstructured<'a> { Ok(&choices[idx]) } + /// Generate a boolean according to the given ratio. + /// + /// # Panics + /// + /// Panics when the numerator and denominator do not meet these constraints: + /// + /// * `0 < numerator <= denominator` + /// + /// # Example + /// + /// Generate a boolean that is `true` five sevenths of the time: + /// + /// ``` + /// # fn foo() -> arbitrary::Result<()> { + /// use arbitrary::Unstructured; + /// + /// # let my_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]; + /// let mut u = Unstructured::new(&my_data); + /// + /// if u.ratio(5, 7)? { + /// // Take this branch 5/7 of the time. + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn ratio<T>(&mut self, numerator: T, denominator: T) -> Result<bool> + where + T: Int, + { + assert!(T::ZERO < numerator); + assert!(numerator <= denominator); + let x = self.int_in_range(T::ONE..=denominator)?; + Ok(x <= numerator) + } + /// Fill a `buffer` with bytes from the underlying raw data. /// /// This should only be called within an `Arbitrary` implementation. This is @@ -389,8 +425,8 @@ impl<'a> Unstructured<'a> { /// `Arbitrary` implementations like `<Vec<u8>>::arbitrary` and /// `String::arbitrary` over using this method directly. /// - /// If this `Unstructured` does not have enough data to fill the whole - /// `buffer`, an error is returned. + /// If this `Unstructured` does not have enough underlying data to fill the + /// whole `buffer`, it pads the buffer out with zeros. /// /// # Example /// @@ -400,16 +436,21 @@ impl<'a> Unstructured<'a> { /// let mut u = Unstructured::new(&[1, 2, 3, 4]); /// /// let mut buf = [0; 2]; + /// + /// assert!(u.fill_buffer(&mut buf).is_ok()); + /// assert_eq!(buf, [1, 2]); + /// /// assert!(u.fill_buffer(&mut buf).is_ok()); + /// assert_eq!(buf, [3, 4]); + /// /// assert!(u.fill_buffer(&mut buf).is_ok()); + /// assert_eq!(buf, [0, 0]); /// ``` pub fn fill_buffer(&mut self, buffer: &mut [u8]) -> Result<()> { let n = std::cmp::min(buffer.len(), self.data.len()); - for i in 0..n { - buffer[i] = self.data[i]; - } - for i in self.data.len()..buffer.len() { - buffer[i] = 0; + buffer[..n].copy_from_slice(&self.data[..n]); + for byte in buffer[n..].iter_mut() { + *byte = 0; } self.data = &self.data[n..]; Ok(()) @@ -518,6 +559,100 @@ impl<'a> Unstructured<'a> { _marker: PhantomData, }) } + + /// Call the given function an arbitrary number of times. + /// + /// The function is given this `Unstructured` so that it can continue to + /// generate arbitrary data and structures. + /// + /// You may optionaly specify minimum and maximum bounds on the number of + /// times the function is called. + /// + /// You may break out of the loop early by returning + /// `Ok(std::ops::ControlFlow::Break)`. To continue the loop, return + /// `Ok(std::ops::ControlFlow::Continue)`. + /// + /// # Panics + /// + /// Panics if `min > max`. + /// + /// # Example + /// + /// Call a closure that generates an arbitrary type inside a context an + /// arbitrary number of times: + /// + /// ``` + /// use arbitrary::{Result, Unstructured}; + /// use std::ops::ControlFlow; + /// + /// enum Type { + /// /// A boolean type. + /// Bool, + /// + /// /// An integer type. + /// Int, + /// + /// /// A list of the `i`th type in this type's context. + /// List(usize), + /// } + /// + /// fn arbitrary_types_context(u: &mut Unstructured) -> Result<Vec<Type>> { + /// let mut context = vec![]; + /// + /// u.arbitrary_loop(Some(10), Some(20), |u| { + /// let num_choices = if context.is_empty() { + /// 2 + /// } else { + /// 3 + /// }; + /// let ty = match u.int_in_range::<u8>(1..=num_choices)? { + /// 1 => Type::Bool, + /// 2 => Type::Int, + /// 3 => Type::List(u.int_in_range(0..=context.len() - 1)?), + /// _ => unreachable!(), + /// }; + /// context.push(ty); + /// Ok(ControlFlow::Continue(())) + /// })?; + /// + /// // The number of loop iterations are constrained by the min/max + /// // bounds that we provided. + /// assert!(context.len() >= 10); + /// assert!(context.len() <= 20); + /// + /// Ok(context) + /// } + /// ``` + pub fn arbitrary_loop( + &mut self, + min: Option<u32>, + max: Option<u32>, + mut f: impl FnMut(&mut Self) -> Result<ControlFlow<(), ()>>, + ) -> Result<()> { + let min = min.unwrap_or(0); + let max = max.unwrap_or(u32::MAX); + assert!(min <= max); + + for _ in 0..min { + match f(self)? { + ControlFlow::Continue(_) => continue, + ControlFlow::Break(_) => return Ok(()), + } + } + + for _ in 0..(max - min) { + let keep_going = self.arbitrary().unwrap_or(false); + if !keep_going { + break; + } + match f(self)? { + ControlFlow::Continue(_) => continue, + ControlFlow::Break(_) => break, + } + } + + Ok(()) + } } /// Utility iterator produced by [`Unstructured::arbitrary_iter`] @@ -701,4 +836,16 @@ mod tests { let choice = *u.choose(&[42]).unwrap(); assert_eq!(choice, 42) } + + #[test] + fn int_in_range_uses_minimal_amount_of_bytes() { + let mut u = Unstructured::new(&[1]); + u.int_in_range::<u8>(0..=u8::MAX).unwrap(); + + let mut u = Unstructured::new(&[1]); + u.int_in_range::<u32>(0..=u8::MAX as u32).unwrap(); + + let mut u = Unstructured::new(&[1]); + u.int_in_range::<u32>(0..=u8::MAX as u32 + 1).unwrap_err(); + } } diff --git a/tests/derive.rs b/tests/derive.rs index 9dfbbd5..adf1188 100644..100755 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -164,7 +164,7 @@ fn one_lifetime() { assert_eq!("abc", lifetime.alpha); let (lower, upper) = <OneLifetime as Arbitrary>::size_hint(0); - assert_eq!(lower, 8); + assert_eq!(lower, std::mem::size_of::<usize>()); assert_eq!(upper, None); } @@ -183,6 +183,6 @@ fn two_lifetimes() { assert_eq!("def", lifetime.beta); let (lower, upper) = <TwoLifetimes as Arbitrary>::size_hint(0); - assert_eq!(lower, 16); + assert_eq!(lower, std::mem::size_of::<usize>() * 2); assert_eq!(upper, None); } |