No public description PiperOrigin-RevId: 903835803
diff --git a/rust/e2e_tests/testdata/fuzz_tests.rs b/rust/e2e_tests/testdata/fuzz_tests.rs index 9b827a9..d4f8431 100644 --- a/rust/e2e_tests/testdata/fuzz_tests.rs +++ b/rust/e2e_tests/testdata/fuzz_tests.rs
@@ -32,14 +32,14 @@ type UserValue<'user> = Vec<u8>; type CorpusValue = Vec<u8>; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { let mut val = vec![0u8; rng.random_range(0..100)]; rng.fill(&mut val[..]); Ok(val) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -78,6 +78,10 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(val.clone()) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } } #[fuzztest(_a = Arbitrary::<bool>::default())] @@ -160,12 +164,12 @@ type UserValue<'user> = u32; type CorpusValue = u32; - fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { Ok(0) } fn mutate( - &self, + &mut self, _val: &mut Self::CorpusValue, _rng: &mut dyn rand::Rng, _only_shrink: bool, @@ -179,6 +183,10 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*val) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } } #[fuzztest(a = FallibleDomain::new())]
diff --git a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs index 6cb6b58..3cb112a 100644 --- a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs +++ b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs
@@ -77,14 +77,14 @@ type UserValue<#user_value_lifetime_generic> = #domain_struct_name <#(#user_value_domain_generics),*>; type CorpusValue = #domain_struct_name <#(#corpus_domain_generics),*>; - fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { Ok(#domain_struct_name { #(#field_names: self.#field_names.init(rng)?),* }) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn ::fuzztest::reexports::rand::Rng, only_shrink: bool, @@ -98,6 +98,17 @@ #(#field_names: self.#field_names.get_user_value(&corpus_value.#field_names)?),* }) } + + fn from_value(&self, value: Self::UserValue<'_>) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { + Ok(#domain_struct_name { + #(#field_names: self.#field_names.from_value(value.#field_names)?),* + }) + } + + fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result<()> { + #( self.#field_names.validate_corpus_value(&corpus_value.#field_names)?; )* + Ok(()) + } } }; (domain_definition_tokens, field_names) @@ -144,7 +155,7 @@ type UserValue<'user> = __FuzzTestTestFuzzStateWrapper<T0::UserValue<'user>, T1::UserValue<'user> >; type CorpusValue = __FuzzTestTestFuzzStateWrapper<T0::CorpusValue, T1::CorpusValue>; - fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { Ok(__FuzzTestTestFuzzStateWrapper { a: self.a.init(rng)?, b: self.b.init(rng)? @@ -152,7 +163,7 @@ } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn ::fuzztest::reexports::rand::Rng, only_shrink: bool, @@ -168,6 +179,19 @@ b: self.b.get_user_value(&corpus_value.b)? }) } + + fn from_value(&self, value: Self::UserValue<'_>) -> ::fuzztest::reexports::anyhow::Result<Self::CorpusValue> { + Ok(__FuzzTestTestFuzzStateWrapper { + a: self.a.from_value(value.a)?, + b: self.b.from_value(value.b)? + }) + } + + fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result<()> { + self.a.validate_corpus_value(&corpus_value.a)?; + self.b.validate_corpus_value(&corpus_value.b)?; + Ok(()) + } } } .to_string())
diff --git a/rust/fuzztest_macro/src/helpers/test_registration.rs b/rust/fuzztest_macro/src/helpers/test_registration.rs index 3806469..bf7be38 100644 --- a/rust/fuzztest_macro/src/helpers/test_registration.rs +++ b/rust/fuzztest_macro/src/helpers/test_registration.rs
@@ -109,9 +109,11 @@ let fuzz_test_struct_instance_tokens = quote!( #fuzz_test_struct_name { - domain: #domain_struct_name { + domain: std::sync::Arc::new(std::sync::Mutex::new( + #domain_struct_name { #(#fuzz_test_domain_field_names: #domain_ctors),* - }, + } + )), test_fn: #prop_fn_ident } ); @@ -212,7 +214,7 @@ { where_clauses.predicates.push( parse_quote! { - for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain<UserValue<#user_value_lifetime_generic> = #ty > + for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain<UserValue<#user_value_lifetime_generic> = #ty > + 'static }); where_clauses.predicates.push(parse_quote! { #corpus_gen: 'static }); } @@ -227,7 +229,7 @@ #fuzz_test_domain_definition struct #fuzz_test_struct_name #generics { - domain: #domain_struct_name #generics, + domain: std::sync::Arc<std::sync::Mutex<#domain_struct_name #generics>>, test_fn: #test_fn_type } @@ -248,7 +250,10 @@ .downcast_ref::<#domain_struct_name<#(#corpus_generics),*>>() .expect("Attempt to recover user value before testing failed."); - let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value"); + let user_value = self.domain.lock() + .expect("Failed to acquire domain lock") + .get_user_value(wrapper) + .expect("Failed to get user value from corpus value"); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(#(user_value.#fuzz_test_domain_field_names),* ) )); @@ -257,8 +262,8 @@ fn print_finding_report(&self) { todo!("Not implemented!") } - fn domains(&self) -> &dyn #crate_name::domains::GenericDomain { - &self.domain + fn domains(&self) -> std::sync::Arc<std::sync::Mutex<dyn #crate_name::domains::GenericDomain>> { + std::sync::Arc::clone(&self.domain) as std::sync::Arc<std::sync::Mutex<dyn #crate_name::domains::GenericDomain>> } } @@ -299,14 +304,14 @@ expect_that!( fuzztest_object_tokenstream.to_string(), ends_with( quote! { struct __FuzzTestTestFuzz<T0, T1> { - domain: __FuzzTestTestFuzzStateWrapper<T0, T1>, + domain: std::sync::Arc<std::sync::Mutex<__FuzzTestTestFuzzStateWrapper<T0, T1> >>, test_fn: fn(i32, std::string::String) } impl<T0, T1> ::fuzztest::internal::FuzzTest for __FuzzTestTestFuzz<T0, T1> - where for <'user> T0: ::fuzztest::domains::Domain<UserValue<'user> = i32>, + where for <'user> T0: ::fuzztest::domains::Domain<UserValue<'user> = i32> + 'static, T0::CorpusValue: 'static, - for <'user> T1: ::fuzztest::domains::Domain<UserValue<'user> = std::string::String>, + for <'user> T1: ::fuzztest::domains::Domain<UserValue<'user> = std::string::String> + 'static, T1::CorpusValue: 'static { fn name(&self) -> &'static str { "test_fuzz" @@ -324,7 +329,10 @@ .downcast_ref::<__FuzzTestTestFuzzStateWrapper<T0::CorpusValue, T1::CorpusValue>>() .expect("Attempt to recover user value before testing failed."); - let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value"); + let user_value = self.domain.lock() + .expect("Failed to acquire domain lock") + .get_user_value(wrapper) + .expect("Failed to get user value from corpus value"); // Safety: Data is not reused after the test. let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(user_value.a, user_value.b) )); @@ -333,17 +341,19 @@ fn print_finding_report(&self) { todo!("Not implemented!") } - fn domains(&self) -> &dyn ::fuzztest::domains::GenericDomain { - &self.domain + fn domains(&self) -> std::sync::Arc<std::sync::Mutex<dyn ::fuzztest::domains::GenericDomain>> { + std::sync::Arc::clone(&self.domain) as std::sync::Arc<std::sync::Mutex<dyn ::fuzztest::domains::GenericDomain>> } } fn __FuzzTestTestFuzz_factory() -> ::fuzztest::internal::BoxedFuzzTest { ::std::boxed::Box::new(__FuzzTestTestFuzz { - domain: __FuzzTestTestFuzzStateWrapper { - a: ::fuzztest::domains::arbitrary::Arbitrary::<i32>::default(), - b: ::fuzztest::domains::arbitrary::Arbitrary::<String>::default() - }, + domain: std::sync::Arc::new(std::sync::Mutex::new( + __FuzzTestTestFuzzStateWrapper { + a: ::fuzztest::domains::arbitrary::Arbitrary::<i32>::default(), + b: ::fuzztest::domains::arbitrary::Arbitrary::<String>::default() + } + )), test_fn: __property_fn__test_fuzz }) }
diff --git a/rust/src/domains.rs b/rust/src/domains.rs index a873c59..1f8836f 100644 --- a/rust/src/domains.rs +++ b/rust/src/domains.rs
@@ -22,8 +22,10 @@ use anyhow; use anyhow::Context; +use rand::RngExt; use std::any::Any; +use std::fmt; pub trait CloneAny: Any { fn clone_box(&self) -> Box<dyn CloneAny>; @@ -123,14 +125,14 @@ type CorpusValue: Serialize + DeserializeOwned + Clone; /// Initializes a new value drawn from the domain. - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue>; + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue>; /// Mutates the value in `val` to a new value drawn from the domain. /// /// If `only_shrink` is `true`, then the mutation must not increase the size of the corpus /// value. Otherwise, the mutation can both shrink and grow the corpus value. fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -158,6 +160,126 @@ fn serialize_corpus(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<Vec<u8>> { postcard::to_stdvec(corpus_value).context("Failed to serialize corpus value to bytes") } + + /// Converts a user value to a corpus value. + #[allow(clippy::wrong_self_convention)] + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue>; + + /// Validates that a corpus value satisfies the domain's constraints. + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } +} + +/// Helper struct that stores seeds and optional lazy seed provider for a domain. +pub struct DomainSeeds<C> { + seeds: Vec<C>, + seed_provider: Option<Box<dyn FnOnce() -> Vec<C> + Send + Sync>>, +} + +impl<C> Default for DomainSeeds<C> { + fn default() -> Self { + Self { + seeds: Vec::new(), + seed_provider: None, + } + } +} + +impl<C> DomainSeeds<C> { + pub fn new() -> Self { + Self::default() + } +} + +impl<C: Clone> Clone for DomainSeeds<C> { + fn clone(&self) -> Self { + if self.seed_provider.is_some() { + panic!("DomainSeeds with a seed provider cannot be cloned before initialization"); + } + Self { + seeds: self.seeds.clone(), + seed_provider: None, + } + } +} + +impl<C: Clone + fmt::Debug> fmt::Debug for DomainSeeds<C> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DomainSeeds") + .field("seeds", &self.seeds) + .field("has_seed_provider", &self.seed_provider.is_some()) + .finish() + } +} + +impl<C: Clone> DomainSeeds<C> { + pub fn extend_seeds(&mut self, seeds: impl IntoIterator<Item = C>) { + self.seeds.extend(seeds); + } + + pub fn set_provider<F>(&mut self, provider: F) + where + F: FnOnce() -> Vec<C> + Send + Sync + 'static, + { + self.seed_provider = Some(Box::new(provider)); + } + + /// Evaluates lazy seed provider (if present) and returns a random seed with 50% probability. + pub fn sample(&mut self, rng: &mut dyn rand::Rng) -> Option<C> { + if let Some(seed_provider) = self.seed_provider.take() { + self.extend_seeds(seed_provider()); + } + if self.seeds.is_empty() || !rng.random_bool(0.5) { + None + } else { + let idx = rng.random_range(0..self.seeds.len()); + Some(self.seeds[idx].clone()) + } + } +} + +pub trait SeedableDomain: Domain + Sized { + /// Mutable accessor to the domain's seed storage. + fn seeds_mut(&mut self) -> &mut DomainSeeds<Self::CorpusValue>; + + /// Adds pre-defined seeds. Panics if any seed is invalid for this domain. + fn with_seeds(mut self, seeds: impl IntoIterator<Item = impl Into<Self::CorpusValue>>) -> Self { + for seed in seeds { + let corpus_val = seed.into(); + if let Err(e) = self.validate_corpus_value(&corpus_val) { + panic!("Invalid seed value for domain: {e:?}"); + } + self.seeds_mut().extend_seeds([corpus_val]); + } + self + } + + /// Non-panicking version for programmatic usage. + fn try_with_seeds( + mut self, + seeds: impl IntoIterator<Item = impl Into<Self::CorpusValue>>, + ) -> anyhow::Result<Self> { + for seed in seeds { + let corpus_val = seed.into(); + self.validate_corpus_value(&corpus_val)?; + self.seeds_mut().extend_seeds([corpus_val]); + } + Ok(self) + } + + /// Adds a lazy seed provider evaluated on first sampling. + fn with_seed_provider<F, I, S>(mut self, seed_provider: F) -> Self + where + F: FnOnce() -> I + Send + Sync + 'static, + I: IntoIterator<Item = S>, + Self::CorpusValue: From<S>, + { + self.seeds_mut().set_provider(move || { + seed_provider().into_iter().map(Self::CorpusValue::from).collect() + }); + self + } } /// A type-erased interface for Domain types. @@ -170,13 +292,13 @@ /// Initializes a new value drawn from the domain. /// /// See `Domain::init` for more details. - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue>; + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue>; /// Mutates the value in `val` to a new value drawn from the domain. /// /// See `Domain::mutate` for more details. fn mutate( - &self, + &mut self, val: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -202,7 +324,7 @@ D: Domain, D::CorpusValue: 'static, { - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue> { Ok(Box::new(self.init(rng)?)) } @@ -214,7 +336,7 @@ /// /// See `GenericDomain::mutate` for more details. fn mutate( - &self, + &mut self, val: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool,
diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs index e2167bf..4f583c2 100644 --- a/rust/src/domains/arbitrary.rs +++ b/rust/src/domains/arbitrary.rs
@@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::domains::SeedableDomain; + use super::utility::choose_value; use super::utility::mutate_integer; use super::utility::shrink_towards; use super::Domain; +use super::DomainSeeds; use std::char; use std::fmt; use std::marker::PhantomData; @@ -36,39 +39,58 @@ /// # use rand::rngs::SmallRng; /// # use rand::SeedableRng; /// -/// let arbitrary_i32 = Arbitrary::<i32>::default(); +/// let mut arbitrary_i32 = Arbitrary::<i32>::default(); /// let mut rng = SmallRng::seed_from_u64(73); /// /// let sample = arbitrary_i32.init(&mut rng); /// assert!(sample.is_ok()); /// ``` pub struct Arbitrary<T> { + seeds: DomainSeeds<T>, _phantom: PhantomData<T>, } -impl<T> Clone for Arbitrary<T> { +impl<T: Clone> Clone for Arbitrary<T> { fn clone(&self) -> Self { - Self { _phantom: PhantomData } + Self { + seeds: self.seeds.clone(), + _phantom: PhantomData, + } } } -impl<T> fmt::Debug for Arbitrary<T> { +impl<T: fmt::Debug + Clone> fmt::Debug for Arbitrary<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Arbitrary").field("_phantom", &self._phantom).finish() + f.debug_struct("Arbitrary") + .field("seeds", &self.seeds) + .field("_phantom", &self._phantom) + .finish() } } // We cannot just use `#[derive(Default)]` because `T` might not be `Default`. impl<T> Default for Arbitrary<T> { fn default() -> Self { - Self { _phantom: PhantomData } + Self { + seeds: DomainSeeds::new(), + _phantom: PhantomData, + } } } impl<T> Arbitrary<T> { /// Creates a new `Arbitrary` domain for the given type `T`. pub fn new() -> Self { - Self { _phantom: PhantomData } + Self::default() + } +} + +impl<T: Clone + 'static> SeedableDomain for Arbitrary<T> +where + Arbitrary<T>: Domain<CorpusValue = T>, +{ + fn seeds_mut(&mut self) -> &mut DomainSeeds<Self::CorpusValue> { + &mut self.seeds } } @@ -76,12 +98,15 @@ type UserValue<'user> = bool; type CorpusValue = bool; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if let Some(seed) = self.seeds.sample(rng) { + return Ok(seed); + } Ok(rng.random()) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -101,6 +126,14 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } macro_rules! impl_domain_for_integer { @@ -112,14 +145,17 @@ type UserValue<'user> = $ty; type CorpusValue = $ty; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { - // We generate a the equivalent integer type so this works for size types. + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if let Some(seed) = self.seeds.sample(rng) { + return Ok(seed); + } + // We generate the equivalent integer type so this works for size types. let val: $int_ty = choose_value(rng); Ok(val as $ty) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -144,6 +180,17 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value( + &self, + _corpus_value: &Self::CorpusValue, + ) -> anyhow::Result<()> { + Ok(()) + } } }; } @@ -167,12 +214,15 @@ type UserValue<'user> = $ty; type CorpusValue = $ty; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if let Some(seed) = self.seeds.sample(rng) { + return Ok(seed); + } Ok(choose_value(rng)) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -215,6 +265,17 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value( + &self, + _corpus_value: &Self::CorpusValue, + ) -> anyhow::Result<()> { + Ok(()) + } } }; } @@ -263,12 +324,15 @@ type UserValue<'user> = char; type CorpusValue = char; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if let Some(seed) = self.seeds.sample(rng) { + return Ok(seed); + } Ok(choose_value(rng)) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -300,18 +364,29 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } impl Domain for Arbitrary<()> { type UserValue<'user> = (); type CorpusValue = (); - fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + if let Some(seed) = self.seeds.sample(rng) { + return Ok(seed); + } Ok(()) } fn mutate( - &self, + &mut self, _val: &mut Self::CorpusValue, _rng: &mut dyn rand::Rng, _only_shrink: bool, @@ -326,6 +401,14 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(()) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } #[cfg(test)] @@ -412,7 +495,7 @@ CorpusValueForArbitrary<T>: std::fmt::Debug + Default + Clone + Copy + PartialOrd + PartialEq + 'static, { - let domain = Arbitrary::<T>::default(); + let mut domain = Arbitrary::<T>::default(); let mut rng = get_rng(); let mut value = domain.init(&mut rng).unwrap(); @@ -442,7 +525,7 @@ + std::hash::Hash + 'static, { - let domain = Arbitrary::<T>::default(); + let mut domain = Arbitrary::<T>::default(); let mut rng = get_rng(); for _ in 0..100 { let mut value = domain.init(&mut rng).unwrap(); @@ -474,7 +557,7 @@ + NumTraitsExtended + 'static, { - let domain = Arbitrary::<T>::default(); + let mut domain = Arbitrary::<T>::default(); let mut rng = get_rng(); // Get a value that is not the shrink target @@ -619,7 +702,7 @@ } fn test_bool_shrink() { - let domain = Arbitrary::<bool>::default(); + let mut domain = Arbitrary::<bool>::default(); let mut rng = get_rng(); let mut value = true; domain.mutate(&mut value, &mut rng, true).unwrap(); @@ -636,7 +719,7 @@ #[test] fn test_unit() { let mut rng = get_rng(); - let domain = Arbitrary::<()>::default(); + let mut domain = Arbitrary::<()>::default(); // init() always returns () assert_eq!(domain.init(&mut rng).unwrap(), ()); @@ -659,7 +742,7 @@ Float + SampleUniform + std::fmt::Display + std::fmt::Debug + SpecialValues + 'static, StandardUniform: Distribution<T>, { - let domain = Arbitrary::<T>::default(); + let mut domain = Arbitrary::<T>::default(); let mut rng = get_rng(); // Positive. @@ -723,7 +806,7 @@ #[test] fn test_char_mutate_boundaries() { - let domain = Arbitrary::<char>::default(); + let mut domain = Arbitrary::<char>::default(); let mut rng = get_rng(); let mut val = '\u{0000}'; domain.mutate(&mut val, &mut rng, false).unwrap(); @@ -762,7 +845,7 @@ #[test] fn test_char_shrink_to_null() { for _ in 0..10 { - let domain = Arbitrary::<char>::default(); + let mut domain = Arbitrary::<char>::default(); let mut rng = get_rng(); let mut value = domain.init(&mut rng).unwrap();
diff --git a/rust/src/domains/containers.rs b/rust/src/domains/containers.rs index a15e28c..7dee293 100644 --- a/rust/src/domains/containers.rs +++ b/rust/src/domains/containers.rs
@@ -1,6 +1,5 @@ use rand::RngExt; use std::fmt; - use super::Domain; const DEFAULT_MAX_LEN: usize = 5000; @@ -113,7 +112,12 @@ impl<T> VecOf<T> { pub fn new(inner: T) -> Self { - Self { inner, min_len: 0, max_len: None, max_len_is_soft: false } + Self { + inner, + min_len: 0, + max_len: None, + max_len_is_soft: false, + } } fn max_len(&self) -> usize { @@ -128,7 +132,7 @@ type CorpusValue = Vec<T::CorpusValue>; type UserValue<'user> = Vec<T::UserValue<'user>>; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { if self.max_len() == 0 { return Ok(Vec::new()); } @@ -143,7 +147,7 @@ } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -193,9 +197,40 @@ } Ok(user_values) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + let mut corpus_values = Vec::with_capacity(value.len()); + for item in value { + corpus_values.push(self.inner.from_value(item)?); + } + Ok(corpus_values) + } + + fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + if self.max_len_is_soft { + anyhow::ensure!( + self.min_len <= corpus_value.len(), + "Length {} is less than the minimum length {}", + corpus_value.len(), + self.min_len + ); + } else { + anyhow::ensure!( + self.min_len <= corpus_value.len() && corpus_value.len() <= self.max_len(), + "Length {} is not between the minimum length {} and maximum length {}", + corpus_value.len(), + self.min_len, + self.max_len() + ); + } + for item in corpus_value { + self.inner.validate_corpus_value(item)?; + } + Ok(()) + } } -impl<T> ContainerDomain for VecOf<T> { +impl<T: Domain> ContainerDomain for VecOf<T> { fn with_len(self, len: usize) -> Self { Self { min_len: len, max_len: Some(len), ..self } } @@ -245,7 +280,7 @@ #[gtest] fn test_vec_of_mutate_shrink() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); let mut rng = get_rng(); @@ -264,7 +299,7 @@ #[gtest] fn test_vec_of_mutate_grow_and_change() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(10); let mut rng = get_rng(); @@ -284,7 +319,7 @@ #[gtest] fn test_vec_of_init_respects_min_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); let mut rng = get_rng(); for _ in 0..100 { @@ -295,7 +330,7 @@ #[gtest] fn test_vec_of_init_fixed_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(7); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(7); let mut rng = get_rng(); for _ in 0..100 { @@ -306,7 +341,7 @@ #[gtest] fn test_vec_of_init_default_max_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()); + let mut domain = VecOf::new(Arbitrary::<u32>::default()); let mut rng = get_rng(); for _ in 0..100 { @@ -317,7 +352,7 @@ #[gtest] fn test_vec_of_mutate_respects_min_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(3); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(3); let mut rng = get_rng(); let mut val = vec![1, 2, 3]; @@ -329,7 +364,7 @@ #[gtest] fn test_vec_of_mutate_respects_max_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(3); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_max_len(3); let mut rng = get_rng(); let mut val = vec![1, 2, 3]; @@ -341,7 +376,7 @@ #[gtest] fn test_vec_of_mutate_min_len_validation() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_min_len(5); let mut rng = get_rng(); let mut val = vec![1, 2, 3]; // Length 3, which is < 5 @@ -358,7 +393,7 @@ #[gtest] fn test_vec_of_mutate_soft_max_len_behavior() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_soft_max_len(5); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_soft_max_len(5); let mut rng = get_rng(); // Valid mutation within bounds @@ -394,7 +429,7 @@ #[gtest] fn test_vec_of_mutate_no_action_at_bounds() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(1); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(1); let mut rng = get_rng(); let mut val = vec![100u32]; @@ -415,7 +450,7 @@ #[gtest] fn test_vec_of_zero_len() { - let domain = VecOf::new(Arbitrary::<u32>::default()).with_len(0); + let mut domain = VecOf::new(Arbitrary::<u32>::default()).with_len(0); let mut rng = get_rng(); let val = domain.init(&mut rng).unwrap(); @@ -433,4 +468,5 @@ let user_val = domain.get_user_value(&corpus_val).unwrap(); expect_that!(user_val, container_eq(vec![1u32, 2u32, 3u32])); } + }
diff --git a/rust/src/domains/range.rs b/rust/src/domains/range.rs index 3bf413b..cb5f641 100644 --- a/rust/src/domains/range.rs +++ b/rust/src/domains/range.rs
@@ -17,31 +17,32 @@ use anyhow; use rand::distr::uniform::SampleUniform; use rand::distr::uniform::UniformSampler; +use std::fmt; /// Generates values of type `T` in a given range. -/// -/// For example, `InRange::new(0, 100)` generates integer -/// values from the inclusive range `[0, 100]`. -/// -/// Example usage: -/// ``` -/// # use fuzztest::domains::Domain; -/// # use fuzztest::domains::range::InRange; -/// # use rand::prelude::*; -/// -/// let range_i32 = InRange::new(21i32, 73); -/// let sample = range_i32.init(&mut rand::rng()); -/// -/// assert!(sample.is_ok()); -/// let sample = sample.unwrap(); -/// assert!(sample >= 21); -/// assert!(sample <= 73); -/// ``` pub struct InRange<T> { lower: T, upper: T, } +impl<T: Clone> Clone for InRange<T> { + fn clone(&self) -> Self { + Self { + lower: self.lower.clone(), + upper: self.upper.clone(), + } + } +} + +impl<T: fmt::Debug + Clone> fmt::Debug for InRange<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("InRange") + .field("lower", &self.lower) + .field("upper", &self.upper) + .finish() + } +} + impl InRange<i32> { pub fn new(lower: i32, upper: i32) -> Self { Self { lower, upper } @@ -57,12 +58,12 @@ type UserValue<'user> = i32; type CorpusValue = i32; - fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<Self::CorpusValue> { Ok(self.get_in_range(rng)) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -81,6 +82,20 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn from_value(&self, value: Self::UserValue<'_>) -> anyhow::Result<Self::CorpusValue> { + Ok(value) + } + + fn validate_corpus_value(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + if *corpus_value < self.lower || *corpus_value > self.upper { + anyhow::bail!( + "Value {} is out of range [{}, {}]", + corpus_value, self.lower, self.upper + ); + } + Ok(()) + } } #[cfg(test)]
diff --git a/rust/src/internal.rs b/rust/src/internal.rs index 213e66a..5f7e95b 100644 --- a/rust/src/internal.rs +++ b/rust/src/internal.rs
@@ -14,8 +14,11 @@ use super::domains::GenericCorpusValue; use super::domains::GenericDomain; + use std::collections::HashMap; +use std::sync::Arc; use std::sync::LazyLock; +use std::sync::Mutex; /// A trait implemented by types used to Fuzz a given property function. /// @@ -35,7 +38,7 @@ /// Returns `true` if the property function holds, `false` if it crashes. fn execute(&self, args: &GenericCorpusValue) -> bool; fn print_finding_report(&self); - fn domains(&self) -> &dyn GenericDomain; + fn domains(&self) -> Arc<Mutex<dyn GenericDomain>>; } /// Identifies the property function of a fuzz test.
diff --git a/rust/src/worker.rs b/rust/src/worker.rs index 8eae57f..d4786f9 100644 --- a/rust/src/worker.rs +++ b/rust/src/worker.rs
@@ -158,7 +158,9 @@ } pub fn get_random_seed_input(&self, sink: &mut InputSink) { - match self.fuzz_test.domains().init(&mut rand::rng()) { + let domains = self.fuzz_test.domains(); + let mut domains_guard = domains.lock().expect("Failed to lock domains"); + match domains_guard.init(&mut rand::rng()) { Ok(val) => { sink.emit(pack_input(val)); } @@ -170,8 +172,10 @@ pub fn mutate(&self, origin: &GenericCorpusValue, shrink: bool, sink: &mut InputSink) { let mut mutant = origin.clone(); + let domains = self.fuzz_test.domains(); + let mut domains_guard = domains.lock().expect("Failed to lock domains"); - if let Err(e) = self.fuzz_test.domains().mutate(&mut mutant, &mut rand::rng(), shrink) { + if let Err(e) = domains_guard.mutate(&mut mutant, &mut rand::rng(), shrink) { emit_error(&format!("Failed to mutate: {:?}", e)); return; } @@ -211,7 +215,9 @@ } pub fn serialize_input_content(&self, input: &GenericCorpusValue, sink: &mut BytesSink) { - match self.fuzz_test.domains().serialize_corpus(input) { + let domains = self.fuzz_test.domains(); + let domains_guard = domains.lock().expect("Failed to lock domains"); + match domains_guard.serialize_corpus(input) { Ok(serialized) => { sink.emit(&serialized); } @@ -222,7 +228,9 @@ } pub fn deserialize_input_content(&self, content: &[u8], sink: &mut InputSink) { - match self.fuzz_test.domains().parse_corpus(content) { + let domains = self.fuzz_test.domains(); + let domains_guard = domains.lock().expect("Failed to lock domains"); + match domains_guard.parse_corpus(content) { Ok(val) => { sink.emit(pack_input(val)); } @@ -639,6 +647,8 @@ let mut generic_corpus_value = fuzztest .domains() + .lock() + .expect("Failed to lock domains") .init(&mut rng) .expect("domain initialization should succeed to provide an initial corpus value"); @@ -649,6 +659,8 @@ while start_time.elapsed() < smoke_test_duration { fuzztest .domains() + .lock() + .expect("Failed to lock domains") .mutate(&mut generic_corpus_value, &mut rng, only_shrink) .expect("domain mutation should succeed"); let result = fuzztest.execute(&generic_corpus_value);
diff --git a/rust/tests/macro_compiles.rs b/rust/tests/macro_compiles.rs index 5eee104..96b6d8a 100644 --- a/rust/tests/macro_compiles.rs +++ b/rust/tests/macro_compiles.rs
@@ -15,6 +15,7 @@ use fuzztest::domains::arbitrary::Arbitrary; use fuzztest::domains::containers::ContainerDomain; use fuzztest::domains::containers::VecOf; +use fuzztest::domains::SeedableDomain; use fuzztest::fuzztest; #[fuzztest(_a = Arbitrary::<i32>::default())] @@ -26,4 +27,10 @@ #[fuzztest(_a = VecOf::new(Arbitrary::<i32>::default()).with_max_len(10))] fn fuzztest_macro_compiles_with_vec(_a: Vec<i32>) {} +#[fuzztest(_a = Arbitrary::<i32>::default().with_seeds([1, 2, 3]))] +fn fuzztest_macro_compiles_with_seeds(_a: i32) {} + +#[fuzztest(_a = Arbitrary::<i32>::default().with_seed_provider(|| vec![1, 2, 3]))] +fn fuzztest_macro_compiles_with_seed_provider(_a: i32) {} + fn main() {}