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();