No public description PiperOrigin-RevId: 990102882
diff --git a/rust/e2e_tests/testdata/fuzz_tests.rs b/rust/e2e_tests/testdata/fuzz_tests.rs index e2f9afd..e725ff9 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, @@ -160,12 +160,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,
diff --git a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs index 5ee045d..f74a152 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,11 @@ #(#field_names: self.#field_names.corpus_to_user_value(&corpus_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 +149,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 +157,7 @@ } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn ::fuzztest::reexports::rand::Rng, only_shrink: bool, @@ -168,6 +173,12 @@ b: self.b.corpus_to_user_value(&corpus_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 60e73f5..b38ceec 100644 --- a/rust/fuzztest_macro/src/helpers/test_registration.rs +++ b/rust/fuzztest_macro/src/helpers/test_registration.rs
@@ -111,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 } ); @@ -214,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 }); } @@ -229,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 } @@ -250,7 +252,10 @@ .downcast_ref::<#domain_struct_name<#(#corpus_generics),*>>() .expect("Attempt to recover user value before testing failed."); - let user_value = self.domain.corpus_to_user_value(wrapper).expect("Failed to get user value from corpus value"); + let user_value = self.domain.lock() + .expect("Failed to acquire domain lock") + .corpus_to_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),* ) )); @@ -259,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>> } } @@ -301,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" @@ -326,7 +331,10 @@ .downcast_ref::<__FuzzTestTestFuzzStateWrapper<T0::CorpusValue, T1::CorpusValue>>() .expect("Attempt to recover user value before testing failed."); - let user_value = self.domain.corpus_to_user_value(wrapper).expect("Failed to get user value from corpus value"); + let user_value = self.domain.lock() + .expect("Failed to acquire domain lock") + .corpus_to_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) )); @@ -335,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 550fa2f..3857424 100644 --- a/rust/src/domains.rs +++ b/rust/src/domains.rs
@@ -123,15 +123,15 @@ 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, - val: &mut Self::CorpusValue, + &mut self, + corpus_value: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, ) -> anyhow::Result<()>; @@ -177,6 +177,16 @@ 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") } + + /// Validates that a corpus value satisfies the domain's constraints. + /// + /// This method is called, among other things, after the `CorpusValue` was constructed from a + /// user provided value. + /// For example, when the domain is seeded, this method is used to check that seeds specified by + /// the users are valid given the domain's constraints. + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } /// A type-erased interface for Domain types. @@ -189,14 +199,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<()>; @@ -221,7 +231,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)?)) } @@ -233,13 +243,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 303d751..326e16a 100644 --- a/rust/src/domains/arbitrary.rs +++ b/rust/src/domains/arbitrary.rs
@@ -36,7 +36,7 @@ /// # 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); @@ -46,13 +46,13 @@ _phantom: PhantomData<T>, } -impl<T> Clone for Arbitrary<T> { +impl<T: Clone> Clone for Arbitrary<T> { fn clone(&self) -> Self { Self { _phantom: PhantomData } } } -impl<T> fmt::Debug for Arbitrary<T> { +impl<T: fmt::Debug> fmt::Debug for Arbitrary<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Arbitrary").field("_phantom", &self._phantom).finish() } @@ -68,7 +68,7 @@ impl<T> Arbitrary<T> { /// Creates a new `Arbitrary` domain for the given type `T`. pub fn new() -> Self { - Self { _phantom: PhantomData } + Self::default() } } @@ -76,12 +76,12 @@ 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> { Ok(rng.random()) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -101,6 +101,10 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } macro_rules! impl_domain_for_integer { @@ -112,14 +116,14 @@ 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> { + // 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 +148,13 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn validate_corpus_value( + &self, + _corpus_value: &Self::CorpusValue, + ) -> anyhow::Result<()> { + Ok(()) + } } }; } @@ -167,12 +178,12 @@ 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> { Ok(choose_value(rng)) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -215,6 +226,13 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_value) } + + fn validate_corpus_value( + &self, + _corpus_value: &Self::CorpusValue, + ) -> anyhow::Result<()> { + Ok(()) + } } }; } @@ -263,12 +281,12 @@ 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> { Ok(choose_value(rng)) } fn mutate( - &self, + &mut self, val: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -300,18 +318,22 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_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> { Ok(()) } fn mutate( - &self, + &mut self, _val: &mut Self::CorpusValue, _rng: &mut dyn rand::Rng, _only_shrink: bool, @@ -326,6 +348,10 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(()) } + + fn validate_corpus_value(&self, _corpus_value: &Self::CorpusValue) -> anyhow::Result<()> { + Ok(()) + } } #[cfg(test)] @@ -412,7 +438,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 +468,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 +500,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 @@ -620,7 +646,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(); @@ -637,7 +663,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(), ()); @@ -660,7 +686,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. @@ -724,7 +750,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(); @@ -763,7 +789,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 30bc1e9..4c816a1 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,32 @@ } Ok(user_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 +267,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 +286,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 +306,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 +317,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 +328,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 +339,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 +351,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 +363,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 +380,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 +416,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 +437,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 1794146..24208de 100644 --- a/rust/src/domains/range.rs +++ b/rust/src/domains/range.rs
@@ -29,7 +29,7 @@ /// # use fuzztest::domains::range::InRange; /// # use rand::prelude::*; /// -/// let range_i32 = InRange::new(21i32, 73); +/// let mut range_i32 = InRange::new(21i32, 73); /// let sample = range_i32.init(&mut rand::rng()); /// /// assert!(sample.is_ok()); @@ -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,18 @@ ) -> anyhow::Result<Self::UserValue<'a>> { Ok(*corpus_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();