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