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 mod stacks;
20 
21 use self::diff_wasmtime::WasmtimeInstance;
22 use self::engine::{DiffEngine, DiffInstance};
23 use crate::generators::{self, DiffValue, DiffValueType};
24 use crate::single_module_fuzzer::KnownValid;
25 use arbitrary::Arbitrary;
26 pub use stacks::check_stacks;
27 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
28 use std::sync::{Arc, Condvar, Mutex};
29 use std::time::{Duration, Instant};
30 use wasmtime::*;
31 use wasmtime_wast::WastContext;
32 
33 #[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))]
34 mod diff_v8;
35 
36 static CNT: AtomicUsize = AtomicUsize::new(0);
37 
38 /// Logs a wasm file to the filesystem to make it easy to figure out what wasm
39 /// was used when debugging.
40 pub fn log_wasm(wasm: &[u8]) {
41     super::init_fuzzing();
42 
43     if !log::log_enabled!(log::Level::Debug) {
44         return;
45     }
46 
47     let i = CNT.fetch_add(1, SeqCst);
48     let name = format!("testcase{}.wasm", i);
49     std::fs::write(&name, wasm).expect("failed to write wasm file");
50     log::debug!("wrote wasm file to `{}`", name);
51     let wat = format!("testcase{}.wat", i);
52     match wasmprinter::print_bytes(wasm) {
53         Ok(s) => std::fs::write(&wat, s).expect("failed to write wat file"),
54         // If wasmprinter failed remove a `*.wat` file, if any, to avoid
55         // confusing a preexisting one with this wasm which failed to get
56         // printed.
57         Err(_) => drop(std::fs::remove_file(&wat)),
58     }
59 }
60 
61 /// The `T` in `Store<T>` for fuzzing stores, used to limit resource
62 /// consumption during fuzzing.
63 #[derive(Clone)]
64 pub struct StoreLimits(Arc<LimitsState>);
65 
66 struct LimitsState {
67     /// Remaining memory, in bytes, left to allocate
68     remaining_memory: AtomicUsize,
69     /// Whether or not an allocation request has been denied
70     oom: AtomicBool,
71 }
72 
73 impl StoreLimits {
74     /// Creates the default set of limits for all fuzzing stores.
75     pub fn new() -> StoreLimits {
76         StoreLimits(Arc::new(LimitsState {
77             // Limits tables/memories within a store to at most 1gb for now to
78             // exercise some larger address but not overflow various limits.
79             remaining_memory: AtomicUsize::new(1 << 30),
80             oom: AtomicBool::new(false),
81         }))
82     }
83 
84     fn alloc(&mut self, amt: usize) -> bool {
85         log::trace!("alloc {amt:#x} bytes");
86         match self
87             .0
88             .remaining_memory
89             .fetch_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt))
90         {
91             Ok(_) => true,
92             Err(_) => {
93                 self.0.oom.store(true, SeqCst);
94                 log::debug!("OOM hit");
95                 false
96             }
97         }
98     }
99 
100     fn is_oom(&self) -> bool {
101         self.0.oom.load(SeqCst)
102     }
103 }
104 
105 impl ResourceLimiter for StoreLimits {
106     fn memory_growing(
107         &mut self,
108         current: usize,
109         desired: usize,
110         _maximum: Option<usize>,
111     ) -> Result<bool> {
112         Ok(self.alloc(desired - current))
113     }
114 
115     fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> Result<bool> {
116         let delta = (desired - current) as usize * std::mem::size_of::<usize>();
117         Ok(self.alloc(delta))
118     }
119 }
120 
121 /// Methods of timing out execution of a WebAssembly module
122 #[derive(Clone, Debug)]
123 pub enum Timeout {
124     /// No timeout is used, it should be guaranteed via some other means that
125     /// the input does not infinite loop.
126     None,
127     /// Fuel-based timeouts are used where the specified fuel is all that the
128     /// provided wasm module is allowed to consume.
129     Fuel(u64),
130     /// An epoch-interruption-based timeout is used with a sleeping
131     /// thread bumping the epoch counter after the specified duration.
132     Epoch(Duration),
133 }
134 
135 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
136 /// panic or segfault or anything else that can be detected "passively".
137 ///
138 /// The engine will be configured using provided config.
139 pub fn instantiate(
140     wasm: &[u8],
141     known_valid: KnownValid,
142     config: &generators::Config,
143     timeout: Timeout,
144 ) {
145     let mut store = config.to_store();
146 
147     let module = match compile_module(store.engine(), wasm, known_valid, config) {
148         Some(module) => module,
149         None => return,
150     };
151 
152     let mut timeout_state = SignalOnDrop::default();
153     match timeout {
154         Timeout::Fuel(fuel) => store.set_fuel(fuel).unwrap(),
155 
156         // If a timeout is requested then we spawn a helper thread to wait for
157         // the requested time and then send us a signal to get interrupted. We
158         // also arrange for the thread's sleep to get interrupted if we return
159         // early (or the wasm returns within the time limit), which allows the
160         // thread to get torn down.
161         //
162         // This prevents us from creating a huge number of sleeping threads if
163         // this function is executed in a loop, like it does on nightly fuzzing
164         // infrastructure.
165         Timeout::Epoch(timeout) => {
166             let engine = store.engine().clone();
167             timeout_state.spawn_timeout(timeout, move || engine.increment_epoch());
168         }
169         Timeout::None => {}
170     }
171 
172     instantiate_with_dummy(&mut store, &module);
173 }
174 
175 /// Represents supported commands to the `instantiate_many` function.
176 #[derive(Arbitrary, Debug)]
177 pub enum Command {
178     /// Instantiates a module.
179     ///
180     /// The value is the index of the module to instantiate.
181     ///
182     /// The module instantiated will be this value modulo the number of modules provided to `instantiate_many`.
183     Instantiate(usize),
184     /// Terminates a "running" instance.
185     ///
186     /// The value is the index of the instance to terminate.
187     ///
188     /// The instance terminated will be this value modulo the number of currently running
189     /// instances.
190     ///
191     /// If no instances are running, the command will be ignored.
192     Terminate(usize),
193 }
194 
195 /// Instantiates many instances from the given modules.
196 ///
197 /// The engine will be configured using the provided config.
198 ///
199 /// The modules are expected to *not* have start functions as no timeouts are configured.
200 pub fn instantiate_many(
201     modules: &[Vec<u8>],
202     known_valid: KnownValid,
203     config: &generators::Config,
204     commands: &[Command],
205 ) {
206     assert!(!config.module_config.config.allow_start_export);
207 
208     let engine = Engine::new(&config.to_wasmtime()).unwrap();
209 
210     let modules = modules
211         .iter()
212         .filter_map(|bytes| compile_module(&engine, bytes, known_valid, config))
213         .collect::<Vec<_>>();
214 
215     // If no modules were valid, we're done
216     if modules.is_empty() {
217         return;
218     }
219 
220     // This stores every `Store` where a successful instantiation takes place
221     let mut stores = Vec::new();
222     let limits = StoreLimits::new();
223 
224     for command in commands {
225         match command {
226             Command::Instantiate(index) => {
227                 let index = *index % modules.len();
228                 log::info!("instantiating {}", index);
229                 let module = &modules[index];
230                 let mut store = Store::new(&engine, limits.clone());
231                 config.configure_store(&mut store);
232 
233                 if instantiate_with_dummy(&mut store, module).is_some() {
234                     stores.push(Some(store));
235                 } else {
236                     log::warn!("instantiation failed");
237                 }
238             }
239             Command::Terminate(index) => {
240                 if stores.is_empty() {
241                     continue;
242                 }
243                 let index = *index % stores.len();
244 
245                 log::info!("dropping {}", index);
246                 stores.swap_remove(index);
247             }
248         }
249     }
250 }
251 
252 fn compile_module(
253     engine: &Engine,
254     bytes: &[u8],
255     known_valid: KnownValid,
256     config: &generators::Config,
257 ) -> Option<Module> {
258     log_wasm(bytes);
259     match config.compile(engine, bytes) {
260         Ok(module) => Some(module),
261         Err(_) if known_valid == KnownValid::No => None,
262         Err(e) => {
263             if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy {
264                 // When using the pooling allocator, accept failures to compile
265                 // when arbitrary table element limits have been exceeded as
266                 // there is currently no way to constrain the generated module
267                 // table types.
268                 let string = e.to_string();
269                 if string.contains("minimum element size") {
270                     return None;
271                 }
272 
273                 // Allow modules-failing-to-compile which exceed the requested
274                 // size for each instance. This is something that is difficult
275                 // to control and ensure it always succeeds, so we simply have a
276                 // "random" instance size limit and if a module doesn't fit we
277                 // move on to the next fuzz input.
278                 if string.contains("instance allocation for this module requires") {
279                     return None;
280                 }
281 
282                 // If the pooling allocator is more restrictive on the number of
283                 // tables and memories than we allowed wasm-smith to generate
284                 // then allow compilation errors along those lines.
285                 if c.max_tables_per_module < (config.module_config.config.max_tables as u32)
286                     && string.contains("defined tables count")
287                     && string.contains("exceeds the per-instance limit")
288                 {
289                     return None;
290                 }
291 
292                 if c.max_memories_per_module < (config.module_config.config.max_memories as u32)
293                     && string.contains("defined memories count")
294                     && string.contains("exceeds the per-instance limit")
295                 {
296                     return None;
297                 }
298             }
299 
300             panic!("failed to compile module: {:?}", e);
301         }
302     }
303 }
304 
305 /// Create a Wasmtime [`Instance`] from a [`Module`] and fill in all imports
306 /// with dummy values (e.g., zeroed values, immediately-trapping functions).
307 /// Also, this function catches certain fuzz-related instantiation failures and
308 /// returns `None` instead of panicking.
309 ///
310 /// TODO: we should implement tracing versions of these dummy imports that
311 /// record a trace of the order that imported functions were called in and with
312 /// what values. Like the results of exported functions, calls to imports should
313 /// also yield the same values for each configuration, and we should assert
314 /// that.
315 pub fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> {
316     // Creation of imports can fail due to resource limit constraints, and then
317     // instantiation can naturally fail for a number of reasons as well. Bundle
318     // the two steps together to match on the error below.
319     let instance =
320         dummy::dummy_linker(store, module).and_then(|l| l.instantiate(&mut *store, module));
321 
322     let e = match instance {
323         Ok(i) => return Some(i),
324         Err(e) => e,
325     };
326 
327     // If the instantiation hit OOM for some reason then that's ok, it's
328     // expected that fuzz-generated programs try to allocate lots of
329     // stuff.
330     if store.data().is_oom() {
331         log::debug!("failed to instantiate: OOM");
332         return None;
333     }
334 
335     // Allow traps which can happen normally with `unreachable` or a
336     // timeout or such
337     if let Some(trap) = e.downcast_ref::<Trap>() {
338         log::debug!("failed to instantiate: {}", trap);
339         return None;
340     }
341 
342     let string = e.to_string();
343     // Currently we instantiate with a `Linker` which can't instantiate
344     // every single module under the sun due to using name-based resolution
345     // rather than positional-based resolution
346     if string.contains("incompatible import type") {
347         log::debug!("failed to instantiate: {}", string);
348         return None;
349     }
350 
351     // Also allow failures to instantiate as a result of hitting pooling limits.
352     if string.contains("maximum concurrent core instance limit")
353         || string.contains("maximum concurrent memory limit")
354         || string.contains("maximum concurrent table limit")
355     {
356         log::debug!("failed to instantiate: {}", string);
357         return None;
358     }
359 
360     // Everything else should be a bug in the fuzzer or a bug in wasmtime
361     panic!("failed to instantiate: {:?}", e);
362 }
363 
364 /// Evaluate the function identified by `name` in two different engine
365 /// instances--`lhs` and `rhs`.
366 ///
367 /// Returns `Ok(true)` if more evaluations can happen or `Ok(false)` if the
368 /// instances may have drifted apart and no more evaluations can happen.
369 ///
370 /// # Panics
371 ///
372 /// This will panic if the evaluation is different between engines (e.g.,
373 /// results are different, hashed instance is different, one side traps, etc.).
374 pub fn differential(
375     lhs: &mut dyn DiffInstance,
376     lhs_engine: &dyn DiffEngine,
377     rhs: &mut WasmtimeInstance,
378     name: &str,
379     args: &[DiffValue],
380     result_tys: &[DiffValueType],
381 ) -> anyhow::Result<bool> {
382     log::debug!("Evaluating: `{}` with {:?}", name, args);
383     let lhs_results = match lhs.evaluate(name, args, result_tys) {
384         Ok(Some(results)) => Ok(results),
385         Err(e) => Err(e),
386         // this engine couldn't execute this type signature, so discard this
387         // execution by returning success.
388         Ok(None) => return Ok(true),
389     };
390     log::debug!(" -> results on {}: {:?}", lhs.name(), &lhs_results);
391 
392     let rhs_results = rhs
393         .evaluate(name, args, result_tys)
394         // wasmtime should be able to invoke any signature, so unwrap this result
395         .map(|results| results.unwrap());
396     log::debug!(" -> results on {}: {:?}", rhs.name(), &rhs_results);
397 
398     // If Wasmtime hit its OOM condition, which is possible since it's set
399     // somewhat low while fuzzing, then don't return an error but return
400     // `false` indicating that differential fuzzing must stop. There's no
401     // guarantee the other engine has the same OOM limits as Wasmtime, and
402     // it's assumed that Wasmtime is configured to have a more conservative
403     // limit than the other engine.
404     if rhs.is_oom() {
405         return Ok(false);
406     }
407 
408     match DiffEqResult::new(lhs_engine, lhs_results, rhs_results) {
409         DiffEqResult::Success(lhs, rhs) => assert_eq!(lhs, rhs),
410         DiffEqResult::Poisoned => return Ok(false),
411         DiffEqResult::Failed => {}
412     }
413 
414     for (global, ty) in rhs.exported_globals() {
415         log::debug!("Comparing global `{global}`");
416         let lhs = match lhs.get_global(&global, ty) {
417             Some(val) => val,
418             None => continue,
419         };
420         let rhs = rhs.get_global(&global, ty).unwrap();
421         assert_eq!(lhs, rhs);
422     }
423     for (memory, shared) in rhs.exported_memories() {
424         log::debug!("Comparing memory `{memory}`");
425         let lhs = match lhs.get_memory(&memory, shared) {
426             Some(val) => val,
427             None => continue,
428         };
429         let rhs = rhs.get_memory(&memory, shared).unwrap();
430         if lhs == rhs {
431             continue;
432         }
433         eprintln!("differential memory is {} bytes long", lhs.len());
434         eprintln!("wasmtime memory is     {} bytes long", rhs.len());
435         panic!("memories have differing values");
436     }
437 
438     Ok(true)
439 }
440 
441 /// Result of comparing the result of two operations during differential
442 /// execution.
443 pub enum DiffEqResult<T, U> {
444     /// Both engines succeeded.
445     Success(T, U),
446     /// The result has reached the state where engines may have diverged and
447     /// results can no longer be compared.
448     Poisoned,
449     /// Both engines failed with the same error message, and internal state
450     /// should still match between the two engines.
451     Failed,
452 }
453 
454 impl<T, U> DiffEqResult<T, U> {
455     /// Computes the differential result from executing in two different
456     /// engines.
457     pub fn new(
458         lhs_engine: &dyn DiffEngine,
459         lhs_result: Result<T>,
460         rhs_result: Result<U>,
461     ) -> DiffEqResult<T, U> {
462         match (lhs_result, rhs_result) {
463             (Ok(lhs_result), Ok(rhs_result)) => DiffEqResult::Success(lhs_result, rhs_result),
464 
465             // Both sides failed. If either one hits a stack overflow then that's an
466             // engine defined limit which means we can no longer compare the state
467             // of the two instances, so `None` is returned and nothing else is
468             // compared.
469             (Err(lhs), Err(rhs)) => {
470                 let err = rhs.downcast::<Trap>().expect("not a trap");
471                 let poisoned = err == Trap::StackOverflow || lhs_engine.is_stack_overflow(&lhs);
472 
473                 if poisoned {
474                     return DiffEqResult::Poisoned;
475                 }
476                 lhs_engine.assert_error_match(&err, &lhs);
477                 DiffEqResult::Failed
478             }
479             // A real bug is found if only one side fails.
480             (Ok(_), Err(_)) => panic!("only the `rhs` failed for this input"),
481             (Err(_), Ok(_)) => panic!("only the `lhs` failed for this input"),
482         }
483     }
484 }
485 
486 /// Invoke the given API calls.
487 pub fn make_api_calls(api: generators::api::ApiCalls) {
488     use crate::generators::api::ApiCall;
489     use std::collections::HashMap;
490 
491     let mut store: Option<Store<StoreLimits>> = None;
492     let mut modules: HashMap<usize, Module> = Default::default();
493     let mut instances: HashMap<usize, Instance> = Default::default();
494 
495     for call in api.calls {
496         match call {
497             ApiCall::StoreNew(config) => {
498                 log::trace!("creating store");
499                 assert!(store.is_none());
500                 store = Some(config.to_store());
501             }
502 
503             ApiCall::ModuleNew { id, wasm } => {
504                 log::debug!("creating module: {}", id);
505                 log_wasm(&wasm);
506                 let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) {
507                     Ok(m) => m,
508                     Err(_) => continue,
509                 };
510                 let old = modules.insert(id, module);
511                 assert!(old.is_none());
512             }
513 
514             ApiCall::ModuleDrop { id } => {
515                 log::trace!("dropping module: {}", id);
516                 drop(modules.remove(&id));
517             }
518 
519             ApiCall::InstanceNew { id, module } => {
520                 log::trace!("instantiating module {} as {}", module, id);
521                 let module = match modules.get(&module) {
522                     Some(m) => m,
523                     None => continue,
524                 };
525 
526                 let store = store.as_mut().unwrap();
527                 if let Some(instance) = instantiate_with_dummy(store, module) {
528                     instances.insert(id, instance);
529                 }
530             }
531 
532             ApiCall::InstanceDrop { id } => {
533                 log::trace!("dropping instance {}", id);
534                 instances.remove(&id);
535             }
536 
537             ApiCall::CallExportedFunc { instance, nth } => {
538                 log::trace!("calling instance export {} / {}", instance, nth);
539                 let instance = match instances.get(&instance) {
540                     Some(i) => i,
541                     None => {
542                         // Note that we aren't guaranteed to instantiate valid
543                         // modules, see comments in `InstanceNew` for details on
544                         // that. But the API call generator can't know if
545                         // instantiation failed, so we might not actually have
546                         // this instance. When that's the case, just skip the
547                         // API call and keep going.
548                         continue;
549                     }
550                 };
551                 let store = store.as_mut().unwrap();
552 
553                 let funcs = instance
554                     .exports(&mut *store)
555                     .filter_map(|e| match e.into_extern() {
556                         Extern::Func(f) => Some(f.clone()),
557                         _ => None,
558                     })
559                     .collect::<Vec<_>>();
560 
561                 if funcs.is_empty() {
562                     continue;
563                 }
564 
565                 let nth = nth % funcs.len();
566                 let f = &funcs[nth];
567                 let ty = f.ty(&store);
568                 if let Ok(params) = dummy::dummy_values(ty.params()) {
569                     let mut results = vec![Val::I32(0); ty.results().len()];
570                     let _ = f.call(store, &params, &mut results);
571                 }
572             }
573         }
574     }
575 }
576 
577 /// Executes the wast `test` spectest with the `config` specified.
578 ///
579 /// Ensures that spec tests pass regardless of the `Config`.
580 pub fn spectest(fuzz_config: generators::Config, test: generators::SpecTest) {
581     crate::init_fuzzing();
582     if !fuzz_config.is_spectest_compliant() {
583         return;
584     }
585     log::debug!("running {:?}", test.file);
586     let mut wast_context = WastContext::new(fuzz_config.to_store());
587     wast_context.register_spectest(false).unwrap();
588     wast_context
589         .run_buffer(test.file, test.contents.as_bytes())
590         .unwrap();
591 }
592 
593 /// Execute a series of `table.get` and `table.set` operations.
594 ///
595 /// Returns the number of `gc` operations which occurred throughout the test
596 /// case -- used to test below that gc happens reasonably soon and eventually.
597 pub fn table_ops(
598     mut fuzz_config: generators::Config,
599     ops: generators::table_ops::TableOps,
600 ) -> usize {
601     let expected_drops = Arc::new(AtomicUsize::new(ops.num_params as usize));
602     let num_dropped = Arc::new(AtomicUsize::new(0));
603 
604     let num_gcs = Arc::new(AtomicUsize::new(0));
605     {
606         fuzz_config.wasmtime.consume_fuel = true;
607         let mut store = fuzz_config.to_store();
608         store.set_fuel(1_000).unwrap();
609 
610         let wasm = ops.to_wasm_binary();
611         log_wasm(&wasm);
612         let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) {
613             Some(m) => m,
614             None => return 0,
615         };
616 
617         let mut linker = Linker::new(store.engine());
618 
619         // To avoid timeouts, limit the number of explicit GCs we perform per
620         // test case.
621         const MAX_GCS: usize = 5;
622 
623         // NB: use `Func::new` so that this can still compile on the old x86
624         // backend, where `IntoFunc` isn't implemented for multi-value
625         // returns.
626         let func_ty = FuncType::new(
627             store.engine(),
628             vec![],
629             vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
630         );
631         let func = Func::new(&mut store, func_ty, {
632             let num_dropped = num_dropped.clone();
633             let expected_drops = expected_drops.clone();
634             let num_gcs = num_gcs.clone();
635             move |mut caller: Caller<'_, StoreLimits>, _params, results| {
636                 log::info!("table_ops: GC");
637                 if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
638                     caller.gc();
639                 }
640 
641                 let a = ExternRef::new(CountDrops(num_dropped.clone()));
642                 let b = ExternRef::new(CountDrops(num_dropped.clone()));
643                 let c = ExternRef::new(CountDrops(num_dropped.clone()));
644 
645                 log::info!("table_ops: make_refs() -> ({:p}, {:p}, {:p})", a, b, c);
646 
647                 expected_drops.fetch_add(3, SeqCst);
648                 results[0] = Some(a).into();
649                 results[1] = Some(b).into();
650                 results[2] = Some(c).into();
651                 Ok(())
652             }
653         });
654         linker.define(&store, "", "gc", func).unwrap();
655 
656         linker
657             .func_wrap("", "take_refs", {
658                 let expected_drops = expected_drops.clone();
659                 move |a: Option<ExternRef>, b: Option<ExternRef>, c: Option<ExternRef>| {
660                     log::info!(
661                         "table_ops: take_refs({}, {}, {})",
662                         a.as_ref().map_or_else(
663                             || format!("{:p}", std::ptr::null::<()>()),
664                             |r| format!("{:p}", *r)
665                         ),
666                         b.as_ref().map_or_else(
667                             || format!("{:p}", std::ptr::null::<()>()),
668                             |r| format!("{:p}", *r)
669                         ),
670                         c.as_ref().map_or_else(
671                             || format!("{:p}", std::ptr::null::<()>()),
672                             |r| format!("{:p}", *r)
673                         ),
674                     );
675 
676                     // Do the assertion on each ref's inner data, even though it
677                     // all points to the same atomic, so that if we happen to
678                     // run into a use-after-free bug with one of these refs we
679                     // are more likely to trigger a segfault.
680                     if let Some(a) = a {
681                         let a = a.data().downcast_ref::<CountDrops>().unwrap();
682                         assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst));
683                     }
684                     if let Some(b) = b {
685                         let b = b.data().downcast_ref::<CountDrops>().unwrap();
686                         assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst));
687                     }
688                     if let Some(c) = c {
689                         let c = c.data().downcast_ref::<CountDrops>().unwrap();
690                         assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst));
691                     }
692                 }
693             })
694             .unwrap();
695 
696         // NB: use `Func::new` so that this can still compile on the old
697         // x86 backend, where `IntoFunc` isn't implemented for
698         // multi-value returns.
699         let func_ty = FuncType::new(
700             store.engine(),
701             vec![],
702             vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
703         );
704         let func = Func::new(&mut store, func_ty, {
705             let num_dropped = num_dropped.clone();
706             let expected_drops = expected_drops.clone();
707             move |_caller, _params, results| {
708                 log::info!("table_ops: make_refs");
709                 expected_drops.fetch_add(3, SeqCst);
710                 results[0] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
711                 results[1] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
712                 results[2] = Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
713                 Ok(())
714             }
715         });
716         linker.define(&store, "", "make_refs", func).unwrap();
717 
718         let instance = linker.instantiate(&mut store, &module).unwrap();
719         let run = instance.get_func(&mut store, "run").unwrap();
720 
721         let args: Vec<_> = (0..ops.num_params)
722             .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
723             .collect();
724 
725         // The generated function should always return a trap. The only two
726         // valid traps are table-out-of-bounds which happens through `table.get`
727         // and `table.set` generated or an out-of-fuel trap. Otherwise any other
728         // error is unexpected and should fail fuzzing.
729         let trap = run
730             .call(&mut store, &args, &mut [])
731             .unwrap_err()
732             .downcast::<Trap>()
733             .unwrap();
734 
735         match trap {
736             Trap::TableOutOfBounds | Trap::OutOfFuel => {}
737             _ => panic!("unexpected trap: {trap}"),
738         }
739 
740         // Do a final GC after running the Wasm.
741         store.gc();
742     }
743 
744     assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst));
745     return num_gcs.load(SeqCst);
746 
747     struct CountDrops(Arc<AtomicUsize>);
748 
749     impl Drop for CountDrops {
750         fn drop(&mut self) {
751             self.0.fetch_add(1, SeqCst);
752         }
753     }
754 }
755 
756 // Test that the `table_ops` fuzzer eventually runs the gc function in the host.
757 // We've historically had issues where this fuzzer accidentally wasn't fuzzing
758 // anything for a long time so this is an attempt to prevent that from happening
759 // again.
760 #[test]
761 fn table_ops_eventually_gcs() {
762     use arbitrary::Unstructured;
763     use rand::prelude::*;
764 
765     // Skip if we're under emulation because some fuzz configurations will do
766     // large address space reservations that QEMU doesn't handle well.
767     if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
768         return;
769     }
770 
771     let mut rng = SmallRng::seed_from_u64(0);
772     let mut buf = vec![0; 2048];
773     let n = 100;
774     for _ in 0..n {
775         rng.fill_bytes(&mut buf);
776         let u = Unstructured::new(&buf);
777 
778         if let Ok((config, test)) = Arbitrary::arbitrary_take_rest(u) {
779             if table_ops(config, test) > 0 {
780                 return;
781             }
782         }
783     }
784 
785     panic!("after {n} runs nothing ever gc'd, something is probably wrong");
786 }
787 
788 #[derive(Default)]
789 struct SignalOnDrop {
790     state: Arc<(Mutex<bool>, Condvar)>,
791     thread: Option<std::thread::JoinHandle<()>>,
792 }
793 
794 impl SignalOnDrop {
795     fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) {
796         let state = self.state.clone();
797         let start = Instant::now();
798         self.thread = Some(std::thread::spawn(move || {
799             // Using our mutex/condvar we wait here for the first of `dur` to
800             // pass or the `SignalOnDrop` instance to get dropped.
801             let (lock, cvar) = &*state;
802             let mut signaled = lock.lock().unwrap();
803             while !*signaled {
804                 // Adjust our requested `dur` based on how much time has passed.
805                 let dur = match dur.checked_sub(start.elapsed()) {
806                     Some(dur) => dur,
807                     None => break,
808                 };
809                 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap();
810                 signaled = lock;
811                 // If we timed out for sure then there's no need to continue
812                 // since we'll just abort on the next `checked_sub` anyway.
813                 if result.timed_out() {
814                     break;
815                 }
816             }
817             drop(signaled);
818 
819             closure();
820         }));
821     }
822 }
823 
824 impl Drop for SignalOnDrop {
825     fn drop(&mut self) {
826         if let Some(thread) = self.thread.take() {
827             let (lock, cvar) = &*self.state;
828             // Signal our thread that we've been dropped and wake it up if it's
829             // blocked.
830             let mut g = lock.lock().unwrap();
831             *g = true;
832             cvar.notify_one();
833             drop(g);
834 
835             // ... and then wait for the thread to exit to ensure we clean up
836             // after ourselves.
837             thread.join().unwrap();
838         }
839     }
840 }
841 
842 /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create
843 /// arbitrary types and values.
844 pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> {
845     use crate::generators::component_types;
846     use component_fuzz_util::{TestCase, Type, EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH};
847     use component_test_util::FuncExt;
848     use wasmtime::component::{Component, Linker, Val};
849 
850     crate::init_fuzzing();
851 
852     let mut types = Vec::new();
853     let mut type_fuel = 500;
854 
855     for _ in 0..5 {
856         types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?);
857     }
858     let params = (0..input.int_in_range(0..=5)?)
859         .map(|_| input.choose(&types))
860         .collect::<arbitrary::Result<Vec<_>>>()?;
861     let results = (0..input.int_in_range(0..=5)?)
862         .map(|_| input.choose(&types))
863         .collect::<arbitrary::Result<Vec<_>>>()?;
864 
865     let case = TestCase {
866         params,
867         results,
868         encoding1: input.arbitrary()?,
869         encoding2: input.arbitrary()?,
870     };
871 
872     let mut config = component_test_util::config();
873     config.debug_adapter_modules(input.arbitrary()?);
874     let engine = Engine::new(&config).unwrap();
875     let mut store = Store::new(&engine, (Vec::new(), None));
876     let wat = case.declarations().make_component();
877     let wat = wat.as_bytes();
878     log_wasm(wat);
879     let component = Component::new(&engine, wat).unwrap();
880     let mut linker = Linker::new(&engine);
881 
882     linker
883         .root()
884         .func_new(&component, IMPORT_FUNCTION, {
885             move |mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>,
886                   params: &[Val],
887                   results: &mut [Val]|
888                   -> Result<()> {
889                 log::trace!("received params {params:?}");
890                 let (expected_args, expected_results) = cx.data_mut();
891                 assert_eq!(params.len(), expected_args.len());
892                 for (expected, actual) in expected_args.iter().zip(params) {
893                     assert_eq!(expected, actual);
894                 }
895                 results.clone_from_slice(&expected_results.take().unwrap());
896                 log::trace!("returning results {results:?}");
897                 Ok(())
898             }
899         })
900         .unwrap();
901 
902     let instance = linker.instantiate(&mut store, &component).unwrap();
903     let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap();
904     let param_tys = func.params(&store);
905     let result_tys = func.results(&store);
906 
907     while input.arbitrary()? {
908         let params = param_tys
909             .iter()
910             .map(|ty| component_types::arbitrary_val(ty, input))
911             .collect::<arbitrary::Result<Vec<_>>>()?;
912         let results = result_tys
913             .iter()
914             .map(|ty| component_types::arbitrary_val(ty, input))
915             .collect::<arbitrary::Result<Vec<_>>>()?;
916 
917         *store.data_mut() = (params.clone(), Some(results.clone()));
918 
919         log::trace!("passing params {params:?}");
920         let mut actual = vec![Val::Bool(false); results.len()];
921         func.call_and_post_return(&mut store, &params, &mut actual)
922             .unwrap();
923         log::trace!("received results {actual:?}");
924         assert_eq!(actual, results);
925     }
926 
927     Ok(())
928 }
929