Skip to main content

wasmtime_fuzzing/
oracles.rs

1//! Oracles.
2//!
3//! Oracles take a test case and determine whether we have a bug. For example,
4//! one of the simplest oracles is to take a Wasm binary as our input test case,
5//! validate and instantiate it, and (implicitly) check that no assertions
6//! failed or segfaults happened. A more complicated oracle might compare the
7//! result of executing a Wasm file with and without optimizations enabled, and
8//! make sure that the two executions are observably identical.
9//!
10//! When an oracle finds a bug, it should report it to the fuzzing engine by
11//! panicking.
12
13pub mod component_api;
14pub mod diff_wasmi;
15pub mod diff_wasmtime;
16pub mod dummy;
17pub mod engine;
18mod gc_access;
19pub mod memory;
20mod stacks;
21
22/// Oracle for sequences of Wasmtime API calls.
23pub mod api;
24pub use api::make_api_calls;
25
26use self::diff_wasmtime::WasmtimeInstance;
27use self::engine::{DiffEngine, DiffInstance};
28use crate::generators::ExceptionOps;
29use crate::generators::GcOps;
30use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType};
31use crate::single_module_fuzzer::KnownValid;
32use crate::{YieldN, block_on};
33use arbitrary::Arbitrary;
34pub use gc_access::gc_access;
35pub use stacks::check_stacks;
36use std::future::Future;
37use std::pin::Pin;
38use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
39use std::sync::{Arc, Condvar, Mutex};
40use std::task::{Context, Poll};
41use std::time::{Duration, Instant};
42use wasmtime::*;
43use wasmtime_wast::WastContext;
44
45#[cfg(v8)]
46mod diff_v8;
47
48static CNT: AtomicUsize = AtomicUsize::new(0);
49
50/// Logs a wasm file to the filesystem to make it easy to figure out what wasm
51/// was used when debugging.
52pub fn log_wasm(wasm: &[u8]) {
53    super::init_fuzzing();
54
55    if !log::log_enabled!(log::Level::Debug) {
56        return;
57    }
58
59    let i = CNT.fetch_add(1, SeqCst);
60    let name = format!("testcase{i}.wasm");
61    std::fs::write(&name, wasm).expect("failed to write wasm file");
62    log::debug!("wrote wasm file to `{name}`");
63    let wat = format!("testcase{i}.wat");
64    match wasmprinter::print_bytes(wasm) {
65        Ok(s) => {
66            std::fs::write(&wat, s).expect("failed to write wat file");
67            log::debug!("wrote wat file to `{wat}`");
68        }
69        // If wasmprinter failed remove a `*.wat` file, if any, to avoid
70        // confusing a preexisting one with this wasm which failed to get
71        // printed.
72        Err(e) => {
73            log::debug!("failed to print to wat: {e}");
74            drop(std::fs::remove_file(&wat));
75        }
76    }
77}
78
79/// The `T` in `Store<T>` for fuzzing stores, used to limit resource
80/// consumption during fuzzing.
81#[derive(Clone)]
82pub struct StoreLimits(Arc<LimitsState>);
83
84struct LimitsState {
85    /// Remaining memory, in bytes, left to allocate
86    remaining_memory: AtomicUsize,
87    /// Remaining amount of memory that's allowed to be copied via a growth.
88    remaining_copy_allowance: AtomicUsize,
89    /// Whether or not an allocation request has been denied
90    oom: AtomicBool,
91}
92
93/// Allow up to 1G which is well below the 2G limit on OSS-Fuzz and should allow
94/// most interesting behavior.
95const MAX_MEMORY: usize = 1 << 30;
96
97/// Maximum number of bytes to be copied (conservatively) which should enable
98/// growth up to `MAX_MEMORY` or at least up to a relatively large amount.
99///
100/// Set to 4G on 64-bit platforms but a bit smaller on 32.
101const MAX_MEMORY_MOVED: usize = if cfg!(target_pointer_width = "32") {
102    1 << 30
103} else {
104    4 << 30
105};
106
107impl StoreLimits {
108    /// Creates the default set of limits for all fuzzing stores.
109    pub fn new() -> StoreLimits {
110        StoreLimits(Arc::new(LimitsState {
111            remaining_memory: AtomicUsize::new(MAX_MEMORY),
112            remaining_copy_allowance: AtomicUsize::new(MAX_MEMORY_MOVED),
113            oom: AtomicBool::new(false),
114        }))
115    }
116
117    fn alloc(&mut self, amt: usize) -> bool {
118        log::trace!("alloc {amt:#x} bytes");
119
120        // Assume that on each allocation of memory that all previous
121        // allocations of memory are moved. This is pretty coarse but is used to
122        // help prevent against fuzz test cases that just move tons of bytes
123        // around continuously. This assumes that all previous memory was
124        // allocated in a single linear memory and growing by `amt` will require
125        // moving all the bytes to a new location. This isn't actually required
126        // all the time nor does it accurately reflect what happens all the
127        // time, but it's a coarse approximation that should be "good enough"
128        // for allowing interesting fuzz behaviors to happen while not timing
129        // out just copying bytes around.
130        let prev_size = MAX_MEMORY - self.0.remaining_memory.load(SeqCst);
131        if self
132            .0
133            .remaining_copy_allowance
134            .try_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(prev_size))
135            .is_err()
136        {
137            self.0.oom.store(true, SeqCst);
138            log::debug!("-> too many bytes moved, rejecting allocation");
139            return false;
140        }
141
142        // If we're allowed to move the bytes, then also check if we're allowed
143        // to actually have this much residence at once.
144        match self
145            .0
146            .remaining_memory
147            .try_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt))
148        {
149            Ok(_) => true,
150            Err(_) => {
151                self.0.oom.store(true, SeqCst);
152                log::debug!("-> OOM hit");
153                false
154            }
155        }
156    }
157
158    fn is_oom(&self) -> bool {
159        self.0.oom.load(SeqCst)
160    }
161}
162
163impl ResourceLimiter for StoreLimits {
164    fn memory_growing(
165        &mut self,
166        current: usize,
167        desired: usize,
168        _maximum: Option<usize>,
169    ) -> Result<bool> {
170        Ok(self.alloc(desired - current))
171    }
172
173    fn table_growing(
174        &mut self,
175        current: usize,
176        desired: usize,
177        _maximum: Option<usize>,
178    ) -> Result<bool> {
179        let delta = (desired - current).saturating_mul(std::mem::size_of::<usize>());
180        Ok(self.alloc(delta))
181    }
182}
183
184/// Methods of timing out execution of a WebAssembly module
185#[derive(Clone, Debug)]
186pub enum Timeout {
187    /// No timeout is used, it should be guaranteed via some other means that
188    /// the input does not infinite loop.
189    None,
190    /// Fuel-based timeouts are used where the specified fuel is all that the
191    /// provided wasm module is allowed to consume.
192    Fuel(u64),
193    /// An epoch-interruption-based timeout is used with a sleeping
194    /// thread bumping the epoch counter after the specified duration.
195    Epoch(Duration),
196}
197
198/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
199/// panic or segfault or anything else that can be detected "passively".
200///
201/// The engine will be configured using provided config.
202pub fn instantiate(
203    wasm: &[u8],
204    known_valid: KnownValid,
205    config: &generators::Config,
206    timeout: Timeout,
207) {
208    let mut store = config.to_store();
209
210    let module = match compile_module(store.engine(), wasm, known_valid, config) {
211        Some(module) => module,
212        None => return,
213    };
214
215    let mut timeout_state = HelperThread::default();
216    match timeout {
217        Timeout::Fuel(fuel) => store.set_fuel(fuel).unwrap(),
218
219        // If a timeout is requested then we spawn a helper thread to wait for
220        // the requested time and then send us a signal to get interrupted. We
221        // also arrange for the thread's sleep to get interrupted if we return
222        // early (or the wasm returns within the time limit), which allows the
223        // thread to get torn down.
224        //
225        // This prevents us from creating a huge number of sleeping threads if
226        // this function is executed in a loop, like it does on nightly fuzzing
227        // infrastructure.
228        Timeout::Epoch(timeout) => {
229            let engine = store.engine().clone();
230            timeout_state.run_periodically(timeout, move || engine.increment_epoch());
231        }
232        Timeout::None => {}
233    }
234
235    instantiate_with_dummy(&mut store, &module);
236}
237
238/// Represents supported commands to the `instantiate_many` function.
239#[derive(Arbitrary, Debug)]
240pub enum Command {
241    /// Instantiates a module.
242    ///
243    /// The value is the index of the module to instantiate.
244    ///
245    /// The module instantiated will be this value modulo the number of modules provided to `instantiate_many`.
246    Instantiate(usize),
247    /// Terminates a "running" instance.
248    ///
249    /// The value is the index of the instance to terminate.
250    ///
251    /// The instance terminated will be this value modulo the number of currently running
252    /// instances.
253    ///
254    /// If no instances are running, the command will be ignored.
255    Terminate(usize),
256}
257
258/// Instantiates many instances from the given modules.
259///
260/// The engine will be configured using the provided config.
261///
262/// The modules are expected to *not* have start functions as no timeouts are configured.
263pub fn instantiate_many(
264    modules: &[Vec<u8>],
265    known_valid: KnownValid,
266    config: &generators::Config,
267    commands: &[Command],
268) {
269    log::debug!("instantiate_many: {commands:#?}");
270
271    assert!(!config.module_config.config.allow_start_export);
272
273    let engine = Engine::new(&config.to_wasmtime()).unwrap();
274
275    let modules = modules
276        .iter()
277        .enumerate()
278        .filter_map(
279            |(i, bytes)| match compile_module(&engine, bytes, known_valid, config) {
280                Some(m) => {
281                    log::debug!("successfully compiled module {i}");
282                    Some(m)
283                }
284                None => {
285                    log::debug!("failed to compile module {i}");
286                    None
287                }
288            },
289        )
290        .collect::<Vec<_>>();
291
292    // If no modules were valid, we're done
293    if modules.is_empty() {
294        return;
295    }
296
297    // This stores every `Store` where a successful instantiation takes place
298    let mut stores = Vec::new();
299    let limits = StoreLimits::new();
300
301    for command in commands {
302        match command {
303            Command::Instantiate(index) => {
304                let index = *index % modules.len();
305                log::info!("instantiating {index}");
306                let module = &modules[index];
307                let mut store = Store::new(&engine, limits.clone());
308                config.configure_store(&mut store);
309
310                if instantiate_with_dummy(&mut store, module).is_some() {
311                    stores.push(Some(store));
312                } else {
313                    log::warn!("instantiation failed");
314                }
315            }
316            Command::Terminate(index) => {
317                if stores.is_empty() {
318                    continue;
319                }
320                let index = *index % stores.len();
321
322                log::info!("dropping {index}");
323                stores.swap_remove(index);
324            }
325        }
326    }
327}
328
329fn compile_module(
330    engine: &Engine,
331    bytes: &[u8],
332    known_valid: KnownValid,
333    config: &generators::Config,
334) -> Option<Module> {
335    log_wasm(bytes);
336
337    match config.compile(engine, bytes) {
338        Ok(module) => Some(module),
339        Err(_) if known_valid == KnownValid::No => None,
340        Err(e) => {
341            if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy {
342                // When using the pooling allocator, accept failures to compile
343                // when arbitrary table element limits have been exceeded as
344                // there is currently no way to constrain the generated module
345                // table types.
346                let string = format!("{e:?}");
347                if string.contains("minimum element size") {
348                    return None;
349                }
350
351                // Allow modules-failing-to-compile which exceed the requested
352                // size for each instance. This is something that is difficult
353                // to control and ensure it always succeeds, so we simply have a
354                // "random" instance size limit and if a module doesn't fit we
355                // move on to the next fuzz input.
356                if string.contains("instance allocation for this module requires") {
357                    return None;
358                }
359
360                // If the pooling allocator is more restrictive on the number of
361                // tables and memories than we allowed wasm-smith to generate
362                // then allow compilation errors along those lines.
363                if c.max_tables_per_module < (config.module_config.config.max_tables as u32)
364                    && string.contains("defined tables count")
365                    && string.contains("exceeds the per-instance limit")
366                {
367                    return None;
368                }
369
370                if c.max_memories_per_module < (config.module_config.config.max_memories as u32)
371                    && string.contains("defined memories count")
372                    && string.contains("exceeds the per-instance limit")
373                {
374                    return None;
375                }
376            }
377
378            panic!("failed to compile module: {e:?}");
379        }
380    }
381}
382
383/// Create a Wasmtime [`Instance`] from a [`Module`] and fill in all imports
384/// with dummy values (e.g., zeroed values, immediately-trapping functions).
385/// Also, this function catches certain fuzz-related instantiation failures and
386/// returns `None` instead of panicking.
387///
388/// TODO: we should implement tracing versions of these dummy imports that
389/// record a trace of the order that imported functions were called in and with
390/// what values. Like the results of exported functions, calls to imports should
391/// also yield the same values for each configuration, and we should assert
392/// that.
393pub fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> {
394    // Creation of imports can fail due to resource limit constraints, and then
395    // instantiation can naturally fail for a number of reasons as well. Bundle
396    // the two steps together to match on the error below.
397    let linker = dummy::dummy_linker(store, module);
398    if let Err(e) = &linker {
399        log::warn!("failed to create dummy linker: {e:?}");
400    }
401    let instance = linker.and_then(|l| l.instantiate(&mut *store, module));
402    unwrap_instance(store, instance)
403}
404
405fn unwrap_instance(
406    store: &Store<StoreLimits>,
407    instance: wasmtime::Result<Instance>,
408) -> Option<Instance> {
409    let e = match instance {
410        Ok(i) => return Some(i),
411        Err(e) => e,
412    };
413
414    log::debug!("failed to instantiate: {e:?}");
415
416    // If the instantiation hit OOM for some reason then that's ok, it's
417    // expected that fuzz-generated programs try to allocate lots of
418    // stuff.
419    if store.data().is_oom() {
420        return None;
421    }
422
423    // Allow traps which can happen normally with `unreachable` or a timeout or
424    // such.
425    if e.is::<Trap>()
426        // Also allow failures to instantiate as a result of hitting pooling
427        // limits.
428        || e.is::<wasmtime::PoolConcurrencyLimitError>()
429        // And GC heap OOMs.
430        || e.is::<wasmtime::GcHeapOutOfMemory<()>>()
431        // And thrown exceptions.
432        || e.is::<wasmtime::ThrownException>()
433    {
434        return None;
435    }
436
437    let string = e.to_string();
438
439    // Currently we instantiate with a `Linker` which can't instantiate
440    // every single module under the sun due to using name-based resolution
441    // rather than positional-based resolution
442    if string.contains("incompatible import type") {
443        return None;
444    }
445
446    // Everything else should be a bug in the fuzzer or a bug in wasmtime
447    panic!("failed to instantiate: {e:?}");
448}
449
450/// Evaluate the function identified by `name` in two different engine
451/// instances--`lhs` and `rhs`.
452///
453/// Returns `Ok(true)` if more evaluations can happen or `Ok(false)` if the
454/// instances may have drifted apart and no more evaluations can happen.
455///
456/// # Panics
457///
458/// This will panic if the evaluation is different between engines (e.g.,
459/// results are different, hashed instance is different, one side traps, etc.).
460pub fn differential(
461    lhs: &mut dyn DiffInstance,
462    lhs_engine: &dyn DiffEngine,
463    rhs: &mut WasmtimeInstance,
464    name: &str,
465    args: &[DiffValue],
466    result_tys: &[DiffValueType],
467) -> wasmtime::Result<bool> {
468    log::debug!("Evaluating: `{name}` with {args:?}");
469    let lhs_results = match lhs.evaluate(name, args, result_tys) {
470        Ok(Some(results)) => Ok(results),
471        Err(e) => Err(e),
472        // this engine couldn't execute this type signature, so discard this
473        // execution by returning success.
474        Ok(None) => return Ok(true),
475    };
476    log::debug!(" -> lhs results on {}: {:?}", lhs.name(), &lhs_results);
477
478    let rhs_results = rhs
479        .evaluate(name, args, result_tys)
480        // wasmtime should be able to invoke any signature, so unwrap this result
481        .map(|results| results.unwrap());
482    log::debug!(" -> rhs results on {}: {:?}", rhs.name(), &rhs_results);
483
484    // If Wasmtime hit its OOM condition, which is possible since it's set
485    // somewhat low while fuzzing, then don't return an error but return
486    // `false` indicating that differential fuzzing must stop. There's no
487    // guarantee the other engine has the same OOM limits as Wasmtime, and
488    // it's assumed that Wasmtime is configured to have a more conservative
489    // limit than the other engine.
490    if rhs.is_oom() {
491        return Ok(false);
492    }
493
494    match DiffEqResult::new(lhs_engine, lhs_results, rhs_results) {
495        DiffEqResult::Success(lhs, rhs) => assert_eq!(lhs, rhs),
496        DiffEqResult::Poisoned => return Ok(false),
497        DiffEqResult::Failed => {}
498    }
499
500    for (global, ty) in rhs.exported_globals() {
501        log::debug!("Comparing global `{global}`");
502        let lhs = match lhs.get_global(&global, ty) {
503            Some(val) => val,
504            None => continue,
505        };
506        let rhs = rhs.get_global(&global, ty).unwrap();
507        assert_eq!(lhs, rhs);
508    }
509    for (memory, shared) in rhs.exported_memories() {
510        log::debug!("Comparing memory `{memory}`");
511        let lhs = match lhs.get_memory(&memory, shared) {
512            Some(val) => val,
513            None => continue,
514        };
515        let rhs = rhs.get_memory(&memory, shared).unwrap();
516        if lhs == rhs {
517            continue;
518        }
519        eprintln!("differential memory is {} bytes long", lhs.len());
520        eprintln!("wasmtime memory is     {} bytes long", rhs.len());
521        panic!("memories have differing values");
522    }
523
524    Ok(true)
525}
526
527/// Result of comparing the result of two operations during differential
528/// execution.
529pub enum DiffEqResult<T, U> {
530    /// Both engines succeeded.
531    Success(T, U),
532    /// The result has reached the state where engines may have diverged and
533    /// results can no longer be compared.
534    Poisoned,
535    /// Both engines failed with the same error message, and internal state
536    /// should still match between the two engines.
537    Failed,
538}
539
540fn wasmtime_trap_is_non_deterministic(trap: &Trap) -> bool {
541    match trap {
542        // Allocations being too large for the GC are
543        // implementation-defined.
544        Trap::AllocationTooLarge |
545        // Stack size, and therefore when overflow happens, is
546        // implementation-defined.
547        Trap::StackOverflow => true,
548        _ => false,
549    }
550}
551
552fn wasmtime_error_is_non_deterministic(error: &wasmtime::Error) -> bool {
553    match error.downcast_ref::<Trap>() {
554        Some(trap) => wasmtime_trap_is_non_deterministic(trap),
555
556        // For general, unknown errors, we can't rely on this being
557        // a deterministic Wasm failure that both engines handled
558        // identically, leaving Wasm in identical states. We could
559        // just as easily be hitting engine-specific failures, like
560        // different implementation-defined limits. So simply poison
561        // this execution and move on to the next test.
562        None => true,
563    }
564}
565
566impl<T, U> DiffEqResult<T, U> {
567    /// Computes the differential result from executing in two different
568    /// engines.
569    pub fn new(
570        lhs_engine: &dyn DiffEngine,
571        lhs_result: Result<T>,
572        rhs_result: Result<U>,
573    ) -> DiffEqResult<T, U> {
574        match (lhs_result, rhs_result) {
575            (Ok(lhs_result), Ok(rhs_result)) => DiffEqResult::Success(lhs_result, rhs_result),
576
577            // Handle all non-deterministic errors by poisoning this execution's
578            // state, so that we simply move on to the next test.
579            (Err(lhs), _) if lhs_engine.is_non_deterministic_error(&lhs) => {
580                log::debug!("lhs failed non-deterministically: {lhs:?}");
581                DiffEqResult::Poisoned
582            }
583            (_, Err(rhs)) if wasmtime_error_is_non_deterministic(&rhs) => {
584                log::debug!("rhs failed non-deterministically: {rhs:?}");
585                DiffEqResult::Poisoned
586            }
587
588            // Both sides failed deterministically. Check that the trap and
589            // state at the time of failure is the same.
590            (Err(lhs), Err(rhs)) => {
591                let rhs = rhs
592                    .downcast::<Trap>()
593                    .expect("non-traps handled in earlier match arm");
594
595                debug_assert!(
596                    !lhs_engine.is_non_deterministic_error(&lhs),
597                    "non-deterministic traps handled in earlier match arm",
598                );
599                debug_assert!(
600                    !wasmtime_trap_is_non_deterministic(&rhs),
601                    "non-deterministic traps handled in earlier match arm",
602                );
603
604                lhs_engine.assert_error_match(&lhs, &rhs);
605                DiffEqResult::Failed
606            }
607
608            // A real bug is found if only one side fails.
609            (Ok(_), Err(err)) => panic!("only the `rhs` failed for this input: {err:?}"),
610            (Err(err), Ok(_)) => panic!("only the `lhs` failed for this input: {err:?}"),
611        }
612    }
613}
614
615/// Executes the wast `test` with the `config` specified.
616///
617/// Ensures that wast tests pass regardless of the `Config`.
618pub fn wast_test(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<()> {
619    crate::init_fuzzing();
620
621    let mut fuzz_config: generators::Config = u.arbitrary()?;
622    fuzz_config.module_config.shared_memory = true;
623    let test: generators::WastTest = u.arbitrary()?;
624
625    let test = &test.test;
626
627    // FIXME(#14222) stack-switching and asan aren't integrated yet, so skip
628    // stack-switching tests when asan is enabled.
629    if cfg!(asan) && test.config.stack_switching == Some(true) {
630        return Err(arbitrary::Error::IncorrectFormat);
631    }
632
633    if test.config.component_model_async() || u.arbitrary()? {
634        fuzz_config.enable_async(u)?;
635    }
636
637    // Discard tests that allocate a lot of memory as we don't want to OOM the
638    // fuzzer and we also limit memory growth which would cause the test to
639    // fail.
640    if test.config.hogs_memory.unwrap_or(false) {
641        return Err(arbitrary::Error::IncorrectFormat);
642    }
643
644    // Like integration tests skip all pooling allocator tests on 32-bit
645    // platforms.
646    if cfg!(target_pointer_width = "32")
647        && matches!(
648            fuzz_config.wasmtime.strategy,
649            generators::InstanceAllocationStrategy::Pooling(_)
650        )
651    {
652        return Err(arbitrary::Error::IncorrectFormat);
653    }
654
655    // Transform `fuzz_config` to be valid for `test` and make sure that this
656    // test is supposed to pass.
657    let wast_config = fuzz_config.make_wast_test_compliant(test);
658    if test.should_fail(&wast_config) {
659        return Err(arbitrary::Error::IncorrectFormat);
660    }
661
662    // Winch requires AVX and AVX2 for SIMD tests to pass so don't run the test
663    // if either isn't enabled.
664    if fuzz_config.wasmtime.compiler_strategy == CompilerStrategy::Winch
665        && test.config.simd()
666        && (fuzz_config
667            .wasmtime
668            .codegen_flag("has_avx")
669            .is_some_and(|value| value == "false")
670            || fuzz_config
671                .wasmtime
672                .codegen_flag("has_avx2")
673                .is_some_and(|value| value == "false"))
674    {
675        log::warn!(
676            "Skipping Wast test because Winch doesn't support SIMD tests with AVX or AVX2 disabled"
677        );
678        return Err(arbitrary::Error::IncorrectFormat);
679    }
680
681    // Fuel and epochs don't play well with threads right now, so exclude any
682    // thread-spawning test if it looks like threads are spawned in that case.
683    if fuzz_config.wasmtime.consume_fuel || fuzz_config.wasmtime.epoch_interruption {
684        if test.contents.contains("(thread") {
685            return Err(arbitrary::Error::IncorrectFormat);
686        }
687    }
688
689    log::debug!("running {:?}", test.path);
690    let async_ = if fuzz_config.wasmtime.async_config == generators::AsyncConfig::Disabled {
691        wasmtime_wast::Async::No
692    } else {
693        wasmtime_wast::Async::Yes
694    };
695    log::debug!("async: {async_:?}");
696    let engine = Engine::new(&fuzz_config.to_wasmtime()).unwrap();
697    let mut wast_context = WastContext::new(&engine, async_, move |store| {
698        fuzz_config.configure_store_epoch_and_fuel(store);
699    });
700    wast_context.ignore_error_messages(test.config.spec_test.unwrap_or(false));
701    wast_context
702        .register_spectest(&wasmtime_wast::SpectestConfig {
703            use_shared_memory: true,
704            suppress_prints: true,
705        })
706        .unwrap();
707    wast_context.register_wasmtime().unwrap();
708    wast_context
709        .run_wast(test.path.to_str().unwrap(), test.contents.as_bytes())
710        .unwrap();
711    Ok(())
712}
713
714/// Execute a series of `gc` operations.
715///
716/// Returns the number of `gc` operations which occurred throughout the test
717/// case -- used to test below that gc happens reasonably soon and eventually.
718pub fn gc_ops(mut fuzz_config: generators::Config, mut ops: GcOps) -> Result<usize> {
719    let expected_drops = Arc::new(AtomicUsize::new(0));
720    let num_dropped = Arc::new(AtomicUsize::new(0));
721
722    let num_gcs = Arc::new(AtomicUsize::new(0));
723    {
724        fuzz_config.wasmtime.consume_fuel = true;
725        let mut store = fuzz_config.to_store();
726        store.set_fuel(1_000).unwrap();
727
728        let wasm = ops.to_wasm_binary();
729        log_wasm(&wasm);
730        let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) {
731            Some(m) => m,
732            None => return Ok(0),
733        };
734
735        let mut linker = Linker::new(store.engine());
736
737        // To avoid timeouts, limit the number of explicit GCs we perform per
738        // test case.
739        const MAX_GCS: usize = 5;
740
741        let func_ty = FuncType::new(
742            store.engine(),
743            vec![],
744            vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
745        );
746        let func = Func::new(&mut store, func_ty, {
747            let num_dropped = num_dropped.clone();
748            let expected_drops = expected_drops.clone();
749            let num_gcs = num_gcs.clone();
750            move |mut caller: Caller<'_, StoreLimits>, _params, results| {
751                log::info!("gc_ops: GC");
752                if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
753                    caller.gc(None)?;
754                }
755
756                let a = ExternRef::new(
757                    &mut caller,
758                    CountDrops::new(&expected_drops, num_dropped.clone()),
759                )?;
760                let b = ExternRef::new(
761                    &mut caller,
762                    CountDrops::new(&expected_drops, num_dropped.clone()),
763                )?;
764                let c = ExternRef::new(
765                    &mut caller,
766                    CountDrops::new(&expected_drops, num_dropped.clone()),
767                )?;
768
769                log::info!("gc_ops: gc() -> ({a:?}, {b:?}, {c:?})");
770                results[0] = Some(a).into();
771                results[1] = Some(b).into();
772                results[2] = Some(c).into();
773                Ok(())
774            }
775        });
776        linker.define(&store, "", "gc", func).unwrap();
777
778        linker
779            .func_wrap("", "take_refs", {
780                let expected_drops = expected_drops.clone();
781                move |caller: Caller<'_, StoreLimits>,
782                      a: Option<Rooted<ExternRef>>,
783                      b: Option<Rooted<ExternRef>>,
784                      c: Option<Rooted<ExternRef>>|
785                      -> Result<()> {
786                    log::info!("gc_ops: take_refs({a:?}, {b:?}, {c:?})",);
787
788                    // Do the assertion on each ref's inner data, even though it
789                    // all points to the same atomic, so that if we happen to
790                    // run into a use-after-free bug with one of these refs we
791                    // are more likely to trigger a segfault.
792                    if let Some(a) = a {
793                        let a = a
794                            .data(&caller)?
795                            .unwrap()
796                            .downcast_ref::<CountDrops>()
797                            .unwrap();
798                        assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst));
799                    }
800                    if let Some(b) = b {
801                        let b = b
802                            .data(&caller)?
803                            .unwrap()
804                            .downcast_ref::<CountDrops>()
805                            .unwrap();
806                        assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst));
807                    }
808                    if let Some(c) = c {
809                        let c = c
810                            .data(&caller)?
811                            .unwrap()
812                            .downcast_ref::<CountDrops>()
813                            .unwrap();
814                        assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst));
815                    }
816                    Ok(())
817                }
818            })
819            .unwrap();
820
821        let func_ty = FuncType::new(
822            store.engine(),
823            vec![],
824            vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
825        );
826        let func = Func::new(&mut store, func_ty, {
827            let num_dropped = num_dropped.clone();
828            let expected_drops = expected_drops.clone();
829            move |mut caller, _params, results| {
830                log::info!("gc_ops: make_refs");
831
832                let a = ExternRef::new(
833                    &mut caller,
834                    CountDrops::new(&expected_drops, num_dropped.clone()),
835                )?;
836                let b = ExternRef::new(
837                    &mut caller,
838                    CountDrops::new(&expected_drops, num_dropped.clone()),
839                )?;
840                let c = ExternRef::new(
841                    &mut caller,
842                    CountDrops::new(&expected_drops, num_dropped.clone()),
843                )?;
844
845                log::info!("gc_ops: make_refs() -> ({a:?}, {b:?}, {c:?})");
846
847                results[0] = Some(a).into();
848                results[1] = Some(b).into();
849                results[2] = Some(c).into();
850
851                Ok(())
852            }
853        });
854        linker.define(&store, "", "make_refs", func).unwrap();
855
856        let func_ty = FuncType::new(
857            store.engine(),
858            vec![ValType::Ref(RefType::new(true, HeapType::Struct))],
859            vec![],
860        );
861
862        let func = Func::new(&mut store, func_ty, {
863            move |_caller: Caller<'_, StoreLimits>, _params, _results| {
864                log::info!("gc_ops: take_struct(<ref null struct>)");
865                Ok(())
866            }
867        });
868
869        linker.define(&store, "", "take_struct", func).unwrap();
870
871        let func_ty = FuncType::new(
872            store.engine(),
873            vec![ValType::Ref(RefType::new(true, HeapType::Eq))],
874            vec![],
875        );
876
877        let func = Func::new(&mut store, func_ty, {
878            move |_caller: Caller<'_, StoreLimits>, _params, _results| {
879                log::info!("gc_ops: take_eq(<ref null eq>)");
880                Ok(())
881            }
882        });
883
884        linker.define(&store, "", "take_eq", func).unwrap();
885
886        let func_ty = FuncType::new(
887            store.engine(),
888            vec![ValType::Ref(RefType::new(true, HeapType::Array))],
889            vec![],
890        );
891
892        let func = Func::new(&mut store, func_ty, {
893            move |_caller: Caller<'_, StoreLimits>, _params, _results| {
894                log::info!("gc_ops: take_array(<ref null array>)");
895                Ok(())
896            }
897        });
898
899        linker.define(&store, "", "take_array", func).unwrap();
900
901        // `take_i31` receives an `i31ref` along with the guest's inline
902        // `i31.get_s` and `i31.get_u` results, and asserts that the host's own
903        // view of the i31 matches the values the Wasm instructions produced.
904        linker
905            .func_wrap("", "take_i31", {
906                move |_caller: Caller<'_, StoreLimits>,
907                      r: Option<I31>,
908                      wasm_get_s: i32,
909                      wasm_get_u: i32|
910                      -> Result<()> {
911                    log::info!("gc_ops: take_i31({r:?}, get_s={wasm_get_s}, get_u={wasm_get_u})");
912                    if let Some(i31) = r {
913                        assert_eq!(
914                            i31.get_i32(),
915                            wasm_get_s,
916                            "host `i31.get_s` must match inline Wasm `i31.get_s`"
917                        );
918                        assert_eq!(
919                            i31.get_u32() as i32,
920                            wasm_get_u,
921                            "host `i31.get_u` must match inline Wasm `i31.get_u`"
922                        );
923                    }
924                    Ok(())
925                }
926            })
927            .unwrap();
928
929        for imp in module.imports() {
930            if imp.module() == "" {
931                let name = imp.name();
932                if name.starts_with("take_struct_") || name.starts_with("take_array_") {
933                    if let wasmtime::ExternType::Func(ft) = imp.ty() {
934                        let imp_name = name.to_string();
935                        let func =
936                            Func::new(&mut store, ft.clone(), move |_caller, _params, _results| {
937                                log::info!("gc_ops: {imp_name}(<typed ref>)");
938                                Ok(())
939                            });
940                        linker.define(&store, "", name, func).unwrap();
941                    }
942                }
943            }
944        }
945
946        let instance = linker.instantiate(&mut store, &module).unwrap();
947        let run = instance.get_func(&mut store, "run").unwrap();
948
949        {
950            let mut scope = RootScope::new(&mut store);
951
952            log::info!(
953                "gc_ops: begin allocating {} externref arguments",
954                ops.limits.num_globals
955            );
956            let args: Vec<_> = (0..ops.limits.num_params)
957                .map(|_| {
958                    Ok(Val::ExternRef(Some(ExternRef::new(
959                        &mut scope,
960                        CountDrops::new(&expected_drops, num_dropped.clone()),
961                    )?)))
962                })
963                .collect::<Result<_>>()?;
964            log::info!(
965                "gc_ops: end allocating {} externref arguments",
966                ops.limits.num_globals
967            );
968
969            // The generated function should always return a trap. The valid
970            // traps are table-out-of-bounds (through `table.get`/`table.set`),
971            // array-out-of-bounds (through `array.get`/`array.set`), or an
972            // out-of-fuel trap. Otherwise any other error is unexpected and
973            // should fail fuzzing.
974            log::info!("gc_ops: calling into Wasm `run` function");
975            let err = run.call(&mut scope, &args, &mut []).unwrap_err();
976            if err.is::<GcHeapOutOfMemory<CountDrops>>() || err.is::<GcHeapOutOfMemory<()>>() {
977                // Accept GC OOM as an allowed outcome for this fuzzer.
978            } else {
979                let trap = err
980                    .downcast::<Trap>()
981                    .expect("if not GC oom, error should be a Wasm trap");
982                match trap {
983                    Trap::TableOutOfBounds
984                    | Trap::ArrayOutOfBounds
985                    | Trap::OutOfFuel
986                    | Trap::AllocationTooLarge => {}
987                    _ => panic!("unexpected trap: {trap}"),
988                }
989            }
990        }
991
992        // Do a final GC after running the Wasm.
993        store.gc(None)?;
994    }
995
996    assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst));
997    return Ok(num_gcs.load(SeqCst));
998
999    struct CountDrops(Arc<AtomicUsize>);
1000
1001    impl CountDrops {
1002        fn new(expected_drops: &AtomicUsize, num_dropped: Arc<AtomicUsize>) -> Self {
1003            let expected = expected_drops.fetch_add(1, SeqCst);
1004            log::info!(
1005                "CountDrops::new: expected drops: {expected} -> {}",
1006                expected + 1
1007            );
1008            Self(num_dropped)
1009        }
1010    }
1011
1012    impl Drop for CountDrops {
1013        fn drop(&mut self) {
1014            let drops = self.0.fetch_add(1, SeqCst);
1015            log::info!("CountDrops::drop: actual drops: {drops} -> {}", drops + 1);
1016        }
1017    }
1018}
1019
1020/// Execute a series of exception-related operations.
1021pub fn exception_ops(mut fuzz_config: generators::Config, mut ops: ExceptionOps) -> Result<()> {
1022    let module_cfg = &mut fuzz_config.module_config.config;
1023    // Force exceptions, but don't force Wasm GC on: Winch doesn't support it,
1024    // and Cranelift should use its generated GC setting.
1025    module_cfg.exceptions_enabled = true;
1026    module_cfg.reference_types_enabled = true;
1027
1028    let expected = ops.expected_result();
1029
1030    let wasm = ops.to_wasm_binary();
1031    log_wasm(&wasm);
1032
1033    let mut store = fuzz_config.to_store();
1034
1035    let module = compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config)
1036        .ok_or_else(|| wasmtime::format_err!("Compilation failed"))?;
1037    let mut linker = Linker::new(store.engine());
1038
1039    let check_ty = FuncType::new(store.engine(), [ValType::I32, ValType::I32], []);
1040    let check_func = Func::new(&mut store, check_ty, |_caller, params, _results| {
1041        let actual = params[0].unwrap_i32();
1042        let expected = params[1].unwrap_i32();
1043        assert_eq!(actual, expected, "check_i32 mismatch");
1044        Ok(())
1045    });
1046    linker.define(&store, "", "check_i32", check_func).unwrap();
1047
1048    let instance = linker.instantiate(&mut store, &module).unwrap();
1049    let run = instance.get_func(&mut store, "run").unwrap();
1050
1051    let mut results = [Val::I32(0)];
1052    match run.call(&mut store, &[], &mut results) {
1053        Ok(()) => {
1054            let actual = results[0].unwrap_i32();
1055            assert_eq!(
1056                actual, expected,
1057                "exception_ops: run returned {actual}, expected {expected} \
1058                 (one catch per scenario)"
1059            );
1060        }
1061        Err(e) => {
1062            // AllocationTooLarge / GcHeapOutOfMemory are acceptable resource-limit traps.
1063            if let Some(trap) = e.downcast_ref::<Trap>() {
1064                match trap {
1065                    Trap::AllocationTooLarge => return Ok(()),
1066                    _ => {}
1067                }
1068            }
1069            if e.is::<GcHeapOutOfMemory<()>>() {
1070                return Ok(());
1071            }
1072            // Any other error (including ThrownException) is unexpected.
1073            panic!("exception_ops: unexpected error during execution: {e:?}");
1074        }
1075    }
1076
1077    Ok(())
1078}
1079
1080#[derive(Default)]
1081struct HelperThread {
1082    state: Arc<HelperThreadState>,
1083    thread: Option<std::thread::JoinHandle<()>>,
1084}
1085
1086#[derive(Default)]
1087struct HelperThreadState {
1088    should_exit: Mutex<bool>,
1089    should_exit_cvar: Condvar,
1090}
1091
1092impl HelperThread {
1093    fn run_periodically(&mut self, dur: Duration, mut closure: impl FnMut() + Send + 'static) {
1094        let state = self.state.clone();
1095        self.thread = Some(std::thread::spawn(move || {
1096            // Using our mutex/condvar we wait here for the first of `dur` to
1097            // pass or the `HelperThread` instance to get dropped.
1098            let mut should_exit = state.should_exit.lock().unwrap();
1099            while !*should_exit {
1100                let (lock, result) = state
1101                    .should_exit_cvar
1102                    .wait_timeout(should_exit, dur)
1103                    .unwrap();
1104                should_exit = lock;
1105                // If we timed out for sure then there's no need to continue
1106                // since we'll just abort on the next `checked_sub` anyway.
1107                if result.timed_out() {
1108                    closure();
1109                }
1110            }
1111        }));
1112    }
1113}
1114
1115impl Drop for HelperThread {
1116    fn drop(&mut self) {
1117        let thread = match self.thread.take() {
1118            Some(thread) => thread,
1119            None => return,
1120        };
1121        // Signal our thread that it should exit and wake it up in case it's
1122        // sleeping.
1123        *self.state.should_exit.lock().unwrap() = true;
1124        self.state.should_exit_cvar.notify_one();
1125
1126        // ... and then wait for the thread to exit to ensure we clean up
1127        // after ourselves.
1128        thread.join().unwrap();
1129    }
1130}
1131
1132/// Instantiates a wasm module and runs its exports with dummy values, all in
1133/// an async fashion.
1134///
1135/// Attempts to stress yields in host functions to ensure that exiting and
1136/// resuming a wasm function call works.
1137pub fn call_async(wasm: &[u8], config: &generators::Config, mut poll_amts: &[u32]) {
1138    let mut store = config.to_store();
1139    let module = match compile_module(store.engine(), wasm, KnownValid::Yes, config) {
1140        Some(module) => module,
1141        None => return,
1142    };
1143
1144    // Configure a helper thread to periodically increment the epoch to
1145    // forcibly enable yields-via-epochs if epochs are in use. Note that this
1146    // is required because the wasm isn't otherwise guaranteed to necessarily
1147    // call any imports which will also increment the epoch.
1148    let mut helper_thread = HelperThread::default();
1149    if let generators::AsyncConfig::YieldWithEpochs { dur, .. } = &config.wasmtime.async_config {
1150        let engine = store.engine().clone();
1151        helper_thread.run_periodically(*dur, move || engine.increment_epoch());
1152    }
1153
1154    // Generate a `Linker` where all function imports are custom-built to yield
1155    // periodically and additionally increment the epoch.
1156    let mut imports = Vec::new();
1157    for import in module.imports() {
1158        let item = match import.ty() {
1159            ExternType::Func(ty) => {
1160                let poll_amt = take_poll_amt(&mut poll_amts);
1161                Func::new_async(&mut store, ty.clone(), move |caller, _, results| {
1162                    let ty = ty.clone();
1163                    Box::new(async move {
1164                        caller.engine().increment_epoch();
1165                        log::info!("yielding {poll_amt} times in import");
1166                        YieldN(poll_amt).await;
1167                        for (ret_ty, result) in ty.results().zip(results) {
1168                            *result = ret_ty.default_value().unwrap();
1169                        }
1170                        Ok(())
1171                    })
1172                })
1173                .into()
1174            }
1175            other_ty => match other_ty.default_value(&mut store) {
1176                Ok(item) => item,
1177                Err(e) => {
1178                    log::warn!("couldn't create import for {import:?}: {e:?}");
1179                    return;
1180                }
1181            },
1182        };
1183        imports.push(item);
1184    }
1185
1186    // Run the instantiation process, asynchronously, and if everything
1187    // succeeds then pull out the instance.
1188    // log::info!("starting instantiation");
1189    let instance = block_on(Timeout {
1190        future: Instance::new_async(&mut store, &module, &imports),
1191        polls: take_poll_amt(&mut poll_amts),
1192        end: Instant::now() + Duration::from_millis(2_000),
1193    });
1194    let instance = match instance {
1195        Ok(instantiation_result) => match unwrap_instance(&store, instantiation_result) {
1196            Some(instance) => instance,
1197            None => {
1198                log::info!("instantiation hit a nominal error");
1199                return; // resource exhaustion or limits met
1200            }
1201        },
1202        Err(_) => {
1203            log::info!("instantiation failed to complete");
1204            return; // Timed out or ran out of polls
1205        }
1206    };
1207
1208    // Run each export of the instance in the same manner as instantiation
1209    // above. Dummy values are passed in for argument values here:
1210    //
1211    // TODO: this should probably be more clever about passing in arguments for
1212    // example they might be used as pointers or something and always using 0
1213    // isn't too interesting.
1214    let funcs = instance
1215        .exports(&mut store)
1216        .filter_map(|e| {
1217            let name = e.name().to_string();
1218            let func = e.into_extern().into_func()?;
1219            Some((name, func))
1220        })
1221        // Each function gets up to 2 seconds, so don't let a lot of exports
1222        // which all infinitely loop lock up the fuzzer. Limit the number of
1223        // exports run.
1224        .take(10)
1225        .collect::<Vec<_>>();
1226    for (name, func) in funcs {
1227        let ty = func.ty(&store);
1228        let params = ty
1229            .params()
1230            .map(|ty| ty.default_value().unwrap())
1231            .collect::<Vec<_>>();
1232        let mut results = ty
1233            .results()
1234            .map(|ty| ty.default_value().unwrap())
1235            .collect::<Vec<_>>();
1236
1237        log::info!("invoking export {name:?}");
1238        let future = func.call_async(&mut store, &params, &mut results);
1239        match block_on(Timeout {
1240            future,
1241            polls: take_poll_amt(&mut poll_amts),
1242            end: Instant::now() + Duration::from_millis(2_000),
1243        }) {
1244            // On success or too many polls, try the next export.
1245            Ok(_) | Err(Exhausted::Polls) => {}
1246
1247            // If time ran out then stop the current test case as we might have
1248            // already sucked up a lot of time for this fuzz test case so don't
1249            // keep it going.
1250            Err(Exhausted::Time) => return,
1251        }
1252    }
1253
1254    fn take_poll_amt(polls: &mut &[u32]) -> u32 {
1255        match polls.split_first() {
1256            Some((a, rest)) => {
1257                *polls = rest;
1258                *a
1259            }
1260            None => 0,
1261        }
1262    }
1263
1264    /// Helper future for applying a timeout to `future` up to either when `end`
1265    /// is the current time or `polls` polls happen.
1266    ///
1267    /// Note that this helps to time out infinite loops in wasm, for example.
1268    struct Timeout<F> {
1269        future: F,
1270        /// If the future isn't ready by this time then the `Timeout<F>` future
1271        /// will return `None`.
1272        end: Instant,
1273        /// If the future doesn't resolve itself in this many calls to `poll`
1274        /// then the `Timeout<F>` future will return `None`.
1275        polls: u32,
1276    }
1277
1278    enum Exhausted {
1279        Time,
1280        Polls,
1281    }
1282
1283    impl<F: Future> Future for Timeout<F> {
1284        type Output = Result<F::Output, Exhausted>;
1285
1286        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1287            let (end, polls, future) = unsafe {
1288                let me = self.get_unchecked_mut();
1289                (me.end, &mut me.polls, Pin::new_unchecked(&mut me.future))
1290            };
1291            match future.poll(cx) {
1292                Poll::Ready(val) => Poll::Ready(Ok(val)),
1293                Poll::Pending => {
1294                    if Instant::now() >= end {
1295                        log::warn!("future operation timed out");
1296                        return Poll::Ready(Err(Exhausted::Time));
1297                    }
1298                    if *polls == 0 {
1299                        log::warn!("future operation ran out of polls");
1300                        return Poll::Ready(Err(Exhausted::Polls));
1301                    }
1302                    *polls -= 1;
1303                    Poll::Pending
1304                }
1305            }
1306        }
1307    }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312    use super::*;
1313    use crate::test::{gen_until_pass, test_n_times};
1314    use wasmparser::{Validator, WasmFeatures};
1315
1316    // Test that the `gc_ops` fuzzer eventually runs the gc function in the host.
1317    // We've historically had issues where this fuzzer accidentally wasn't fuzzing
1318    // anything for a long time so this is an attempt to prevent that from happening
1319    // again.
1320    #[test]
1321    fn gc_ops_eventually_gcs() {
1322        // Skip if we're under emulation because some fuzz configurations will do
1323        // large address space reservations that QEMU doesn't handle well.
1324        if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
1325            return;
1326        }
1327
1328        let ok = gen_until_pass(|(config, test), _| match gc_ops(config, test) {
1329            Ok(result) => Ok(result > 0),
1330            Err(_) => Ok(false),
1331        });
1332
1333        if !ok {
1334            panic!("gc was never found");
1335        }
1336    }
1337
1338    #[test]
1339    fn module_generation_uses_expected_proposals() {
1340        // Proposals that Wasmtime supports. Eventually a module should be
1341        // generated that needs these proposals.
1342        let mut expected = WasmFeatures::MUTABLE_GLOBAL
1343            | WasmFeatures::FLOATS
1344            | WasmFeatures::SIGN_EXTENSION
1345            | WasmFeatures::SATURATING_FLOAT_TO_INT
1346            | WasmFeatures::MULTI_VALUE
1347            | WasmFeatures::BULK_MEMORY
1348            | WasmFeatures::REFERENCE_TYPES
1349            | WasmFeatures::SIMD
1350            | WasmFeatures::MULTI_MEMORY
1351            | WasmFeatures::RELAXED_SIMD
1352            | WasmFeatures::TAIL_CALL
1353            | WasmFeatures::WIDE_ARITHMETIC
1354            | WasmFeatures::MEMORY64
1355            | WasmFeatures::FUNCTION_REFERENCES
1356            | WasmFeatures::GC
1357            | WasmFeatures::GC_TYPES
1358            | WasmFeatures::COMPACT_IMPORTS
1359            | WasmFeatures::CUSTOM_PAGE_SIZES
1360            | WasmFeatures::EXTENDED_CONST
1361            | WasmFeatures::EXCEPTIONS;
1362
1363        // All other features that wasmparser supports, which is presumably a
1364        // superset of the features that wasm-smith supports, are listed here as
1365        // unexpected. This means, for example, that if wasm-smith updates to
1366        // include a new proposal by default that wasmtime implements then it
1367        // will be required to be listed above.
1368        let unexpected = WasmFeatures::all() ^ expected;
1369
1370        let ok = gen_until_pass(|config: generators::Config, u| {
1371            let wasm = config.generate(u, None)?.to_bytes();
1372
1373            // Double-check the module is valid
1374            Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?;
1375
1376            // If any of the unexpected features are removed then this module
1377            // should always be valid, otherwise something went wrong.
1378            for feature in unexpected.iter() {
1379                let ok =
1380                    Validator::new_with_features(WasmFeatures::all() ^ feature).validate_all(&wasm);
1381                if ok.is_err() {
1382                    wasmtime::bail!("generated a module with {feature:?} but that wasn't expected");
1383                }
1384            }
1385
1386            // If any of `expected` is removed and the module fails to validate,
1387            // then that means the module requires that feature. Remove that
1388            // from the set of features we're then expecting.
1389            for feature in expected.iter() {
1390                let ok =
1391                    Validator::new_with_features(WasmFeatures::all() ^ feature).validate_all(&wasm);
1392                if ok.is_err() {
1393                    expected ^= feature;
1394                }
1395            }
1396
1397            Ok(expected.is_empty())
1398        });
1399
1400        if !ok {
1401            panic!("never generated wasm module using {expected:?}");
1402        }
1403    }
1404
1405    #[test]
1406    fn wast_smoke_test() {
1407        test_n_times(50, |(), u| super::wast_test(u));
1408    }
1409}