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