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, ¶ms, &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, ¶ms, &mut actual) 922 .unwrap(); 923 log::trace!("received results {actual:?}"); 924 assert_eq!(actual, results); 925 } 926 927 Ok(()) 928 } 929