diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs index 8ab95c7b0e..b946235cf3 100644 --- a/crates/fuzzing/src/generators.rs +++ b/crates/fuzzing/src/generators.rs @@ -253,6 +253,7 @@ pub struct WasmtimeConfig { canonicalize_nans: bool, interruptable: bool, pub(crate) consume_fuel: bool, + epoch_interruption: bool, /// The Wasmtime memory configuration to use. pub memory_config: MemoryConfig, force_jump_veneers: bool, @@ -441,6 +442,7 @@ impl Config { .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime()) .interruptable(self.wasmtime.interruptable) .consume_fuel(self.wasmtime.consume_fuel) + .epoch_interruption(self.wasmtime.epoch_interruption) .memory_init_cow(self.wasmtime.memory_init_cow) .memory_guaranteed_dense_image_size(std::cmp::min( // Clamp this at 16MiB so we don't get huge in-memory @@ -509,18 +511,44 @@ impl Config { if self.wasmtime.consume_fuel { store.add_fuel(u64::max_value()).unwrap(); } + if self.wasmtime.epoch_interruption { + // Without fuzzing of async execution, we can't test the + // "update deadline and continue" behavior, but we can at + // least test the codegen paths and checks with the + // trapping behavior, which works synchronously too. We'll + // set the deadline one epoch tick in the future; then + // this works exactly like an interrupt flag. We expect no + // traps/interrupts unless we bump the epoch, which we do + // as one particular Timeout mode (`Timeout::Epoch`). + store.epoch_deadline_trap(); + store.set_epoch_deadline(1); + } } /// Generates an arbitrary method of timing out an instance, ensuring that /// this configuration supports the returned timeout. pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result { - if u.arbitrary()? { - self.wasmtime.interruptable = true; - Ok(Timeout::Time(Duration::from_secs(20))) - } else { - self.wasmtime.consume_fuel = true; - Ok(Timeout::Fuel(100_000)) + let time_duration = Duration::from_secs(20); + let timeout = u + .choose(&[ + Timeout::Time(time_duration), + Timeout::Fuel(100_000), + Timeout::Epoch(time_duration), + ])? + .clone(); + match &timeout { + &Timeout::Time(..) => { + self.wasmtime.interruptable = true; + } + &Timeout::Fuel(..) => { + self.wasmtime.consume_fuel = true; + } + &Timeout::Epoch(..) => { + self.wasmtime.epoch_interruption = true; + } + &Timeout::None => unreachable!("Not an option given to choose()"), } + Ok(timeout) } /// Compiles the `wasm` within the `engine` provided. diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 4bf0b54c78..edb5d27e81 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -103,7 +103,7 @@ impl ResourceLimiter for StoreLimits { } /// Methods of timing out execution of a WebAssembly module -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Timeout { /// No timeout is used, it should be guaranteed via some other means that /// the input does not infinite loop. @@ -114,6 +114,9 @@ pub enum Timeout { /// Fuel-based timeouts are used where the specified fuel is all that the /// provided wasm module is allowed to consume. Fuel(u64), + /// An epoch-interruption-based timeout is used with a sleeping + /// thread bumping the epoch counter after the specified duration. + Epoch(Duration), } /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected @@ -145,6 +148,12 @@ pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, let handle = store.interrupt_handle().unwrap(); timeout_state.spawn_timeout(timeout, move || handle.interrupt()); } + // Similar to above, but we bump the epoch rather than set the + // interrupt flag. + Timeout::Epoch(timeout) => { + let engine = store.engine().clone(); + timeout_state.spawn_timeout(timeout, move || engine.increment_epoch()); + } Timeout::None => {} }