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..e843b4f 100644 --- a/rust/fuzztest_macro/src/helpers/test_registration.rs +++ b/rust/fuzztest_macro/src/helpers/test_registration.rs
@@ -44,7 +44,8 @@ } /// A context struct holding the pre-computed information and token streams required -/// to generate the fuzz test registration, struct definitions, and integration with the test framework. +/// to generate the fuzz test registration, struct definitions, and integration with the test +/// framework. /// /// This context is created once per property function. pub struct FuzzTestRegistrationCtx<'a> { @@ -60,7 +61,8 @@ } impl<'a> FuzzTestRegistrationCtx<'a> { - /// Creates a new registration context from a property function signature and its domain constructors. + /// Creates a new registration context from a property function signature and its domain + /// constructors. /// /// This method analyzes the inputs, derives necessary identifiers and lifetime generics, and /// pre-computes the tokenstream for the fuzz test struct instance. @@ -109,9 +111,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 +216,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 +231,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 +252,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 +264,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 +306,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 +331,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 +343,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..0228aa5 100644 --- a/rust/src/domains.rs +++ b/rust/src/domains.rs
@@ -123,14 +123,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,8 +158,130 @@ 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(()) + } } +mod seeds { + use super::Domain; + + use rand::RngExt; + + use std::fmt; + use std::sync::Arc; + + /// Helper struct that stores seeds and optional lazy seed provider for a domain. + #[derive(Clone)] + pub struct DomainSeeds<C> { + seeds: Vec<C>, + seed_provider: Option<Arc<Box<dyn Fn() -> Vec<C> + Send + Sync>>>, + } + + impl<C> Default for DomainSeeds<C> { + fn default() -> Self { + Self { seeds: Vec::new(), seed_provider: None } + } + } + + impl<C: 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> DomainSeeds<C> { + pub fn new() -> Self { + Self::default() + } + } + + /// Helper methods for the `DomainSeeds` struct. Those methods are private to the domain module + /// as they are not intended to be used by the user. + /// Internals of the `DomainSeeds` should only be accessed through the SeedableDomain trait. + impl<C: Clone> DomainSeeds<C> { + fn extend_seeds(&mut self, seeds: impl IntoIterator<Item = C>) { + self.seeds.extend(seeds); + } + + fn set_provider<F>(&mut self, provider: F) + where + F: Fn() -> Vec<C> + Send + Sync + 'static, + { + self.seed_provider = Some(Arc::new(Box::new(provider))); + } + + /// Returns a random seed with 50% probability. Does not call the seed provider as seeds + /// must be validated by the domain before being added to the domain seeds. + fn sample(&self, rng: &mut dyn rand::Rng) -> Option<C> { + 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>; + + /// Evaluates & validates the lazy seed provider (if any) once, then samples with 50% + /// probability. + fn sample_seed( + &mut self, + rng: &mut dyn rand::Rng, + ) -> anyhow::Result<Option<Self::CorpusValue>> { + if let Some(provider) = self.seeds_mut().seed_provider.take() { + let seeds = provider() + .into_iter() + .map(|s| self.validate_corpus_value(&s).map(|_| s)) + .collect::<anyhow::Result<Vec<Self::CorpusValue>>>()?; + self.seeds_mut().extend_seeds(seeds); + } + Ok(self.seeds_mut().sample(rng)) + } + + /// 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 + } + + /// Adds a lazy seed provider evaluated on first sampling. + fn with_seed_provider<F, I, S>(mut self, seed_provider: F) -> Self + where + F: Fn() -> 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 + } + } +} // mod seeds + /// A type-erased interface for Domain types. /// /// This trait is used to expose a common interface for Domain types to the fuzzing engine through @@ -170,14 +292,14 @@ /// 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, - val: &mut GenericCorpusValue, + &mut self, + corpus_value: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, ) -> anyhow::Result<()>; @@ -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,13 +336,13 @@ /// /// See `GenericDomain::mutate` for more details. fn mutate( - &self, - val: &mut GenericCorpusValue, + &mut self, + corpus_value: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, ) -> anyhow::Result<()> { self.mutate( - val.downcast_mut().context("Failed to retrieve the Corpus Value")?, + corpus_value.downcast_mut().context("Failed to retrieve the Corpus Value")?, rng, only_shrink, )
diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs index e2167bf..9e350b8 100644 --- a/rust/src/domains/arbitrary.rs +++ b/rust/src/domains/arbitrary.rs
@@ -11,13 +11,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - +use super::seeds::DomainSeeds; +use super::seeds::SeedableDomain; use super::utility::choose_value; use super::utility::mutate_integer; use super::utility::shrink_towards; use super::Domain; use std::char; -use std::fmt; use std::marker::PhantomData; use anyhow; @@ -36,39 +36,38 @@ /// # 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()); /// ``` +#[derive(Clone, Debug)] pub struct Arbitrary<T> { + seeds: DomainSeeds<T>, _phantom: PhantomData<T>, } -impl<T> Clone for Arbitrary<T> { - fn clone(&self) -> Self { - Self { _phantom: PhantomData } - } -} - -impl<T> fmt::Debug for Arbitrary<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Arbitrary").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 +75,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.sample_seed(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 +103,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 +122,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.sample_seed(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 +157,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 +191,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.sample_seed(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 +242,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 +301,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.sample_seed(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 +341,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.sample_seed(rng)? { + return Ok(seed); + } Ok(()) } fn mutate( - &self, + &mut self, _val: &mut Self::CorpusValue, _rng: &mut dyn rand::Rng, _only_shrink: bool, @@ -326,6 +378,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 +472,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 +502,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 +534,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 @@ -492,7 +552,8 @@ domain.mutate(&mut value, &mut rng, true).unwrap(); if value.is_at_shrink_target() { - // Ensure that once the shrink target is reached, further shrinking doesn't change it. + // Ensure that once the shrink target is reached, further shrinking doesn't change + // it. domain.mutate(&mut value, &mut rng, true).unwrap(); assert!( value.is_at_shrink_target(), @@ -619,7 +680,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 +697,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 +720,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 +784,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 +823,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..40dce2b 100644 --- a/rust/src/domains/containers.rs +++ b/rust/src/domains/containers.rs
@@ -1,8 +1,7 @@ +use super::Domain; use rand::RngExt; use std::fmt; -use super::Domain; - const DEFAULT_MAX_LEN: usize = 5000; macro_rules! choose_one { @@ -128,7 +127,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 +142,7 @@ } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -193,9 +192,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 +275,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 +294,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 +314,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 +325,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 +336,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 +347,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 +359,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 +371,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 +388,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 +424,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 +445,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();
diff --git a/rust/src/domains/range.rs b/rust/src/domains/range.rs index 3bf413b..c9ad1c7 100644 --- a/rust/src/domains/range.rs +++ b/rust/src/domains/range.rs
@@ -57,12 +57,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 +81,22 @@ ) -> 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 e19f21f..cd9ed80 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)); } @@ -242,8 +250,9 @@ pub fn free_input(&self, input: engine_ffi::FuzzTestInputHandle) { if input.0 != 0 { - // SAFETY: The engine guarantees `input` was created by `deserialize_input_content_callback` - // (or `emit` in `InputSink`) and has not been freed yet. + // SAFETY: The engine guarantees `input` was created by + // `deserialize_input_content_callback` (or `emit` in `InputSink`) and has + // not been freed yet. unsafe { let _ = Box::from_raw(input.0 as *mut GenericCorpusValue); } @@ -292,8 +301,8 @@ /// /// The caller must ensure that: /// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization. -/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration -/// of this call. +/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration of +/// this call. pub unsafe extern "C" fn get_binary_id_callback( ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx, sink: *const engine_ffi::FuzzTestBytesSink, @@ -311,8 +320,8 @@ /// /// The caller must ensure that: /// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization. -/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration -/// of this call. +/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration of +/// this call. pub unsafe extern "C" fn get_test_name_callback( ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx, sink: *const engine_ffi::FuzzTestBytesSink, @@ -342,8 +351,8 @@ // passed during initialization. let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) }; - // SAFETY: The engine guarantees `diagnostic_sink` is a valid pointer to a `FuzzTestDiagnosticSink` - // whose lifetime extends until `FreeCtx` is called on the adapter. + // SAFETY: The engine guarantees `diagnostic_sink` is a valid pointer to a + // `FuzzTestDiagnosticSink` whose lifetime extends until `FreeCtx` is called on the adapter. let safe_sink = unsafe { DiagnosticSink::from_raw(diagnostic_sink) }; set_diagnostic_sink(safe_sink); @@ -387,7 +396,8 @@ // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` // created by `construct_adapter_callback`. let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; - // SAFETY: The engine guarantees `registry` is a valid pointer to `FuzzTestCoverageDomainRegistry`. + // SAFETY: The engine guarantees `registry` is a valid pointer to + // `FuzzTestCoverageDomainRegistry`. let mut registry = unsafe { CoverageDomainRegistry::from_raw(registry) }; adapter.set_up_coverage_domains(&mut registry); } @@ -577,7 +587,8 @@ /// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. /// * `metadata` is a valid pointer to `FuzzTestBytesView` containing serialized input metadata. /// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` -/// managed by the framework, and the engine guarantees exclusive access to it for the call duration. +/// managed by the framework, and the engine guarantees exclusive access to it for the call +/// duration. pub unsafe extern "C" fn update_input_metadata_callback( ctx: *mut engine_ffi::FuzzTestAdapterCtx, metadata: *const engine_ffi::FuzzTestBytesView, @@ -639,6 +650,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 +662,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); @@ -673,9 +688,9 @@ /// engine loop until complete. /// - Returns cleanly on [`WorkerStatus::Success`], or panics on [`WorkerStatus::Failure`] to /// signal test failure to the harness. -/// 2. Smoke Test Mode: If worker mode is not active (e.g., during standard `blaze test` or -/// `cargo test` unit test runs), falls back to executing a short local smoke test using sample -/// inputs and mutation iterations to verify property function sanity. +/// 2. Smoke Test Mode: If worker mode is not active (e.g., during standard `blaze test` or `cargo +/// test` unit test runs), falls back to executing a short local smoke test using sample inputs +/// and mutation iterations to verify property function sanity. pub fn process(manager: RustFuzzTestAdapterManager) { super::crash_handler::register_crash_handler();
diff --git a/rust/tests/macro_compiles.rs b/rust/tests/macro_compiles.rs index 04c245e..e77615e 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::seeds::SeedableDomain; use fuzztest::fuzztest; #[fuzztest(_a = Arbitrary::<i32>::default())] @@ -26,6 +27,12 @@ #[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() { // The property function is defined in the scope where the macro is // invoked, so it can be called directly, e.g. from a regression test.