No public description PiperOrigin-RevId: 903228420
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..8fbf5a6 100644 --- a/rust/src/domains.rs +++ b/rust/src/domains.rs
@@ -32,17 +32,10 @@ } impl dyn CloneAny { - /// Returns `true` if the underlying type is of type `T` - pub fn is<T: Any>(&self) -> bool { - self.as_any().is::<T>() - } - - /// Returns a reference to the concrete value if it is of type `T`, or `None` otherwise. pub fn downcast_ref<T: Any>(&self) -> Option<&T> { self.as_any().downcast_ref::<T>() } - /// Returns a mutable reference to the concrete value if it is of type `T`, or `None` otherwise. pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> { self.as_mut_any().downcast_mut::<T>() } @@ -50,7 +43,6 @@ impl<T: Any + Clone> CloneAny for T { fn clone_box(&self) -> Box<dyn CloneAny> { - // Prevent infinite recursion if T is a nested Box type. if let Some(boxed) = self.as_any().downcast_ref::<Box<dyn CloneAny>>() { return (**boxed).clone_box(); } @@ -78,143 +70,73 @@ /// Type alias for a type-erased user value. pub type GenericUserValue = Box<dyn CloneAny>; -/// A trait for types that represent a set of values that an input can take. -/// -/// Domain types are used to represent the domain of values that a fuzz test input can take -/// throughout the fuzzing process. -/// Domain types are used by the fuzzing engine through the APIs defined in this trait. -/// -/// ## Serialization/Deserialization of CorpusValue and UserValue relationship. -/// -/// The values drawn from a domain are always of type `CorpusValue`. These values are the -/// internal representation of the value and they are not directly usable by the fuzz property -/// function. -/// -/// The `get_user_value` method is used to retrieve the user value from the corpus value. For -/// example, if the domain outputs an `&str` then the CorpusValue could be a `String` and the -/// `get_user_value` method would be used to retrieve the `&str` from the `String`. -/// -/// The `parse_corpus` (resp. `serialize_corpus`) method is used deserialize (resp. serialize) -/// the corpus value from (to) a slice of bytes (resp. a vector of bytes). -/// -/// ### Relationship diagram -/// -/// The methods below are responsible for transforming between `UserValue`, `CorpusValue`, and the -/// serialized representation of `CorpusValue`. Here's a quick overview: -/// -/// ```text -/// +-- get_user_value() <---+ +-- parse_corpus() <---+ -/// | | | | -/// v | v | -/// UserValue<'a> CorpusValue &[u8] -/// | ^ -/// | | -/// +-> serialize_corpus() + -/// ``` -pub trait Domain { - /// The type of the values that the domain outputs. This should of the same type as the - /// parameter of the fuzz property function. +pub trait Domain: 'static { + /// The user-facing type representing values in this domain. type UserValue<'user>; - /// The type of the corpus from which the values of type UserValue are drawn. - /// For example, if the domain should output an `&str` then the UserValue should be `&str` and - /// the CorpusValue could the owned data structured that the `&str` points to (eg: String). - /// The CorpusValue type should implement `serde::Serialize`, `serde::de::DeserializeOwned` and - /// `Clone`. + + /// The type representing the value stored in the corpus for this domain. 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>; + /// Produces a new initial `CorpusValue` for this domain. + 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. + /// Mutates the given `corpus_value` in place. fn mutate( - &self, - val: &mut Self::CorpusValue, + &mut self, + corpus_value: &mut Self::CorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, ) -> anyhow::Result<()>; - /// Retrieves a UserValue from a given CorpusValue. - /// - /// This is used to convert the corpus value into the user value that can then be passed to the - /// fuzz property function. + /// Obtains a `UserValue` from a `corpus_value`. fn get_user_value<'a>( &self, corpus_value: &'a Self::CorpusValue, ) -> anyhow::Result<Self::UserValue<'a>>; - /// Turns a slice of bytes into `CorpusValue`. - /// - /// By default, it uses Postcard to deserialize the data. + /// Deserializes a `CorpusValue` from a byte slice. fn parse_corpus(&self, data: &[u8]) -> anyhow::Result<Self::CorpusValue> { postcard::from_bytes(data).context("Failed to deserialize corpus value from bytes") } /// Serializes `corpus_value` to a Vec of bytes (ie, Vec<u8>). - /// - /// By default, it uses Postcard to serialize the data. 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(()) + } } -/// A type-erased interface for Domain types. -/// -/// This trait is used to expose a common interface for Domain types to the fuzzing engine through -/// the `FuzzTest` trait. -/// The methods of this trait are similar to that of the `Domain` trait, but they operate on -/// a type-erased `GenericCorpusValue` instead of an explicit type. pub trait GenericDomain { - /// 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>; - - /// Mutates the value in `val` to a new value drawn from the domain. - /// - /// See `Domain::mutate` for more details. + fn init(&mut self, rng: &mut dyn rand::Rng) -> anyhow::Result<GenericCorpusValue>; fn mutate( - &self, + &mut self, val: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, ) -> anyhow::Result<()>; - - /// Parses a slice of bytes into a `GenericCorpusValue`. - /// - /// See `Domain::parse_corpus` for more details. fn parse_corpus(&self, data: &[u8]) -> anyhow::Result<GenericCorpusValue>; - - /// Serializes a `GenericCorpusValue` to a Vec of bytes (ie, Vec<u8>). - /// - /// See `Domain::serialize_corpus` for more details. fn serialize_corpus(&self, val: &GenericCorpusValue) -> anyhow::Result<Vec<u8>>; } -/// Blanket implementation of the `GenericDomain` trait for types implementing the `Domain` trait. -/// -/// This implementation is a simple pass-through implementation that uses the methods of the -/// `Domain` trait to implement the `GenericDomain` trait. impl<D> GenericDomain for D where 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)?)) } - /// Retrieves the underlying `Domain`'s `CorpusValue` from the `GenericCorpusValue` and calls - /// the underlying `Domain::mutate` method. - /// - /// If the `GenericCorpusValue` cannot be downcasted to the underlying `Domain`'s `CorpusValue` - /// then an Error is returned. - /// - /// See `GenericDomain::mutate` for more details. fn mutate( - &self, + &mut self, val: &mut GenericCorpusValue, rng: &mut dyn rand::Rng, only_shrink: bool, @@ -226,21 +148,10 @@ ) } - /// Calls the underlying `Domain::parse_corpus` method with the `data` and wraps the resulting - /// `CorpusValue` in a `GenericCorpusValue`. - /// - /// See `GenericDomain::parse_corpus` for more details. fn parse_corpus(&self, data: &[u8]) -> anyhow::Result<GenericCorpusValue> { Ok(Box::new(self.parse_corpus(data)?)) } - /// Retrieves the underlying `Domain`'s `CorpusValue` from the `GenericCorpusValue` and calls - /// the underlying `Domain::serialize_corpus` method. - /// - /// If the `GenericCorpusValue` cannot be downcasted to the underlying `Domain`'s `CorpusValue` - /// then an Error is returned. - /// - /// See `GenericDomain::serialize_corpus` for more details. fn serialize_corpus(&self, val: &GenericCorpusValue) -> anyhow::Result<Vec<u8>> { self.serialize_corpus(val.downcast_ref().context("Failed to retrieve the Corpus Value")?) }
diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs index e2167bf..7872c0d 100644 --- a/rust/src/domains/arbitrary.rs +++ b/rust/src/domains/arbitrary.rs
@@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. + use super::utility::choose_value; use super::utility::mutate_integer; use super::utility::shrink_towards; @@ -36,7 +37,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,29 +47,35 @@ _phantom: PhantomData<T>, } -impl<T> Clone for Arbitrary<T> { +impl<T: Clone> Clone for Arbitrary<T> { fn clone(&self) -> Self { - Self { _phantom: PhantomData } + 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() + 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 { + _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() } } @@ -76,12 +83,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 +108,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 +127,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 +159,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 +193,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 +241,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 +300,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 +337,26 @@ ) -> 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> { Ok(()) } fn mutate( - &self, + &mut self, _val: &mut Self::CorpusValue, _rng: &mut dyn rand::Rng, _only_shrink: bool, @@ -326,6 +371,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 +465,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 +495,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 +527,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 +672,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 +689,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 +712,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 +776,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 +815,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);