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