diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:11:57 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:11:57 +0000 |
commit | f3b2847c1cd3da1a103b4dfbe80f9e01d61c4495 (patch) | |
tree | dc2692d0085b040a1674a3092cf72cedba7404ed | |
parent | c8c757de63969fd5f509ed685b5d012b6590a0f3 (diff) | |
parent | 9d1d38c4a8c5cbb5b69d6d52edfb19ef7d85c651 (diff) | |
download | arbitrary-android14-mainline-sdkext-release.tar.gz |
Snap for 10453563 from 9d1d38c4a8c5cbb5b69d6d52edfb19ef7d85c651 to mainline-sdkext-releaseaml_sdk_341510000aml_sdk_341410000aml_sdk_341110080aml_sdk_341110000aml_sdk_341010000aml_sdk_340912010android14-mainline-sdkext-release
Change-Id: Ic6a852f76746119fdb0cf445c648f9288e6fced8
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | .github/workflows/rust.yml | 26 | ||||
-rw-r--r-- | Android.bp | 11 | ||||
-rw-r--r-- | CHANGELOG.md | 125 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | Cargo.toml.orig | 8 | ||||
-rw-r--r-- | METADATA | 12 | ||||
-rw-r--r-- | README.md | 33 | ||||
-rw-r--r-- | examples/derive_enum.rs | 4 | ||||
-rw-r--r-- | src/error.rs | 15 | ||||
-rw-r--r-- | src/lib.rs | 292 | ||||
-rw-r--r-- | src/unstructured.rs | 324 | ||||
-rw-r--r--[-rwxr-xr-x] | tests/derive.rs | 94 | ||||
-rw-r--r-- | tests/path.rs | 3 |
14 files changed, 786 insertions, 170 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 508adbd..4b94df4 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "d0d238d880276fd617c38f7e4712bf40db58aad6" + "sha1": "98044ba8b0f7d730b500fa3b4f2e96a5edcd926d" }, "path_in_vcs": "" }
\ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 14c2d65..72f3d12 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -7,7 +7,7 @@ jobs: name: Vanilla Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: rustup update - name: Build run: cargo build --verbose --all @@ -17,7 +17,7 @@ jobs: name: All Features Enabled Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: rustup update - name: Build run: cargo build --verbose --all-features --all @@ -25,11 +25,31 @@ jobs: run: cargo test --verbose --all-features --all - name: Build Examples run: cargo build --examples --all-features --all + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update + - run: rustup component add clippy + - run: cargo clippy --all-features --workspace -- -Dclippy::all rustfmt: name: Check rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: rustup update - run: rustup component add rustfmt --toolchain stable - run: cargo +stable fmt --all -- --check + fuzz: + name: Run `int_in_range` fuzz target + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update + - name: "Install nightly" + run: rustup toolchain install nightly && rustup default nightly + - name: "Install `cargo-fuzz`" + run: cargo install cargo-fuzz + - name: "Fuzz for 3 minutes" + run: cargo fuzz run int_in_range -- -max_total_time=$((3 * 60)) @@ -39,11 +39,10 @@ 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", + cargo_pkg_version: "1.2.3", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -60,11 +59,10 @@ rust_test { 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", + cargo_pkg_version: "1.2.3", srcs: ["tests/derive.rs"], test_suites: ["general-tests"], auto_gen_config: true, @@ -84,11 +82,10 @@ rust_test { rust_library_rlib { name: "libarbitrary", - // has rustc warnings host_supported: true, crate_name: "arbitrary", cargo_env_compat: true, - cargo_pkg_version: "1.1.0", + cargo_pkg_version: "1.2.3", srcs: ["src/lib.rs"], edition: "2018", features: [ @@ -99,5 +96,7 @@ rust_library_rlib { apex_available: [ "com.android.uwb", ], + product_available: true, + vendor_available: true, min_sdk_version: "Tiramisu", } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7c179..2d4bb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ Released YYYY-MM-DD. ### Fixed -* TODO (or remove section if none) +* (Included in `arbitrary_derive` 1.2.1) Fixed bug in Derive macro around `no_std` path uses [#131](https://github.com/rust-fuzz/arbitrary/pull/131) ### Security @@ -28,6 +28,129 @@ Released YYYY-MM-DD. -------------------------------------------------------------------------------- +## 1.2.3 + +Released 2023-01-20. + +### Fixed + +* The `derive(Arbitrary)` will now annotate the generated `impl`s with a `#[automatically_derived]` + attribute to indicate to e.g. clippy that lints should not fire for the code within the derived + implementation. + +## 1.2.2 + +Released 2023-01-03. + +### Fixed + +* Ensured that `arbitrary` and `derive_arbitrary` versions are synced up so that + they don't, e.g., emit generated code that depends on newer versions of + `arbitrary` than the one currently in + use. [#134](https://github.com/rust-fuzz/arbitrary/issues/134) + +## 1.2.1 + +### Fixed + +* Fixed an issue where `std::thread_local!` macro invocations in derive code + were not fully prefixed, causing confusing build errors in certain situations. + +## 1.2.0 + +Released 2022-10-20. + +### Added + +* Support custom arbitrary implementation for fields on + derive. [#129](https://github.com/rust-fuzz/arbitrary/pull/129) + +-------------------------------------------------------------------------------- + +## 1.1.6 + +Released 2022-09-08. + +### Fixed + +* Fixed a potential panic due to an off-by-one error in the `Arbitrary` + implementation for `std::ops::Bound<T>`. + +-------------------------------------------------------------------------------- + +## 1.1.5 + +Released 2022-09-20. + +### Added + +* Implemented `Arbitrary` for `std::ops::Bound<T>`. + +### Fixed + +* Fixed a bug where `Unstructured::int_in_range` could return out-of-range + integers when generating arbitrary signed integers. + +-------------------------------------------------------------------------------- + +## 1.1.4 + +Released 2022-08-29. + +### Added + +* Implemented `Arbitrary` for `Rc<str>` and `Arc<str>` + +### Changed + +* Allow overriding the error type in `arbitrary::Result` +* The `Unstructured::arbitrary_loop` method will consume fewer bytes of input + now. + +### Fixed + +* Fixed a bug where `Unstructured::int_in_range` could return out-of-range + integers. + +-------------------------------------------------------------------------------- + +## 1.1.3 + +Released 2022-06-23. + +### Fixed + +* Fixed some potential (but highly unlikely) name-clashes inside + `derive(Arbitrary)`'s generated + code. [#111](https://github.com/rust-fuzz/arbitrary/pull/111) +* Fixed an edge case where `derive(Arbitrary)` for recursive types that detected + an overflow would not reset the overflow + detection. [#111](https://github.com/rust-fuzz/arbitrary/pull/111) + +-------------------------------------------------------------------------------- + +## 1.1.2 + +Released 2022-06-16. + +### Fixed + +* Fixed a warning inside `derive(Arbitrary)`-generated + code. [#110](https://github.com/rust-fuzz/arbitrary/pull/110) + +-------------------------------------------------------------------------------- + +## 1.1.1 + +Released 2022-06-14. + +### Fixed + +* Fixed a stack overflow when using `derive(Arbitrary)` with recursive types and + empty inputs. [#109](https://github.com/rust-fuzz/arbitrary/pull/109) + +-------------------------------------------------------------------------------- + ## 1.1.0 Released 2022-02-09. @@ -11,8 +11,9 @@ [package] edition = "2018" +rust-version = "1.63.0" name = "arbitrary" -version = "1.1.0" +version = "1.2.3" authors = [ "The Rust-Fuzz Project Developers", "Nick Fitzgerald <fitzgen@gmail.com>", @@ -42,10 +43,8 @@ path = "./tests/derive.rs" required-features = ["derive"] [dependencies.derive_arbitrary] -version = "1.0.0" +version = "1.2.3" optional = true -[dev-dependencies] - [features] derive = ["derive_arbitrary"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index df4e2a1..ed72b4c 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "arbitrary" -version = "1.1.0" # Make sure this matches the derive crate version +version = "1.2.3" # Make sure this matches the derive crate version (not including the patch version) authors = [ "The Rust-Fuzz Project Developers", "Nick Fitzgerald <fitzgen@gmail.com>", @@ -17,11 +17,10 @@ description = "The trait for generating structured data from unstructured data" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-fuzz/arbitrary/" documentation = "https://docs.rs/arbitrary/" +rust-version = "1.63.0" [dependencies] -derive_arbitrary = { version = "1.0.0", path = "./derive", optional = true } - -[dev-dependencies] +derive_arbitrary = { version = "1.2.3", path = "./derive", optional = true } [features] # Turn this feature on to enable support for `#[derive(Arbitrary)]`. @@ -37,3 +36,4 @@ path = "./tests/derive.rs" required-features = ["derive"] [workspace] +members = ["./fuzz"] @@ -1,3 +1,7 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update rust/crates/arbitrary +# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md + name: "arbitrary" description: "The trait for generating structured data from unstructured data" third_party { @@ -7,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/arbitrary/arbitrary-1.1.0.crate" + value: "https://static.crates.io/crates/arbitrary/arbitrary-1.2.3.crate" } - version: "1.1.0" + version: "1.2.3" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 3 + year: 2023 + month: 2 day: 1 } } @@ -61,6 +61,39 @@ pub struct Rgb { } ``` +#### Customizing single fields + +This can be particular handy if your structure uses a type that does not implement `Arbitrary` or you want to have more customization for particular fields. + +```rust +#[derive(Arbitrary)] +pub struct Rgba { + // set `r` to Default::default() + #[arbitrary(default)] + pub r: u8, + + // set `g` to 255 + #[arbitrary(value = 255)] + pub g: u8, + + // Generate `b` with a custom function of type + // + // fn(&mut Unstructured) -> arbitrary::Result<T> + // + // where `T` is the field's type. + #[arbitrary(with = arbitrary_b)] + pub b: u8, + + // Generate `a` with a custom closure (shortuct to avoid a custom funciton) + #[arbitrary(with = |u: &mut Unstructured| u.int_in_range(0..=64))] + pub a: u8, +} + +fn arbitrary_b(u: &mut Unstructured) -> arbitrary::Result<u8> { + u.int_in_range(64..=128) +} +``` + ### Implementing `Arbitrary` By Hand Alternatively, you can write an `Arbitrary` implementation by hand: diff --git a/examples/derive_enum.rs b/examples/derive_enum.rs index fbcc106..c4fc9c9 100644 --- a/examples/derive_enum.rs +++ b/examples/derive_enum.rs @@ -2,6 +2,10 @@ //! //! Note that this requires enabling the "derive" cargo feature. +// Various enums/fields that we are deriving `Arbitrary` for aren't actually +// used except to show off the derive. +#![allow(dead_code)] + use arbitrary::{Arbitrary, Unstructured}; #[derive(Arbitrary, Debug)] diff --git a/src/error.rs b/src/error.rs index f590c12..1d3df2a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,4 +37,17 @@ impl error::Error for Error {} /// A `Result` with the error type fixed as `arbitrary::Error`. /// /// Either an `Ok(T)` or `Err(arbitrary::Error)`. -pub type Result<T> = std::result::Result<T, Error>; +pub type Result<T, E = Error> = std::result::Result<T, E>; + +#[cfg(test)] +mod tests { + // Often people will import our custom `Result` type because 99.9% of + // results in a file will be `arbitrary::Result` but then have that one last + // 0.1% that want to have a custom error type. Don't make them prefix that + // 0.1% as `std::result::Result`; instead, let `arbitrary::Result` have an + // overridable error type. + #[test] + fn can_use_custom_error_types_with_result() -> super::Result<(), String> { + Ok(()) + } +} @@ -34,6 +34,7 @@ pub use unstructured::Unstructured; pub mod size_hint; +use core::array; use core::cell::{Cell, RefCell, UnsafeCell}; use core::iter; use core::mem; @@ -47,6 +48,7 @@ use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedL use std::ffi::{CString, OsString}; use std::hash::BuildHasher; use std::net::{Ipv4Addr, Ipv6Addr}; +use std::ops::Bound; use std::path::PathBuf; use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize}; @@ -138,14 +140,17 @@ pub trait Arbitrary<'a>: Sized { /// perhaps given to you by a fuzzer like AFL or libFuzzer. You wrap this /// raw data in an `Unstructured`, and then you can call `<MyType as /// Arbitrary>::arbitrary` to construct an arbitrary instance of `MyType` - /// from that unstuctured data. + /// from that unstructured data. /// - /// Implementation may return an error if there is not enough data to - /// construct a full instance of `Self`. This is generally OK: it is better - /// to exit early and get the fuzzer to provide more input data, than it is - /// to generate default values in place of the missing data, which would - /// bias the distribution of generated values, and ultimately make fuzzing - /// less efficient. + /// Implementations may return an error if there is not enough data to + /// construct a full instance of `Self`, or they may fill out the rest of + /// `Self` with dummy values. Using dummy values when the underlying data is + /// exhausted can help avoid accidentally "defeating" some of the fuzzer's + /// mutations to the underlying byte stream that might otherwise lead to + /// interesting runtime behavior or new code coverage if only we had just a + /// few more bytes. However, it also requires that implementations for + /// recursive types (e.g. `struct Foo(Option<Box<Foo>>)`) avoid infinite + /// recursion when the underlying data is exhausted. /// /// ``` /// # #[cfg(feature = "derive")] fn foo() { @@ -174,11 +179,13 @@ pub trait Arbitrary<'a>: Sized { /// See also the documentation for [`Unstructured`][crate::Unstructured]. fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self>; - /// Generate an arbitrary value of `Self` from the entirety of the given unstructured data. + /// Generate an arbitrary value of `Self` from the entirety of the given + /// unstructured data. /// - /// This is similar to Arbitrary::arbitrary, however it assumes that it is the - /// last consumer of the given data, and is thus able to consume it all if it needs. - /// See also the documentation for [`Unstructured`][crate::Unstructured]. + /// This is similar to Arbitrary::arbitrary, however it assumes that it is + /// the last consumer of the given data, and is thus able to consume it all + /// if it needs. See also the documentation for + /// [`Unstructured`][crate::Unstructured]. fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<Self> { Self::arbitrary(&mut u) } @@ -202,6 +209,17 @@ pub trait Arbitrary<'a>: Sized { /// default with a better implementation. The /// [`size_hint`][crate::size_hint] module will help with this task. /// + /// ## Invariant + /// + /// It must be possible to construct every possible output using only inputs + /// of lengths bounded by these parameters. This applies to both + /// [`Arbitrary::arbitrary`] and [`Arbitrary::arbitrary_take_rest`]. + /// + /// This is trivially true for `(0, None)`. To restrict this further, it + /// must be proven that all inputs that are now excluded produced redundant + /// outputs which are still possible to produce using the reduced input + /// space. + /// /// ## The `depth` Parameter /// /// If you 100% know that the type you are implementing `Arbitrary` for is @@ -593,27 +611,6 @@ impl<T, const N: usize> Drop for ArrayGuard<T, N> { } } -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>, @@ -626,7 +623,7 @@ where initialized: 0, }; unsafe { - for (idx, value_ptr) in (&mut *array.as_mut_ptr()).iter_mut().enumerate() { + for (idx, value_ptr) in (*array.as_mut_ptr()).iter_mut().enumerate() { core::ptr::write(value_ptr, cb(idx)?); guard.initialized += 1; } @@ -655,7 +652,7 @@ where #[inline] fn size_hint(d: usize) -> (usize, Option<usize>) { - crate::size_hint::and_all(&create_array::<_, (usize, Option<usize>), N>(|_| { + crate::size_hint::and_all(&array::from_fn::<_, N, _>(|_| { <T as Arbitrary>::size_hint(d) })) } @@ -672,8 +669,8 @@ impl<'a> Arbitrary<'a> for &'a [u8] { } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - <usize as Arbitrary>::size_hint(depth) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -687,8 +684,8 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Vec<A> { } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -702,8 +699,8 @@ impl<'a, K: Arbitrary<'a> + Ord, V: Arbitrary<'a>> Arbitrary<'a> for BTreeMap<K, } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -717,8 +714,27 @@ impl<'a, A: Arbitrary<'a> + Ord> Arbitrary<'a> for BTreeSet<A> { } #[inline] + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) + } +} + +impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Bound<A> { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + match u.int_in_range::<u8>(0..=2)? { + 0 => Ok(Bound::Included(A::arbitrary(u)?)), + 1 => Ok(Bound::Excluded(A::arbitrary(u)?)), + 2 => Ok(Bound::Unbounded), + _ => unreachable!(), + } + } + + #[inline] fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + size_hint::or( + size_hint::and((1, Some(1)), A::size_hint(depth)), + (1, Some(1)), + ) } } @@ -732,8 +748,8 @@ impl<'a, A: Arbitrary<'a> + Ord> Arbitrary<'a> for BinaryHeap<A> { } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -749,8 +765,8 @@ impl<'a, K: Arbitrary<'a> + Eq + ::std::hash::Hash, V: Arbitrary<'a>, S: BuildHa } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -766,8 +782,8 @@ impl<'a, A: Arbitrary<'a> + Eq + ::std::hash::Hash, S: BuildHasher + Default> Ar } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -781,8 +797,8 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for LinkedList<A> { } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -796,8 +812,8 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for VecDeque<A> { } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -821,7 +837,7 @@ where impl<'a> Arbitrary<'a> for &'a str { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { let size = u.arbitrary_len::<u8>()?; - match str::from_utf8(&u.peek_bytes(size).unwrap()) { + match str::from_utf8(u.peek_bytes(size).unwrap()) { Ok(s) => { u.bytes(size).unwrap(); Ok(s) @@ -840,14 +856,12 @@ impl<'a> Arbitrary<'a> for &'a str { fn arbitrary_take_rest(u: Unstructured<'a>) -> Result<Self> { let bytes = u.take_rest(); - str::from_utf8(bytes) - .map_err(|_| Error::IncorrectFormat) - .map(Into::into) + str::from_utf8(bytes).map_err(|_| Error::IncorrectFormat) } #[inline] - fn size_hint(depth: usize) -> (usize, Option<usize>) { - crate::size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None)) + fn size_hint(_depth: usize) -> (usize, Option<usize>) { + (0, None) } } @@ -959,6 +973,17 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Arc<A> { } } +impl<'a> Arbitrary<'a> for Arc<str> { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + <&str as Arbitrary>::arbitrary(u).map(Into::into) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option<usize>) { + <&str as Arbitrary>::size_hint(depth) + } +} + impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Rc<A> { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { Arbitrary::arbitrary(u).map(Self::new) @@ -970,6 +995,17 @@ impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Rc<A> { } } +impl<'a> Arbitrary<'a> for Rc<str> { + fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { + <&str as Arbitrary>::arbitrary(u).map(Into::into) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option<usize>) { + <&str as Arbitrary>::size_hint(depth) + } +} + impl<'a, A: Arbitrary<'a>> Arbitrary<'a> for Cell<A> { fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { Arbitrary::arbitrary(u).map(Self::new) @@ -1104,6 +1140,56 @@ impl<'a> Arbitrary<'a> for Ipv6Addr { mod test { use super::*; + /// Generates an arbitrary `T`, and checks that the result is consistent with the + /// `size_hint()` reported by `T`. + fn checked_arbitrary<'a, T: Arbitrary<'a>>(u: &mut Unstructured<'a>) -> Result<T> { + let (min, max) = T::size_hint(0); + + let len_before = u.len(); + let result = T::arbitrary(u); + + let consumed = len_before - u.len(); + + if let Some(max) = max { + assert!( + consumed <= max, + "incorrect maximum size: indicated {}, actually consumed {}", + max, + consumed + ); + } + + if result.is_ok() { + assert!( + consumed >= min, + "incorrect minimum size: indicated {}, actually consumed {}", + min, + consumed + ); + } + + result + } + + /// Like `checked_arbitrary()`, but calls `arbitrary_take_rest()` instead of `arbitrary()`. + fn checked_arbitrary_take_rest<'a, T: Arbitrary<'a>>(u: Unstructured<'a>) -> Result<T> { + let (min, _) = T::size_hint(0); + + let len_before = u.len(); + let result = T::arbitrary_take_rest(u); + + if result.is_ok() { + assert!( + len_before >= min, + "incorrect minimum size: indicated {}, worked with {}", + min, + len_before + ); + } + + result + } + #[test] fn finite_buffer_fill_buffer() { let x = [1, 2, 3, 4]; @@ -1122,7 +1208,7 @@ mod test { let x = [1, 2, 3, 4]; let mut buf = Unstructured::new(&x); let expected = 1 | (2 << 8) | (3 << 16) | (4 << 24); - let actual = i32::arbitrary(&mut buf).unwrap(); + let actual = checked_arbitrary::<i32>(&mut buf).unwrap(); assert_eq!(expected, actual); } @@ -1131,7 +1217,7 @@ mod test { let x = [1, 2, 3, 4, 4]; let mut buf = Unstructured::new(&x); let expected = &[1, 2, 3, 4]; - let actual = <&[u8] as Arbitrary>::arbitrary(&mut buf).unwrap(); + let actual = checked_arbitrary::<&[u8]>(&mut buf).unwrap(); assert_eq!(expected, actual); } @@ -1140,26 +1226,30 @@ mod test { let x = [1, 2, 3, 4]; let buf = Unstructured::new(&x); let expected = &[1, 2, 3, 4]; - let actual = <&[u8] as Arbitrary>::arbitrary_take_rest(buf).unwrap(); + let actual = checked_arbitrary_take_rest::<&[u8]>(buf).unwrap(); assert_eq!(expected, actual); } #[test] fn arbitrary_collection() { let x = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 12, ]; assert_eq!( - Vec::<u8>::arbitrary(&mut Unstructured::new(&x)).unwrap(), + checked_arbitrary::<&[u8]>(&mut Unstructured::new(&x)).unwrap(), + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3] + ); + assert_eq!( + checked_arbitrary::<Vec<u8>>(&mut Unstructured::new(&x)).unwrap(), &[2, 4, 6, 8, 1] ); assert_eq!( - Vec::<u32>::arbitrary(&mut Unstructured::new(&x)).unwrap(), + checked_arbitrary::<Vec<u32>>(&mut Unstructured::new(&x)).unwrap(), &[84148994] ); assert_eq!( - String::arbitrary(&mut Unstructured::new(&x)).unwrap(), - "\x01\x02\x03\x04\x05\x06\x07\x08" + checked_arbitrary::<String>(&mut Unstructured::new(&x)).unwrap(), + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x01\x02\x03" ); } @@ -1167,17 +1257,30 @@ mod test { fn arbitrary_take_rest() { let x = [1, 2, 3, 4]; assert_eq!( - Vec::<u8>::arbitrary_take_rest(Unstructured::new(&x)).unwrap(), + checked_arbitrary_take_rest::<&[u8]>(Unstructured::new(&x)).unwrap(), + &[1, 2, 3, 4] + ); + assert_eq!( + checked_arbitrary_take_rest::<Vec<u8>>(Unstructured::new(&x)).unwrap(), &[1, 2, 3, 4] ); assert_eq!( - Vec::<u32>::arbitrary_take_rest(Unstructured::new(&x)).unwrap(), + checked_arbitrary_take_rest::<Vec<u32>>(Unstructured::new(&x)).unwrap(), &[0x4030201] ); assert_eq!( - String::arbitrary_take_rest(Unstructured::new(&x)).unwrap(), + checked_arbitrary_take_rest::<String>(Unstructured::new(&x)).unwrap(), "\x01\x02\x03\x04" ); + + assert_eq!( + checked_arbitrary_take_rest::<&[u8]>(Unstructured::new(&[])).unwrap(), + &[] + ); + assert_eq!( + checked_arbitrary_take_rest::<Vec<u8>>(Unstructured::new(&[])).unwrap(), + &[] + ); } #[test] @@ -1186,9 +1289,54 @@ mod test { (7, Some(7)), <(bool, u16, i32) as Arbitrary<'_>>::size_hint(0) ); - assert_eq!( - (1 + mem::size_of::<usize>(), None), - <(u8, Vec<u8>) as Arbitrary>::size_hint(0) - ); + assert_eq!((1, None), <(u8, Vec<u8>) as Arbitrary>::size_hint(0)); } } + +/// Multiple conflicting arbitrary attributes are used on the same field: +/// ```compile_fail +/// #[derive(::arbitrary::Arbitrary)] +/// struct Point { +/// #[arbitrary(value = 2)] +/// #[arbitrary(value = 2)] +/// x: i32, +/// } +/// ``` +/// +/// An unknown attribute: +/// ```compile_fail +/// #[derive(::arbitrary::Arbitrary)] +/// struct Point { +/// #[arbitrary(unknown_attr)] +/// x: i32, +/// } +/// ``` +/// +/// An unknown attribute with a value: +/// ```compile_fail +/// #[derive(::arbitrary::Arbitrary)] +/// struct Point { +/// #[arbitrary(unknown_attr = 13)] +/// x: i32, +/// } +/// ``` +/// +/// `value` without RHS: +/// ```compile_fail +/// #[derive(::arbitrary::Arbitrary)] +/// struct Point { +/// #[arbitrary(value)] +/// x: i32, +/// } +/// ``` +/// +/// `with` without RHS: +/// ```compile_fail +/// #[derive(::arbitrary::Arbitrary)] +/// struct Point { +/// #[arbitrary(with)] +/// x: i32, +/// } +/// ``` +#[cfg(all(doctest, feature = "derive"))] +pub struct CompileFailTests; diff --git a/src/unstructured.rs b/src/unstructured.rs index ff3e1f3..0bfdff2 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -216,7 +216,7 @@ impl<'a> Unstructured<'a> { { let byte_size = self.arbitrary_byte_size()?; let (lower, upper) = <ElementType as Arbitrary>::size_hint(0); - let elem_size = upper.unwrap_or_else(|| lower * 2); + let elem_size = upper.unwrap_or(lower * 2); let elem_size = std::cmp::max(1, elem_size); Ok(byte_size / elem_size) } @@ -236,19 +236,20 @@ impl<'a> Unstructured<'a> { // We only consume as many bytes as necessary to cover the entire // range of the byte string. - let len = if self.data.len() <= std::u8::MAX as usize + 1 { + // Note: We cast to u64 so we don't overflow when checking std::u32::MAX + 4 on 32-bit archs + let len = if self.data.len() as u64 <= std::u8::MAX as u64 + 1 { let bytes = 1; let max_size = self.data.len() - bytes; let (rest, for_size) = self.data.split_at(max_size); self.data = rest; Self::int_in_range_impl(0..=max_size as u8, for_size.iter().copied())?.0 as usize - } else if self.data.len() <= std::u16::MAX as usize + 1 { + } else if self.data.len() as u64 <= std::u16::MAX as u64 + 2 { let bytes = 2; let max_size = self.data.len() - bytes; let (rest, for_size) = self.data.split_at(max_size); self.data = rest; Self::int_in_range_impl(0..=max_size as u16, for_size.iter().copied())?.0 as usize - } else if self.data.len() <= std::u32::MAX as usize + 1 { + } else if self.data.len() as u64 <= std::u32::MAX as u64 + 4 { let bytes = 4; let max_size = self.data.len() - bytes; let (rest, for_size) = self.data.split_at(max_size); @@ -273,7 +274,7 @@ impl<'a> Unstructured<'a> { /// /// # Panics /// - /// Panics if `range.start >= range.end`. That is, the given range must be + /// Panics if `range.start > range.end`. That is, the given range must be /// non-empty. /// /// # Example @@ -305,8 +306,8 @@ impl<'a> Unstructured<'a> { where T: Int, { - let start = range.start(); - let end = range.end(); + let start = *range.start(); + let end = *range.end(); assert!( start <= end, "`arbitrary::Unstructured::int_in_range` requires a non-empty range" @@ -315,30 +316,59 @@ impl<'a> Unstructured<'a> { // When there is only one possible choice, don't waste any entropy from // the underlying data. if start == end { - return Ok((*start, 0)); + return Ok((start, 0)); } - let range: T::Widest = end.as_widest() - start.as_widest(); - let mut result = T::Widest::ZERO; - let mut offset: usize = 0; + // From here on out we work with the unsigned representation. All of the + // operations performed below work out just as well whether or not `T` + // is a signed or unsigned integer. + let start = start.to_unsigned(); + let end = end.to_unsigned(); + + let delta = end.wrapping_sub(start); + debug_assert_ne!(delta, T::Unsigned::ZERO); - while offset < mem::size_of::<T>() - && (range >> T::Widest::from_usize(offset * 8)) > T::Widest::ZERO + // Compute an arbitrary integer offset from the start of the range. We + // do this by consuming `size_of(T)` bytes from the input to create an + // arbitrary integer and then clamping that int into our range bounds + // with a modulo operation. + let mut arbitrary_int = T::Unsigned::ZERO; + let mut bytes_consumed: usize = 0; + + while (bytes_consumed < mem::size_of::<T>()) + && (delta >> T::Unsigned::from_usize(bytes_consumed * 8)) > T::Unsigned::ZERO { - let byte = bytes.next().ok_or(Error::NotEnoughData)?; - result = (result << 8) | T::Widest::from_u8(byte); - offset += 1; - } + let byte = match bytes.next() { + None => break, + Some(b) => b, + }; + bytes_consumed += 1; - // Avoid division by zero. - if let Some(range) = range.checked_add(T::Widest::ONE) { - result = result % range; + // Combine this byte into our arbitrary integer, but avoid + // overflowing the shift for `u8` and `i8`. + arbitrary_int = if mem::size_of::<T>() == 1 { + T::Unsigned::from_u8(byte) + } else { + (arbitrary_int << 8) | T::Unsigned::from_u8(byte) + }; } - Ok(( - T::from_widest(start.as_widest().wrapping_add(result)), - offset, - )) + let offset = if delta == T::Unsigned::MAX { + arbitrary_int + } else { + arbitrary_int % (delta.checked_add(T::Unsigned::ONE).unwrap()) + }; + + // Finally, we add `start` to our offset from `start` to get the result + // actual value within the range. + let result = start.wrapping_add(offset); + + // And convert back to our maybe-signed representation. + let result = T::from_unsigned(result); + debug_assert!(*range.start() <= result); + debug_assert!(result <= *range.end()); + + Ok((result, bytes_consumed)) } /// Choose one of the given choices. @@ -376,11 +406,53 @@ impl<'a> Unstructured<'a> { /// assert!(result.is_err()); /// ``` pub fn choose<'b, T>(&mut self, choices: &'b [T]) -> Result<&'b T> { - if choices.is_empty() { + let idx = self.choose_index(choices.len())?; + Ok(&choices[idx]) + } + + /// Choose a value in `0..len`. + /// + /// Returns an error if the `len` is zero. + /// + /// # Examples + /// + /// Using Fisher–Yates shuffle shuffle to gerate an arbitrary permutation. + /// + /// [Fisher–Yates shuffle]: https://en.wikipedia.org/wiki/Fisher–Yates_shuffle + /// + /// ``` + /// use arbitrary::Unstructured; + /// + /// let mut u = Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + /// let mut permutation = ['a', 'b', 'c', 'd', 'e', 'f', 'g']; + /// let mut to_permute = &mut permutation[..]; + /// while to_permute.len() > 1 { + /// let idx = u.choose_index(to_permute.len()).unwrap(); + /// to_permute.swap(0, idx); + /// to_permute = &mut to_permute[1..]; + /// } + /// + /// println!("permutation: {:?}", permutation); + /// ``` + /// + /// An error is returned if the length is zero: + /// + /// ``` + /// use arbitrary::Unstructured; + /// + /// let mut u = Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); + /// let array: [i32; 0] = []; + /// + /// let result = u.choose_index(array.len()); + /// + /// assert!(result.is_err()); + /// ``` + pub fn choose_index(&mut self, len: usize) -> Result<usize> { + if len == 0 { return Err(Error::EmptyChoose); } - let idx = self.int_in_range(0..=choices.len() - 1)?; - Ok(&choices[idx]) + let idx = self.int_in_range(0..=len - 1)?; + Ok(idx) } /// Generate a boolean according to the given ratio. @@ -524,7 +596,7 @@ impl<'a> Unstructured<'a> { /// assert_eq!(remaining, [1, 2, 3]); /// ``` pub fn take_rest(mut self) -> &'a [u8] { - mem::replace(&mut self.data, &[]) + mem::take(&mut self.data) } /// Provide an iterator over elements for constructing a collection @@ -631,20 +703,8 @@ impl<'a> Unstructured<'a> { ) -> 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; - } + for _ in 0..self.int_in_range(min..=max)? { match f(self)? { ControlFlow::Continue(_) => continue, ControlFlow::Break(_) => break, @@ -718,6 +778,7 @@ impl<'a, ElementType: Arbitrary<'a>> Iterator for ArbitraryTakeRestIter<'a, Elem /// Don't implement this trait yourself. pub trait Int: Copy + + std::fmt::Debug + PartialOrd + Ord + ops::Sub<Self, Output = Self> @@ -727,7 +788,7 @@ pub trait Int: + ops::BitOr<Self, Output = Self> { #[doc(hidden)] - type Widest: Int; + type Unsigned: Int; #[doc(hidden)] const ZERO: Self; @@ -736,10 +797,7 @@ pub trait Int: const ONE: Self; #[doc(hidden)] - fn as_widest(self) -> Self::Widest; - - #[doc(hidden)] - fn from_widest(w: Self::Widest) -> Self; + const MAX: Self; #[doc(hidden)] fn from_u8(b: u8) -> Self; @@ -752,26 +810,28 @@ pub trait Int: #[doc(hidden)] fn wrapping_add(self, rhs: Self) -> Self; + + #[doc(hidden)] + fn wrapping_sub(self, rhs: Self) -> Self; + + #[doc(hidden)] + fn to_unsigned(self) -> Self::Unsigned; + + #[doc(hidden)] + fn from_unsigned(unsigned: Self::Unsigned) -> Self; } macro_rules! impl_int { - ( $( $ty:ty : $widest:ty ; )* ) => { + ( $( $ty:ty : $unsigned_ty: ty ; )* ) => { $( impl Int for $ty { - type Widest = $widest; + type Unsigned = $unsigned_ty; const ZERO: Self = 0; const ONE: Self = 1; - fn as_widest(self) -> Self::Widest { - self as $widest - } - - fn from_widest(w: Self::Widest) -> Self { - let x = <$ty>::max_value().as_widest(); - (w % x) as Self - } + const MAX: Self = Self::MAX; fn from_u8(b: u8) -> Self { b as Self @@ -788,24 +848,36 @@ macro_rules! impl_int { fn wrapping_add(self, rhs: Self) -> Self { <$ty>::wrapping_add(self, rhs) } + + fn wrapping_sub(self, rhs: Self) -> Self { + <$ty>::wrapping_sub(self, rhs) + } + + fn to_unsigned(self) -> Self::Unsigned { + self as $unsigned_ty + } + + fn from_unsigned(unsigned: $unsigned_ty) -> Self { + unsigned as Self + } } )* } } impl_int! { - u8: u128; - u16: u128; - u32: u128; - u64: u128; + u8: u8; + u16: u16; + u32: u32; + u64: u64; u128: u128; - usize: u128; - i8: i128; - i16: i128; - i32: i128; - i64: i128; - i128: i128; - isize: i128; + usize: usize; + i8: u8; + i16: u16; + i32: u32; + i64: u64; + i128: u128; + isize: usize; } #[cfg(test)] @@ -839,13 +911,121 @@ mod tests { #[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, 2]); + assert_eq!(1, u.int_in_range::<u8>(0..=u8::MAX).unwrap()); + assert_eq!(u.len(), 1); - let mut u = Unstructured::new(&[1]); - u.int_in_range::<u32>(0..=u8::MAX as u32).unwrap(); + let mut u = Unstructured::new(&[1, 2]); + assert_eq!(1, u.int_in_range::<u32>(0..=u8::MAX as u32).unwrap()); + assert_eq!(u.len(), 1); let mut u = Unstructured::new(&[1]); - u.int_in_range::<u32>(0..=u8::MAX as u32 + 1).unwrap_err(); + assert_eq!(1, u.int_in_range::<u32>(0..=u8::MAX as u32 + 1).unwrap()); + assert!(u.is_empty()); + } + + #[test] + fn int_in_range_in_bounds() { + for input in u8::MIN..=u8::MAX { + let input = [input]; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(1..=u8::MAX).unwrap(); + assert_ne!(x, 0); + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(0..=u8::MAX - 1).unwrap(); + assert_ne!(x, u8::MAX); + } + } + + #[test] + fn int_in_range_covers_unsigned_range() { + // Test that we generate all values within the range given to + // `int_in_range`. + + let mut full = [false; u8::MAX as usize + 1]; + let mut no_zero = [false; u8::MAX as usize]; + let mut no_max = [false; u8::MAX as usize]; + let mut narrow = [false; 10]; + + for input in u8::MIN..=u8::MAX { + let input = [input]; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(0..=u8::MAX).unwrap(); + full[x as usize] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(1..=u8::MAX).unwrap(); + no_zero[x as usize - 1] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(0..=u8::MAX - 1).unwrap(); + no_max[x as usize] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(100..=109).unwrap(); + narrow[x as usize - 100] = true; + } + + for (i, covered) in full.iter().enumerate() { + assert!(covered, "full[{}] should have been generated", i); + } + for (i, covered) in no_zero.iter().enumerate() { + assert!(covered, "no_zero[{}] should have been generated", i); + } + for (i, covered) in no_max.iter().enumerate() { + assert!(covered, "no_max[{}] should have been generated", i); + } + for (i, covered) in narrow.iter().enumerate() { + assert!(covered, "narrow[{}] should have been generated", i); + } + } + + #[test] + fn int_in_range_covers_signed_range() { + // Test that we generate all values within the range given to + // `int_in_range`. + + let mut full = [false; u8::MAX as usize + 1]; + let mut no_min = [false; u8::MAX as usize]; + let mut no_max = [false; u8::MAX as usize]; + let mut narrow = [false; 21]; + + let abs_i8_min: isize = 128; + + for input in 0..=u8::MAX { + let input = [input]; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(i8::MIN..=i8::MAX).unwrap(); + full[(x as isize + abs_i8_min) as usize] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(i8::MIN + 1..=i8::MAX).unwrap(); + no_min[(x as isize + abs_i8_min - 1) as usize] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(i8::MIN..=i8::MAX - 1).unwrap(); + no_max[(x as isize + abs_i8_min) as usize] = true; + + let mut u = Unstructured::new(&input); + let x = u.int_in_range(-10..=10).unwrap(); + narrow[(x as isize + 10) as usize] = true; + } + + for (i, covered) in full.iter().enumerate() { + assert!(covered, "full[{}] should have been generated", i); + } + for (i, covered) in no_min.iter().enumerate() { + assert!(covered, "no_min[{}] should have been generated", i); + } + for (i, covered) in no_max.iter().enumerate() { + assert!(covered, "no_max[{}] should have been generated", i); + } + for (i, covered) in narrow.iter().enumerate() { + assert!(covered, "narrow[{}] should have been generated", i); + } } } diff --git a/tests/derive.rs b/tests/derive.rs index adf1188..f29d227 100755..100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -1,4 +1,7 @@ #![cfg(feature = "derive")] +// Various structs/fields that we are deriving `Arbitrary` for aren't actually +// used except to exercise the derive. +#![allow(dead_code)] use arbitrary::*; @@ -164,7 +167,7 @@ fn one_lifetime() { assert_eq!("abc", lifetime.alpha); let (lower, upper) = <OneLifetime as Arbitrary>::size_hint(0); - assert_eq!(lower, std::mem::size_of::<usize>()); + assert_eq!(lower, 0); assert_eq!(upper, None); } @@ -183,6 +186,93 @@ fn two_lifetimes() { assert_eq!("def", lifetime.beta); let (lower, upper) = <TwoLifetimes as Arbitrary>::size_hint(0); - assert_eq!(lower, std::mem::size_of::<usize>() * 2); + assert_eq!(lower, 0); assert_eq!(upper, None); } + +#[test] +fn recursive_and_empty_input() { + // None of the following derives should result in a stack overflow. See + // https://github.com/rust-fuzz/arbitrary/issues/107 for details. + + #[derive(Debug, Arbitrary)] + enum Nat { + Succ(Box<Nat>), + Zero, + } + + let _ = Nat::arbitrary(&mut Unstructured::new(&[])); + + #[derive(Debug, Arbitrary)] + enum Nat2 { + Zero, + Succ(Box<Nat2>), + } + + let _ = Nat2::arbitrary(&mut Unstructured::new(&[])); + + #[derive(Debug, Arbitrary)] + struct Nat3 { + f: Option<Box<Nat3>>, + } + + let _ = Nat3::arbitrary(&mut Unstructured::new(&[])); + + #[derive(Debug, Arbitrary)] + struct Nat4(Option<Box<Nat4>>); + + let _ = Nat4::arbitrary(&mut Unstructured::new(&[])); + + #[derive(Debug, Arbitrary)] + enum Nat5 { + Zero, + Succ { f: Box<Nat5> }, + } + + let _ = Nat5::arbitrary(&mut Unstructured::new(&[])); +} + +#[test] +fn test_field_attributes() { + // A type that DOES NOT implement Arbitrary + #[derive(Debug)] + struct Weight(u8); + + #[derive(Debug, Arbitrary)] + struct Parcel { + #[arbitrary(with = arbitrary_weight)] + weight: Weight, + + #[arbitrary(default)] + width: u8, + + #[arbitrary(value = 2 + 2)] + length: u8, + + height: u8, + + #[arbitrary(with = |u: &mut Unstructured| u.int_in_range(0..=100))] + price: u8, + } + + fn arbitrary_weight(u: &mut Unstructured) -> arbitrary::Result<Weight> { + u.int_in_range(45..=56).map(Weight) + } + + let parcel: Parcel = arbitrary_from(&[6, 199, 17]); + + // 45 + 6 = 51 + assert_eq!(parcel.weight.0, 51); + + // u8::default() + assert_eq!(parcel.width, 0); + + // 2 + 2 = 4 + assert_eq!(parcel.length, 4); + + // 199 is the 2nd byte used by arbitrary + assert_eq!(parcel.height, 199); + + // 17 is the 3rd byte used by arbitrary + assert_eq!(parcel.price, 17); +} diff --git a/tests/path.rs b/tests/path.rs index 15dbbe3..c42ec0a 100644 --- a/tests/path.rs +++ b/tests/path.rs @@ -1,4 +1,7 @@ #![cfg(feature = "derive")] +// Various structs/fields that we are deriving `Arbitrary` for aren't actually +// used except to show off the derive. +#![allow(dead_code)] // Regression test for ensuring the derives work without Arbitrary being imported |