1pub mod component_api;
14pub mod diff_wasmi;
15pub mod diff_wasmtime;
16pub mod dummy;
17pub mod engine;
18mod gc_access;
19pub mod memory;
20mod stacks;
21
22pub mod api;
24pub use api::make_api_calls;
25
26use self::diff_wasmtime::WasmtimeInstance;
27use self::engine::{DiffEngine, DiffInstance};
28use crate::generators::ExceptionOps;
29use crate::generators::GcOps;
30use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType};
31use crate::single_module_fuzzer::KnownValid;
32use crate::{YieldN, block_on};
33use arbitrary::Arbitrary;
34pub use gc_access::gc_access;
35pub use stacks::check_stacks;
36use std::future::Future;
37use std::pin::Pin;
38use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
39use std::sync::{Arc, Condvar, Mutex};
40use std::task::{Context, Poll};
41use std::time::{Duration, Instant};
42use wasmtime::*;
43use wasmtime_wast::WastContext;
44
45#[cfg(v8)]
46mod diff_v8;
47
48static CNT: AtomicUsize = AtomicUsize::new(0);
49
50pub fn log_wasm(wasm: &[u8]) {
53 super::init_fuzzing();
54
55 if !log::log_enabled!(log::Level::Debug) {
56 return;
57 }
58
59 let i = CNT.fetch_add(1, SeqCst);
60 let name = format!("testcase{i}.wasm");
61 std::fs::write(&name, wasm).expect("failed to write wasm file");
62 log::debug!("wrote wasm file to `{name}`");
63 let wat = format!("testcase{i}.wat");
64 match wasmprinter::print_bytes(wasm) {
65 Ok(s) => {
66 std::fs::write(&wat, s).expect("failed to write wat file");
67 log::debug!("wrote wat file to `{wat}`");
68 }
69 Err(e) => {
73 log::debug!("failed to print to wat: {e}");
74 drop(std::fs::remove_file(&wat));
75 }
76 }
77}
78
79#[derive(Clone)]
82pub struct StoreLimits(Arc<LimitsState>);
83
84struct LimitsState {
85 remaining_memory: AtomicUsize,
87 remaining_copy_allowance: AtomicUsize,
89 oom: AtomicBool,
91}
92
93const MAX_MEMORY: usize = 1 << 30;
96
97const MAX_MEMORY_MOVED: usize = if cfg!(target_pointer_width = "32") {
102 1 << 30
103} else {
104 4 << 30
105};
106
107impl StoreLimits {
108 pub fn new() -> StoreLimits {
110 StoreLimits(Arc::new(LimitsState {
111 remaining_memory: AtomicUsize::new(MAX_MEMORY),
112 remaining_copy_allowance: AtomicUsize::new(MAX_MEMORY_MOVED),
113 oom: AtomicBool::new(false),
114 }))
115 }
116
117 fn alloc(&mut self, amt: usize) -> bool {
118 log::trace!("alloc {amt:#x} bytes");
119
120 let prev_size = MAX_MEMORY - self.0.remaining_memory.load(SeqCst);
131 if self
132 .0
133 .remaining_copy_allowance
134 .try_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(prev_size))
135 .is_err()
136 {
137 self.0.oom.store(true, SeqCst);
138 log::debug!("-> too many bytes moved, rejecting allocation");
139 return false;
140 }
141
142 match self
145 .0
146 .remaining_memory
147 .try_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt))
148 {
149 Ok(_) => true,
150 Err(_) => {
151 self.0.oom.store(true, SeqCst);
152 log::debug!("-> OOM hit");
153 false
154 }
155 }
156 }
157
158 fn is_oom(&self) -> bool {
159 self.0.oom.load(SeqCst)
160 }
161}
162
163impl ResourceLimiter for StoreLimits {
164 fn memory_growing(
165 &mut self,
166 current: usize,
167 desired: usize,
168 _maximum: Option<usize>,
169 ) -> Result<bool> {
170 Ok(self.alloc(desired - current))
171 }
172
173 fn table_growing(
174 &mut self,
175 current: usize,
176 desired: usize,
177 _maximum: Option<usize>,
178 ) -> Result<bool> {
179 let delta = (desired - current).saturating_mul(std::mem::size_of::<usize>());
180 Ok(self.alloc(delta))
181 }
182}
183
184#[derive(Clone, Debug)]
186pub enum Timeout {
187 None,
190 Fuel(u64),
193 Epoch(Duration),
196}
197
198pub fn instantiate(
203 wasm: &[u8],
204 known_valid: KnownValid,
205 config: &generators::Config,
206 timeout: Timeout,
207) {
208 let mut store = config.to_store();
209
210 let module = match compile_module(store.engine(), wasm, known_valid, config) {
211 Some(module) => module,
212 None => return,
213 };
214
215 let mut timeout_state = HelperThread::default();
216 match timeout {
217 Timeout::Fuel(fuel) => store.set_fuel(fuel).unwrap(),
218
219 Timeout::Epoch(timeout) => {
229 let engine = store.engine().clone();
230 timeout_state.run_periodically(timeout, move || engine.increment_epoch());
231 }
232 Timeout::None => {}
233 }
234
235 instantiate_with_dummy(&mut store, &module);
236}
237
238#[derive(Arbitrary, Debug)]
240pub enum Command {
241 Instantiate(usize),
247 Terminate(usize),
256}
257
258pub fn instantiate_many(
264 modules: &[Vec<u8>],
265 known_valid: KnownValid,
266 config: &generators::Config,
267 commands: &[Command],
268) {
269 log::debug!("instantiate_many: {commands:#?}");
270
271 assert!(!config.module_config.config.allow_start_export);
272
273 let engine = Engine::new(&config.to_wasmtime()).unwrap();
274
275 let modules = modules
276 .iter()
277 .enumerate()
278 .filter_map(
279 |(i, bytes)| match compile_module(&engine, bytes, known_valid, config) {
280 Some(m) => {
281 log::debug!("successfully compiled module {i}");
282 Some(m)
283 }
284 None => {
285 log::debug!("failed to compile module {i}");
286 None
287 }
288 },
289 )
290 .collect::<Vec<_>>();
291
292 if modules.is_empty() {
294 return;
295 }
296
297 let mut stores = Vec::new();
299 let limits = StoreLimits::new();
300
301 for command in commands {
302 match command {
303 Command::Instantiate(index) => {
304 let index = *index % modules.len();
305 log::info!("instantiating {index}");
306 let module = &modules[index];
307 let mut store = Store::new(&engine, limits.clone());
308 config.configure_store(&mut store);
309
310 if instantiate_with_dummy(&mut store, module).is_some() {
311 stores.push(Some(store));
312 } else {
313 log::warn!("instantiation failed");
314 }
315 }
316 Command::Terminate(index) => {
317 if stores.is_empty() {
318 continue;
319 }
320 let index = *index % stores.len();
321
322 log::info!("dropping {index}");
323 stores.swap_remove(index);
324 }
325 }
326 }
327}
328
329fn compile_module(
330 engine: &Engine,
331 bytes: &[u8],
332 known_valid: KnownValid,
333 config: &generators::Config,
334) -> Option<Module> {
335 log_wasm(bytes);
336
337 match config.compile(engine, bytes) {
338 Ok(module) => Some(module),
339 Err(_) if known_valid == KnownValid::No => None,
340 Err(e) => {
341 if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy {
342 let string = format!("{e:?}");
347 if string.contains("minimum element size") {
348 return None;
349 }
350
351 if string.contains("instance allocation for this module requires") {
357 return None;
358 }
359
360 if c.max_tables_per_module < (config.module_config.config.max_tables as u32)
364 && string.contains("defined tables count")
365 && string.contains("exceeds the per-instance limit")
366 {
367 return None;
368 }
369
370 if c.max_memories_per_module < (config.module_config.config.max_memories as u32)
371 && string.contains("defined memories count")
372 && string.contains("exceeds the per-instance limit")
373 {
374 return None;
375 }
376 }
377
378 panic!("failed to compile module: {e:?}");
379 }
380 }
381}
382
383pub fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> {
394 let linker = dummy::dummy_linker(store, module);
398 if let Err(e) = &linker {
399 log::warn!("failed to create dummy linker: {e:?}");
400 }
401 let instance = linker.and_then(|l| l.instantiate(&mut *store, module));
402 unwrap_instance(store, instance)
403}
404
405fn unwrap_instance(
406 store: &Store<StoreLimits>,
407 instance: wasmtime::Result<Instance>,
408) -> Option<Instance> {
409 let e = match instance {
410 Ok(i) => return Some(i),
411 Err(e) => e,
412 };
413
414 log::debug!("failed to instantiate: {e:?}");
415
416 if store.data().is_oom() {
420 return None;
421 }
422
423 if e.is::<Trap>()
426 || e.is::<wasmtime::PoolConcurrencyLimitError>()
429 || e.is::<wasmtime::GcHeapOutOfMemory<()>>()
431 || e.is::<wasmtime::ThrownException>()
433 {
434 return None;
435 }
436
437 let string = e.to_string();
438
439 if string.contains("incompatible import type") {
443 return None;
444 }
445
446 panic!("failed to instantiate: {e:?}");
448}
449
450pub fn differential(
461 lhs: &mut dyn DiffInstance,
462 lhs_engine: &dyn DiffEngine,
463 rhs: &mut WasmtimeInstance,
464 name: &str,
465 args: &[DiffValue],
466 result_tys: &[DiffValueType],
467) -> wasmtime::Result<bool> {
468 log::debug!("Evaluating: `{name}` with {args:?}");
469 let lhs_results = match lhs.evaluate(name, args, result_tys) {
470 Ok(Some(results)) => Ok(results),
471 Err(e) => Err(e),
472 Ok(None) => return Ok(true),
475 };
476 log::debug!(" -> lhs results on {}: {:?}", lhs.name(), &lhs_results);
477
478 let rhs_results = rhs
479 .evaluate(name, args, result_tys)
480 .map(|results| results.unwrap());
482 log::debug!(" -> rhs results on {}: {:?}", rhs.name(), &rhs_results);
483
484 if rhs.is_oom() {
491 return Ok(false);
492 }
493
494 match DiffEqResult::new(lhs_engine, lhs_results, rhs_results) {
495 DiffEqResult::Success(lhs, rhs) => assert_eq!(lhs, rhs),
496 DiffEqResult::Poisoned => return Ok(false),
497 DiffEqResult::Failed => {}
498 }
499
500 for (global, ty) in rhs.exported_globals() {
501 log::debug!("Comparing global `{global}`");
502 let lhs = match lhs.get_global(&global, ty) {
503 Some(val) => val,
504 None => continue,
505 };
506 let rhs = rhs.get_global(&global, ty).unwrap();
507 assert_eq!(lhs, rhs);
508 }
509 for (memory, shared) in rhs.exported_memories() {
510 log::debug!("Comparing memory `{memory}`");
511 let lhs = match lhs.get_memory(&memory, shared) {
512 Some(val) => val,
513 None => continue,
514 };
515 let rhs = rhs.get_memory(&memory, shared).unwrap();
516 if lhs == rhs {
517 continue;
518 }
519 eprintln!("differential memory is {} bytes long", lhs.len());
520 eprintln!("wasmtime memory is {} bytes long", rhs.len());
521 panic!("memories have differing values");
522 }
523
524 Ok(true)
525}
526
527pub enum DiffEqResult<T, U> {
530 Success(T, U),
532 Poisoned,
535 Failed,
538}
539
540fn wasmtime_trap_is_non_deterministic(trap: &Trap) -> bool {
541 match trap {
542 Trap::AllocationTooLarge |
545 Trap::StackOverflow => true,
548 _ => false,
549 }
550}
551
552fn wasmtime_error_is_non_deterministic(error: &wasmtime::Error) -> bool {
553 match error.downcast_ref::<Trap>() {
554 Some(trap) => wasmtime_trap_is_non_deterministic(trap),
555
556 None => true,
563 }
564}
565
566impl<T, U> DiffEqResult<T, U> {
567 pub fn new(
570 lhs_engine: &dyn DiffEngine,
571 lhs_result: Result<T>,
572 rhs_result: Result<U>,
573 ) -> DiffEqResult<T, U> {
574 match (lhs_result, rhs_result) {
575 (Ok(lhs_result), Ok(rhs_result)) => DiffEqResult::Success(lhs_result, rhs_result),
576
577 (Err(lhs), _) if lhs_engine.is_non_deterministic_error(&lhs) => {
580 log::debug!("lhs failed non-deterministically: {lhs:?}");
581 DiffEqResult::Poisoned
582 }
583 (_, Err(rhs)) if wasmtime_error_is_non_deterministic(&rhs) => {
584 log::debug!("rhs failed non-deterministically: {rhs:?}");
585 DiffEqResult::Poisoned
586 }
587
588 (Err(lhs), Err(rhs)) => {
591 let rhs = rhs
592 .downcast::<Trap>()
593 .expect("non-traps handled in earlier match arm");
594
595 debug_assert!(
596 !lhs_engine.is_non_deterministic_error(&lhs),
597 "non-deterministic traps handled in earlier match arm",
598 );
599 debug_assert!(
600 !wasmtime_trap_is_non_deterministic(&rhs),
601 "non-deterministic traps handled in earlier match arm",
602 );
603
604 lhs_engine.assert_error_match(&lhs, &rhs);
605 DiffEqResult::Failed
606 }
607
608 (Ok(_), Err(err)) => panic!("only the `rhs` failed for this input: {err:?}"),
610 (Err(err), Ok(_)) => panic!("only the `lhs` failed for this input: {err:?}"),
611 }
612 }
613}
614
615pub fn wast_test(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<()> {
619 crate::init_fuzzing();
620
621 let mut fuzz_config: generators::Config = u.arbitrary()?;
622 fuzz_config.module_config.shared_memory = true;
623 let test: generators::WastTest = u.arbitrary()?;
624
625 let test = &test.test;
626
627 if cfg!(asan) && test.config.stack_switching == Some(true) {
630 return Err(arbitrary::Error::IncorrectFormat);
631 }
632
633 if test.config.component_model_async() || u.arbitrary()? {
634 fuzz_config.enable_async(u)?;
635 }
636
637 if test.config.hogs_memory.unwrap_or(false) {
641 return Err(arbitrary::Error::IncorrectFormat);
642 }
643
644 if cfg!(target_pointer_width = "32")
647 && matches!(
648 fuzz_config.wasmtime.strategy,
649 generators::InstanceAllocationStrategy::Pooling(_)
650 )
651 {
652 return Err(arbitrary::Error::IncorrectFormat);
653 }
654
655 let wast_config = fuzz_config.make_wast_test_compliant(test);
658 if test.should_fail(&wast_config) {
659 return Err(arbitrary::Error::IncorrectFormat);
660 }
661
662 if fuzz_config.wasmtime.compiler_strategy == CompilerStrategy::Winch
665 && test.config.simd()
666 && (fuzz_config
667 .wasmtime
668 .codegen_flag("has_avx")
669 .is_some_and(|value| value == "false")
670 || fuzz_config
671 .wasmtime
672 .codegen_flag("has_avx2")
673 .is_some_and(|value| value == "false"))
674 {
675 log::warn!(
676 "Skipping Wast test because Winch doesn't support SIMD tests with AVX or AVX2 disabled"
677 );
678 return Err(arbitrary::Error::IncorrectFormat);
679 }
680
681 if fuzz_config.wasmtime.consume_fuel || fuzz_config.wasmtime.epoch_interruption {
684 if test.contents.contains("(thread") {
685 return Err(arbitrary::Error::IncorrectFormat);
686 }
687 }
688
689 log::debug!("running {:?}", test.path);
690 let async_ = if fuzz_config.wasmtime.async_config == generators::AsyncConfig::Disabled {
691 wasmtime_wast::Async::No
692 } else {
693 wasmtime_wast::Async::Yes
694 };
695 log::debug!("async: {async_:?}");
696 let engine = Engine::new(&fuzz_config.to_wasmtime()).unwrap();
697 let mut wast_context = WastContext::new(&engine, async_, move |store| {
698 fuzz_config.configure_store_epoch_and_fuel(store);
699 });
700 wast_context.ignore_error_messages(test.config.spec_test.unwrap_or(false));
701 wast_context
702 .register_spectest(&wasmtime_wast::SpectestConfig {
703 use_shared_memory: true,
704 suppress_prints: true,
705 })
706 .unwrap();
707 wast_context.register_wasmtime().unwrap();
708 wast_context
709 .run_wast(test.path.to_str().unwrap(), test.contents.as_bytes())
710 .unwrap();
711 Ok(())
712}
713
714pub fn gc_ops(mut fuzz_config: generators::Config, mut ops: GcOps) -> Result<usize> {
719 let expected_drops = Arc::new(AtomicUsize::new(0));
720 let num_dropped = Arc::new(AtomicUsize::new(0));
721
722 let num_gcs = Arc::new(AtomicUsize::new(0));
723 {
724 fuzz_config.wasmtime.consume_fuel = true;
725 let mut store = fuzz_config.to_store();
726 store.set_fuel(1_000).unwrap();
727
728 let wasm = ops.to_wasm_binary();
729 log_wasm(&wasm);
730 let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) {
731 Some(m) => m,
732 None => return Ok(0),
733 };
734
735 let mut linker = Linker::new(store.engine());
736
737 const MAX_GCS: usize = 5;
740
741 let func_ty = FuncType::new(
742 store.engine(),
743 vec![],
744 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
745 );
746 let func = Func::new(&mut store, func_ty, {
747 let num_dropped = num_dropped.clone();
748 let expected_drops = expected_drops.clone();
749 let num_gcs = num_gcs.clone();
750 move |mut caller: Caller<'_, StoreLimits>, _params, results| {
751 log::info!("gc_ops: GC");
752 if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
753 caller.gc(None)?;
754 }
755
756 let a = ExternRef::new(
757 &mut caller,
758 CountDrops::new(&expected_drops, num_dropped.clone()),
759 )?;
760 let b = ExternRef::new(
761 &mut caller,
762 CountDrops::new(&expected_drops, num_dropped.clone()),
763 )?;
764 let c = ExternRef::new(
765 &mut caller,
766 CountDrops::new(&expected_drops, num_dropped.clone()),
767 )?;
768
769 log::info!("gc_ops: gc() -> ({a:?}, {b:?}, {c:?})");
770 results[0] = Some(a).into();
771 results[1] = Some(b).into();
772 results[2] = Some(c).into();
773 Ok(())
774 }
775 });
776 linker.define(&store, "", "gc", func).unwrap();
777
778 linker
779 .func_wrap("", "take_refs", {
780 let expected_drops = expected_drops.clone();
781 move |caller: Caller<'_, StoreLimits>,
782 a: Option<Rooted<ExternRef>>,
783 b: Option<Rooted<ExternRef>>,
784 c: Option<Rooted<ExternRef>>|
785 -> Result<()> {
786 log::info!("gc_ops: take_refs({a:?}, {b:?}, {c:?})",);
787
788 if let Some(a) = a {
793 let a = a
794 .data(&caller)?
795 .unwrap()
796 .downcast_ref::<CountDrops>()
797 .unwrap();
798 assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst));
799 }
800 if let Some(b) = b {
801 let b = b
802 .data(&caller)?
803 .unwrap()
804 .downcast_ref::<CountDrops>()
805 .unwrap();
806 assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst));
807 }
808 if let Some(c) = c {
809 let c = c
810 .data(&caller)?
811 .unwrap()
812 .downcast_ref::<CountDrops>()
813 .unwrap();
814 assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst));
815 }
816 Ok(())
817 }
818 })
819 .unwrap();
820
821 let func_ty = FuncType::new(
822 store.engine(),
823 vec![],
824 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
825 );
826 let func = Func::new(&mut store, func_ty, {
827 let num_dropped = num_dropped.clone();
828 let expected_drops = expected_drops.clone();
829 move |mut caller, _params, results| {
830 log::info!("gc_ops: make_refs");
831
832 let a = ExternRef::new(
833 &mut caller,
834 CountDrops::new(&expected_drops, num_dropped.clone()),
835 )?;
836 let b = ExternRef::new(
837 &mut caller,
838 CountDrops::new(&expected_drops, num_dropped.clone()),
839 )?;
840 let c = ExternRef::new(
841 &mut caller,
842 CountDrops::new(&expected_drops, num_dropped.clone()),
843 )?;
844
845 log::info!("gc_ops: make_refs() -> ({a:?}, {b:?}, {c:?})");
846
847 results[0] = Some(a).into();
848 results[1] = Some(b).into();
849 results[2] = Some(c).into();
850
851 Ok(())
852 }
853 });
854 linker.define(&store, "", "make_refs", func).unwrap();
855
856 let func_ty = FuncType::new(
857 store.engine(),
858 vec![ValType::Ref(RefType::new(true, HeapType::Struct))],
859 vec![],
860 );
861
862 let func = Func::new(&mut store, func_ty, {
863 move |_caller: Caller<'_, StoreLimits>, _params, _results| {
864 log::info!("gc_ops: take_struct(<ref null struct>)");
865 Ok(())
866 }
867 });
868
869 linker.define(&store, "", "take_struct", func).unwrap();
870
871 let func_ty = FuncType::new(
872 store.engine(),
873 vec![ValType::Ref(RefType::new(true, HeapType::Eq))],
874 vec![],
875 );
876
877 let func = Func::new(&mut store, func_ty, {
878 move |_caller: Caller<'_, StoreLimits>, _params, _results| {
879 log::info!("gc_ops: take_eq(<ref null eq>)");
880 Ok(())
881 }
882 });
883
884 linker.define(&store, "", "take_eq", func).unwrap();
885
886 let func_ty = FuncType::new(
887 store.engine(),
888 vec![ValType::Ref(RefType::new(true, HeapType::Array))],
889 vec![],
890 );
891
892 let func = Func::new(&mut store, func_ty, {
893 move |_caller: Caller<'_, StoreLimits>, _params, _results| {
894 log::info!("gc_ops: take_array(<ref null array>)");
895 Ok(())
896 }
897 });
898
899 linker.define(&store, "", "take_array", func).unwrap();
900
901 linker
905 .func_wrap("", "take_i31", {
906 move |_caller: Caller<'_, StoreLimits>,
907 r: Option<I31>,
908 wasm_get_s: i32,
909 wasm_get_u: i32|
910 -> Result<()> {
911 log::info!("gc_ops: take_i31({r:?}, get_s={wasm_get_s}, get_u={wasm_get_u})");
912 if let Some(i31) = r {
913 assert_eq!(
914 i31.get_i32(),
915 wasm_get_s,
916 "host `i31.get_s` must match inline Wasm `i31.get_s`"
917 );
918 assert_eq!(
919 i31.get_u32() as i32,
920 wasm_get_u,
921 "host `i31.get_u` must match inline Wasm `i31.get_u`"
922 );
923 }
924 Ok(())
925 }
926 })
927 .unwrap();
928
929 for imp in module.imports() {
930 if imp.module() == "" {
931 let name = imp.name();
932 if name.starts_with("take_struct_") || name.starts_with("take_array_") {
933 if let wasmtime::ExternType::Func(ft) = imp.ty() {
934 let imp_name = name.to_string();
935 let func =
936 Func::new(&mut store, ft.clone(), move |_caller, _params, _results| {
937 log::info!("gc_ops: {imp_name}(<typed ref>)");
938 Ok(())
939 });
940 linker.define(&store, "", name, func).unwrap();
941 }
942 }
943 }
944 }
945
946 let instance = linker.instantiate(&mut store, &module).unwrap();
947 let run = instance.get_func(&mut store, "run").unwrap();
948
949 {
950 let mut scope = RootScope::new(&mut store);
951
952 log::info!(
953 "gc_ops: begin allocating {} externref arguments",
954 ops.limits.num_globals
955 );
956 let args: Vec<_> = (0..ops.limits.num_params)
957 .map(|_| {
958 Ok(Val::ExternRef(Some(ExternRef::new(
959 &mut scope,
960 CountDrops::new(&expected_drops, num_dropped.clone()),
961 )?)))
962 })
963 .collect::<Result<_>>()?;
964 log::info!(
965 "gc_ops: end allocating {} externref arguments",
966 ops.limits.num_globals
967 );
968
969 log::info!("gc_ops: calling into Wasm `run` function");
975 let err = run.call(&mut scope, &args, &mut []).unwrap_err();
976 if err.is::<GcHeapOutOfMemory<CountDrops>>() || err.is::<GcHeapOutOfMemory<()>>() {
977 } else {
979 let trap = err
980 .downcast::<Trap>()
981 .expect("if not GC oom, error should be a Wasm trap");
982 match trap {
983 Trap::TableOutOfBounds
984 | Trap::ArrayOutOfBounds
985 | Trap::OutOfFuel
986 | Trap::AllocationTooLarge => {}
987 _ => panic!("unexpected trap: {trap}"),
988 }
989 }
990 }
991
992 store.gc(None)?;
994 }
995
996 assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst));
997 return Ok(num_gcs.load(SeqCst));
998
999 struct CountDrops(Arc<AtomicUsize>);
1000
1001 impl CountDrops {
1002 fn new(expected_drops: &AtomicUsize, num_dropped: Arc<AtomicUsize>) -> Self {
1003 let expected = expected_drops.fetch_add(1, SeqCst);
1004 log::info!(
1005 "CountDrops::new: expected drops: {expected} -> {}",
1006 expected + 1
1007 );
1008 Self(num_dropped)
1009 }
1010 }
1011
1012 impl Drop for CountDrops {
1013 fn drop(&mut self) {
1014 let drops = self.0.fetch_add(1, SeqCst);
1015 log::info!("CountDrops::drop: actual drops: {drops} -> {}", drops + 1);
1016 }
1017 }
1018}
1019
1020pub fn exception_ops(mut fuzz_config: generators::Config, mut ops: ExceptionOps) -> Result<()> {
1022 let module_cfg = &mut fuzz_config.module_config.config;
1023 module_cfg.exceptions_enabled = true;
1026 module_cfg.reference_types_enabled = true;
1027
1028 let expected = ops.expected_result();
1029
1030 let wasm = ops.to_wasm_binary();
1031 log_wasm(&wasm);
1032
1033 let mut store = fuzz_config.to_store();
1034
1035 let module = compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config)
1036 .ok_or_else(|| wasmtime::format_err!("Compilation failed"))?;
1037 let mut linker = Linker::new(store.engine());
1038
1039 let check_ty = FuncType::new(store.engine(), [ValType::I32, ValType::I32], []);
1040 let check_func = Func::new(&mut store, check_ty, |_caller, params, _results| {
1041 let actual = params[0].unwrap_i32();
1042 let expected = params[1].unwrap_i32();
1043 assert_eq!(actual, expected, "check_i32 mismatch");
1044 Ok(())
1045 });
1046 linker.define(&store, "", "check_i32", check_func).unwrap();
1047
1048 let instance = linker.instantiate(&mut store, &module).unwrap();
1049 let run = instance.get_func(&mut store, "run").unwrap();
1050
1051 let mut results = [Val::I32(0)];
1052 match run.call(&mut store, &[], &mut results) {
1053 Ok(()) => {
1054 let actual = results[0].unwrap_i32();
1055 assert_eq!(
1056 actual, expected,
1057 "exception_ops: run returned {actual}, expected {expected} \
1058 (one catch per scenario)"
1059 );
1060 }
1061 Err(e) => {
1062 if let Some(trap) = e.downcast_ref::<Trap>() {
1064 match trap {
1065 Trap::AllocationTooLarge => return Ok(()),
1066 _ => {}
1067 }
1068 }
1069 if e.is::<GcHeapOutOfMemory<()>>() {
1070 return Ok(());
1071 }
1072 panic!("exception_ops: unexpected error during execution: {e:?}");
1074 }
1075 }
1076
1077 Ok(())
1078}
1079
1080#[derive(Default)]
1081struct HelperThread {
1082 state: Arc<HelperThreadState>,
1083 thread: Option<std::thread::JoinHandle<()>>,
1084}
1085
1086#[derive(Default)]
1087struct HelperThreadState {
1088 should_exit: Mutex<bool>,
1089 should_exit_cvar: Condvar,
1090}
1091
1092impl HelperThread {
1093 fn run_periodically(&mut self, dur: Duration, mut closure: impl FnMut() + Send + 'static) {
1094 let state = self.state.clone();
1095 self.thread = Some(std::thread::spawn(move || {
1096 let mut should_exit = state.should_exit.lock().unwrap();
1099 while !*should_exit {
1100 let (lock, result) = state
1101 .should_exit_cvar
1102 .wait_timeout(should_exit, dur)
1103 .unwrap();
1104 should_exit = lock;
1105 if result.timed_out() {
1108 closure();
1109 }
1110 }
1111 }));
1112 }
1113}
1114
1115impl Drop for HelperThread {
1116 fn drop(&mut self) {
1117 let thread = match self.thread.take() {
1118 Some(thread) => thread,
1119 None => return,
1120 };
1121 *self.state.should_exit.lock().unwrap() = true;
1124 self.state.should_exit_cvar.notify_one();
1125
1126 thread.join().unwrap();
1129 }
1130}
1131
1132pub fn call_async(wasm: &[u8], config: &generators::Config, mut poll_amts: &[u32]) {
1138 let mut store = config.to_store();
1139 let module = match compile_module(store.engine(), wasm, KnownValid::Yes, config) {
1140 Some(module) => module,
1141 None => return,
1142 };
1143
1144 let mut helper_thread = HelperThread::default();
1149 if let generators::AsyncConfig::YieldWithEpochs { dur, .. } = &config.wasmtime.async_config {
1150 let engine = store.engine().clone();
1151 helper_thread.run_periodically(*dur, move || engine.increment_epoch());
1152 }
1153
1154 let mut imports = Vec::new();
1157 for import in module.imports() {
1158 let item = match import.ty() {
1159 ExternType::Func(ty) => {
1160 let poll_amt = take_poll_amt(&mut poll_amts);
1161 Func::new_async(&mut store, ty.clone(), move |caller, _, results| {
1162 let ty = ty.clone();
1163 Box::new(async move {
1164 caller.engine().increment_epoch();
1165 log::info!("yielding {poll_amt} times in import");
1166 YieldN(poll_amt).await;
1167 for (ret_ty, result) in ty.results().zip(results) {
1168 *result = ret_ty.default_value().unwrap();
1169 }
1170 Ok(())
1171 })
1172 })
1173 .into()
1174 }
1175 other_ty => match other_ty.default_value(&mut store) {
1176 Ok(item) => item,
1177 Err(e) => {
1178 log::warn!("couldn't create import for {import:?}: {e:?}");
1179 return;
1180 }
1181 },
1182 };
1183 imports.push(item);
1184 }
1185
1186 let instance = block_on(Timeout {
1190 future: Instance::new_async(&mut store, &module, &imports),
1191 polls: take_poll_amt(&mut poll_amts),
1192 end: Instant::now() + Duration::from_millis(2_000),
1193 });
1194 let instance = match instance {
1195 Ok(instantiation_result) => match unwrap_instance(&store, instantiation_result) {
1196 Some(instance) => instance,
1197 None => {
1198 log::info!("instantiation hit a nominal error");
1199 return; }
1201 },
1202 Err(_) => {
1203 log::info!("instantiation failed to complete");
1204 return; }
1206 };
1207
1208 let funcs = instance
1215 .exports(&mut store)
1216 .filter_map(|e| {
1217 let name = e.name().to_string();
1218 let func = e.into_extern().into_func()?;
1219 Some((name, func))
1220 })
1221 .take(10)
1225 .collect::<Vec<_>>();
1226 for (name, func) in funcs {
1227 let ty = func.ty(&store);
1228 let params = ty
1229 .params()
1230 .map(|ty| ty.default_value().unwrap())
1231 .collect::<Vec<_>>();
1232 let mut results = ty
1233 .results()
1234 .map(|ty| ty.default_value().unwrap())
1235 .collect::<Vec<_>>();
1236
1237 log::info!("invoking export {name:?}");
1238 let future = func.call_async(&mut store, ¶ms, &mut results);
1239 match block_on(Timeout {
1240 future,
1241 polls: take_poll_amt(&mut poll_amts),
1242 end: Instant::now() + Duration::from_millis(2_000),
1243 }) {
1244 Ok(_) | Err(Exhausted::Polls) => {}
1246
1247 Err(Exhausted::Time) => return,
1251 }
1252 }
1253
1254 fn take_poll_amt(polls: &mut &[u32]) -> u32 {
1255 match polls.split_first() {
1256 Some((a, rest)) => {
1257 *polls = rest;
1258 *a
1259 }
1260 None => 0,
1261 }
1262 }
1263
1264 struct Timeout<F> {
1269 future: F,
1270 end: Instant,
1273 polls: u32,
1276 }
1277
1278 enum Exhausted {
1279 Time,
1280 Polls,
1281 }
1282
1283 impl<F: Future> Future for Timeout<F> {
1284 type Output = Result<F::Output, Exhausted>;
1285
1286 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1287 let (end, polls, future) = unsafe {
1288 let me = self.get_unchecked_mut();
1289 (me.end, &mut me.polls, Pin::new_unchecked(&mut me.future))
1290 };
1291 match future.poll(cx) {
1292 Poll::Ready(val) => Poll::Ready(Ok(val)),
1293 Poll::Pending => {
1294 if Instant::now() >= end {
1295 log::warn!("future operation timed out");
1296 return Poll::Ready(Err(Exhausted::Time));
1297 }
1298 if *polls == 0 {
1299 log::warn!("future operation ran out of polls");
1300 return Poll::Ready(Err(Exhausted::Polls));
1301 }
1302 *polls -= 1;
1303 Poll::Pending
1304 }
1305 }
1306 }
1307 }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312 use super::*;
1313 use crate::test::{gen_until_pass, test_n_times};
1314 use wasmparser::{Validator, WasmFeatures};
1315
1316 #[test]
1321 fn gc_ops_eventually_gcs() {
1322 if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
1325 return;
1326 }
1327
1328 let ok = gen_until_pass(|(config, test), _| match gc_ops(config, test) {
1329 Ok(result) => Ok(result > 0),
1330 Err(_) => Ok(false),
1331 });
1332
1333 if !ok {
1334 panic!("gc was never found");
1335 }
1336 }
1337
1338 #[test]
1339 fn module_generation_uses_expected_proposals() {
1340 let mut expected = WasmFeatures::MUTABLE_GLOBAL
1343 | WasmFeatures::FLOATS
1344 | WasmFeatures::SIGN_EXTENSION
1345 | WasmFeatures::SATURATING_FLOAT_TO_INT
1346 | WasmFeatures::MULTI_VALUE
1347 | WasmFeatures::BULK_MEMORY
1348 | WasmFeatures::REFERENCE_TYPES
1349 | WasmFeatures::SIMD
1350 | WasmFeatures::MULTI_MEMORY
1351 | WasmFeatures::RELAXED_SIMD
1352 | WasmFeatures::TAIL_CALL
1353 | WasmFeatures::WIDE_ARITHMETIC
1354 | WasmFeatures::MEMORY64
1355 | WasmFeatures::FUNCTION_REFERENCES
1356 | WasmFeatures::GC
1357 | WasmFeatures::GC_TYPES
1358 | WasmFeatures::COMPACT_IMPORTS
1359 | WasmFeatures::CUSTOM_PAGE_SIZES
1360 | WasmFeatures::EXTENDED_CONST
1361 | WasmFeatures::EXCEPTIONS;
1362
1363 let unexpected = WasmFeatures::all() ^ expected;
1369
1370 let ok = gen_until_pass(|config: generators::Config, u| {
1371 let wasm = config.generate(u, None)?.to_bytes();
1372
1373 Validator::new_with_features(WasmFeatures::all()).validate_all(&wasm)?;
1375
1376 for feature in unexpected.iter() {
1379 let ok =
1380 Validator::new_with_features(WasmFeatures::all() ^ feature).validate_all(&wasm);
1381 if ok.is_err() {
1382 wasmtime::bail!("generated a module with {feature:?} but that wasn't expected");
1383 }
1384 }
1385
1386 for feature in expected.iter() {
1390 let ok =
1391 Validator::new_with_features(WasmFeatures::all() ^ feature).validate_all(&wasm);
1392 if ok.is_err() {
1393 expected ^= feature;
1394 }
1395 }
1396
1397 Ok(expected.is_empty())
1398 });
1399
1400 if !ok {
1401 panic!("never generated wasm module using {expected:?}");
1402 }
1403 }
1404
1405 #[test]
1406 fn wast_smoke_test() {
1407 test_n_times(50, |(), u| super::wast_test(u));
1408 }
1409}