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", target_arch = "riscv64")))] 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 // Currently we instantiate with a `Linker` which can't instantiate 310 // every single module under the sun due to using name-based resolution 311 // rather than positional-based resolution 312 if string.contains("incompatible import type") { 313 log::debug!("failed to instantiate: {}", string); 314 return None; 315 } 316 317 // Also allow failures to instantiate as a result of hitting instance limits 318 if string.contains("maximum concurrent instance limit") { 319 log::debug!("failed to instantiate: {}", string); 320 return None; 321 } 322 323 // Everything else should be a bug in the fuzzer or a bug in wasmtime 324 panic!("failed to instantiate: {:?}", e); 325 } 326 327 /// Evaluate the function identified by `name` in two different engine 328 /// instances--`lhs` and `rhs`. 329 /// 330 /// Returns `Ok(true)` if more evaluations can happen or `Ok(false)` if the 331 /// instances may have drifted apart and no more evaluations can happen. 332 /// 333 /// # Panics 334 /// 335 /// This will panic if the evaluation is different between engines (e.g., 336 /// results are different, hashed instance is different, one side traps, etc.). 337 pub fn differential( 338 lhs: &mut dyn DiffInstance, 339 lhs_engine: &dyn DiffEngine, 340 rhs: &mut WasmtimeInstance, 341 name: &str, 342 args: &[DiffValue], 343 result_tys: &[DiffValueType], 344 ) -> anyhow::Result<bool> { 345 log::debug!("Evaluating: `{}` with {:?}", name, args); 346 let lhs_results = match lhs.evaluate(name, args, result_tys) { 347 Ok(Some(results)) => Ok(results), 348 Err(e) => Err(e), 349 // this engine couldn't execute this type signature, so discard this 350 // execution by returning success. 351 Ok(None) => return Ok(true), 352 }; 353 log::debug!(" -> results on {}: {:?}", lhs.name(), &lhs_results); 354 355 let rhs_results = rhs 356 .evaluate(name, args, result_tys) 357 // wasmtime should be able to invoke any signature, so unwrap this result 358 .map(|results| results.unwrap()); 359 log::debug!(" -> results on {}: {:?}", rhs.name(), &rhs_results); 360 361 match (lhs_results, rhs_results) { 362 // If the evaluation succeeds, we compare the results. 363 (Ok(lhs_results), Ok(rhs_results)) => assert_eq!(lhs_results, rhs_results), 364 365 // Both sides failed. If either one hits a stack overflow then that's an 366 // engine defined limit which means we can no longer compare the state 367 // of the two instances, so `false` is returned and nothing else is 368 // compared. 369 // 370 // Otherwise, though, the same error should have popped out and this 371 // falls through to checking the intermediate state otherwise. 372 (Err(lhs), Err(rhs)) => { 373 let err = rhs.downcast::<Trap>().expect("not a trap"); 374 let poisoned = err == Trap::StackOverflow || lhs_engine.is_stack_overflow(&lhs); 375 376 if poisoned { 377 return Ok(false); 378 } 379 lhs_engine.assert_error_match(&err, &lhs); 380 } 381 // A real bug is found if only one side fails. 382 (Ok(_), Err(_)) => panic!("only the `rhs` ({}) failed for this input", rhs.name()), 383 (Err(_), Ok(_)) => panic!("only the `lhs` ({}) failed for this input", lhs.name()), 384 }; 385 386 for (global, ty) in rhs.exported_globals() { 387 log::debug!("Comparing global `{global}`"); 388 let lhs = match lhs.get_global(&global, ty) { 389 Some(val) => val, 390 None => continue, 391 }; 392 let rhs = rhs.get_global(&global, ty).unwrap(); 393 assert_eq!(lhs, rhs); 394 } 395 for (memory, shared) in rhs.exported_memories() { 396 log::debug!("Comparing memory `{memory}`"); 397 let lhs = match lhs.get_memory(&memory, shared) { 398 Some(val) => val, 399 None => continue, 400 }; 401 let rhs = rhs.get_memory(&memory, shared).unwrap(); 402 if lhs == rhs { 403 continue; 404 } 405 panic!("memories have differing values"); 406 } 407 408 Ok(true) 409 } 410 411 /// Invoke the given API calls. 412 pub fn make_api_calls(api: generators::api::ApiCalls) { 413 use crate::generators::api::ApiCall; 414 use std::collections::HashMap; 415 416 let mut store: Option<Store<StoreLimits>> = None; 417 let mut modules: HashMap<usize, Module> = Default::default(); 418 let mut instances: HashMap<usize, Instance> = Default::default(); 419 420 for call in api.calls { 421 match call { 422 ApiCall::StoreNew(config) => { 423 log::trace!("creating store"); 424 assert!(store.is_none()); 425 store = Some(config.to_store()); 426 } 427 428 ApiCall::ModuleNew { id, wasm } => { 429 log::debug!("creating module: {}", id); 430 log_wasm(&wasm); 431 let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) { 432 Ok(m) => m, 433 Err(_) => continue, 434 }; 435 let old = modules.insert(id, module); 436 assert!(old.is_none()); 437 } 438 439 ApiCall::ModuleDrop { id } => { 440 log::trace!("dropping module: {}", id); 441 drop(modules.remove(&id)); 442 } 443 444 ApiCall::InstanceNew { id, module } => { 445 log::trace!("instantiating module {} as {}", module, id); 446 let module = match modules.get(&module) { 447 Some(m) => m, 448 None => continue, 449 }; 450 451 let store = store.as_mut().unwrap(); 452 if let Some(instance) = instantiate_with_dummy(store, module) { 453 instances.insert(id, instance); 454 } 455 } 456 457 ApiCall::InstanceDrop { id } => { 458 log::trace!("dropping instance {}", id); 459 drop(instances.remove(&id)); 460 } 461 462 ApiCall::CallExportedFunc { instance, nth } => { 463 log::trace!("calling instance export {} / {}", instance, nth); 464 let instance = match instances.get(&instance) { 465 Some(i) => i, 466 None => { 467 // Note that we aren't guaranteed to instantiate valid 468 // modules, see comments in `InstanceNew` for details on 469 // that. But the API call generator can't know if 470 // instantiation failed, so we might not actually have 471 // this instance. When that's the case, just skip the 472 // API call and keep going. 473 continue; 474 } 475 }; 476 let store = store.as_mut().unwrap(); 477 478 let funcs = instance 479 .exports(&mut *store) 480 .filter_map(|e| match e.into_extern() { 481 Extern::Func(f) => Some(f.clone()), 482 _ => None, 483 }) 484 .collect::<Vec<_>>(); 485 486 if funcs.is_empty() { 487 continue; 488 } 489 490 let nth = nth % funcs.len(); 491 let f = &funcs[nth]; 492 let ty = f.ty(&store); 493 let params = dummy::dummy_values(ty.params()); 494 let mut results = vec![Val::I32(0); ty.results().len()]; 495 let _ = f.call(store, ¶ms, &mut results); 496 } 497 } 498 } 499 } 500 501 /// Executes the wast `test` spectest with the `config` specified. 502 /// 503 /// Ensures that spec tests pass regardless of the `Config`. 504 pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest) { 505 crate::init_fuzzing(); 506 fuzz_config.set_spectest_compliant(); 507 log::debug!("running {:?}", test.file); 508 let mut wast_context = WastContext::new(fuzz_config.to_store()); 509 wast_context.register_spectest(false).unwrap(); 510 wast_context 511 .run_buffer(test.file, test.contents.as_bytes()) 512 .unwrap(); 513 } 514 515 /// Execute a series of `table.get` and `table.set` operations. 516 /// 517 /// Returns the number of `gc` operations which occurred throughout the test 518 /// case -- used to test below that gc happens reasonably soon and eventually. 519 pub fn table_ops( 520 mut fuzz_config: generators::Config, 521 ops: generators::table_ops::TableOps, 522 ) -> usize { 523 let expected_drops = Arc::new(AtomicUsize::new(ops.num_params as usize)); 524 let num_dropped = Arc::new(AtomicUsize::new(0)); 525 526 let num_gcs = Arc::new(AtomicUsize::new(0)); 527 { 528 fuzz_config.wasmtime.consume_fuel = true; 529 let mut store = fuzz_config.to_store(); 530 set_fuel(&mut store, 1_000); 531 532 let wasm = ops.to_wasm_binary(); 533 log_wasm(&wasm); 534 let module = match compile_module(store.engine(), &wasm, false, &fuzz_config) { 535 Some(m) => m, 536 None => return 0, 537 }; 538 539 let mut linker = Linker::new(store.engine()); 540 541 // To avoid timeouts, limit the number of explicit GCs we perform per 542 // test case. 543 const MAX_GCS: usize = 5; 544 545 linker 546 .define( 547 "", 548 "gc", 549 // NB: use `Func::new` so that this can still compile on the old x86 550 // backend, where `IntoFunc` isn't implemented for multi-value 551 // returns. 552 Func::new( 553 &mut store, 554 FuncType::new( 555 vec![], 556 vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef], 557 ), 558 { 559 let num_dropped = num_dropped.clone(); 560 let expected_drops = expected_drops.clone(); 561 let num_gcs = num_gcs.clone(); 562 move |mut caller: Caller<'_, StoreLimits>, _params, results| { 563 log::info!("table_ops: GC"); 564 if num_gcs.fetch_add(1, SeqCst) < MAX_GCS { 565 caller.gc(); 566 } 567 568 let a = ExternRef::new(CountDrops(num_dropped.clone())); 569 let b = ExternRef::new(CountDrops(num_dropped.clone())); 570 let c = ExternRef::new(CountDrops(num_dropped.clone())); 571 572 log::info!("table_ops: make_refs() -> ({:p}, {:p}, {:p})", a, b, c); 573 574 expected_drops.fetch_add(3, SeqCst); 575 results[0] = Some(a).into(); 576 results[1] = Some(b).into(); 577 results[2] = Some(c).into(); 578 Ok(()) 579 } 580 }, 581 ), 582 ) 583 .unwrap(); 584 585 linker 586 .func_wrap("", "take_refs", { 587 let expected_drops = expected_drops.clone(); 588 move |a: Option<ExternRef>, b: Option<ExternRef>, c: Option<ExternRef>| { 589 log::info!( 590 "table_ops: take_refs({}, {}, {})", 591 a.as_ref().map_or_else( 592 || format!("{:p}", std::ptr::null::<()>()), 593 |r| format!("{:p}", *r) 594 ), 595 b.as_ref().map_or_else( 596 || format!("{:p}", std::ptr::null::<()>()), 597 |r| format!("{:p}", *r) 598 ), 599 c.as_ref().map_or_else( 600 || format!("{:p}", std::ptr::null::<()>()), 601 |r| format!("{:p}", *r) 602 ), 603 ); 604 605 // Do the assertion on each ref's inner data, even though it 606 // all points to the same atomic, so that if we happen to 607 // run into a use-after-free bug with one of these refs we 608 // are more likely to trigger a segfault. 609 if let Some(a) = a { 610 let a = a.data().downcast_ref::<CountDrops>().unwrap(); 611 assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst)); 612 } 613 if let Some(b) = b { 614 let b = b.data().downcast_ref::<CountDrops>().unwrap(); 615 assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst)); 616 } 617 if let Some(c) = c { 618 let c = c.data().downcast_ref::<CountDrops>().unwrap(); 619 assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst)); 620 } 621 } 622 }) 623 .unwrap(); 624 625 linker 626 .define( 627 "", 628 "make_refs", 629 // NB: use `Func::new` so that this can still compile on the old 630 // x86 backend, where `IntoFunc` isn't implemented for 631 // multi-value returns. 632 Func::new( 633 &mut store, 634 FuncType::new( 635 vec![], 636 vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef], 637 ), 638 { 639 let num_dropped = num_dropped.clone(); 640 let expected_drops = expected_drops.clone(); 641 move |_caller, _params, results| { 642 log::info!("table_ops: make_refs"); 643 expected_drops.fetch_add(3, SeqCst); 644 results[0] = 645 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 646 results[1] = 647 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 648 results[2] = 649 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 650 Ok(()) 651 } 652 }, 653 ), 654 ) 655 .unwrap(); 656 657 let instance = linker.instantiate(&mut store, &module).unwrap(); 658 let run = instance.get_func(&mut store, "run").unwrap(); 659 660 let args: Vec<_> = (0..ops.num_params) 661 .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone()))))) 662 .collect(); 663 664 // The generated function should always return a trap. The only two 665 // valid traps are table-out-of-bounds which happens through `table.get` 666 // and `table.set` generated or an out-of-fuel trap. Otherwise any other 667 // error is unexpected and should fail fuzzing. 668 let trap = run 669 .call(&mut store, &args, &mut []) 670 .unwrap_err() 671 .downcast::<Trap>() 672 .unwrap(); 673 674 match trap { 675 Trap::TableOutOfBounds | Trap::OutOfFuel => {} 676 _ => panic!("unexpected trap: {trap}"), 677 } 678 679 // Do a final GC after running the Wasm. 680 store.gc(); 681 } 682 683 assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst)); 684 return num_gcs.load(SeqCst); 685 686 struct CountDrops(Arc<AtomicUsize>); 687 688 impl Drop for CountDrops { 689 fn drop(&mut self) { 690 self.0.fetch_add(1, SeqCst); 691 } 692 } 693 } 694 695 // Test that the `table_ops` fuzzer eventually runs the gc function in the host. 696 // We've historically had issues where this fuzzer accidentally wasn't fuzzing 697 // anything for a long time so this is an attempt to prevent that from happening 698 // again. 699 #[test] 700 fn table_ops_eventually_gcs() { 701 use arbitrary::Unstructured; 702 use rand::prelude::*; 703 704 // Skip if we're under emulation because some fuzz configurations will do 705 // large address space reservations that QEMU doesn't handle well. 706 if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() { 707 return; 708 } 709 710 let mut rng = SmallRng::seed_from_u64(0); 711 let mut buf = vec![0; 2048]; 712 let n = 100; 713 for _ in 0..n { 714 rng.fill_bytes(&mut buf); 715 let u = Unstructured::new(&buf); 716 717 if let Ok((config, test)) = Arbitrary::arbitrary_take_rest(u) { 718 if table_ops(config, test) > 0 { 719 return; 720 } 721 } 722 } 723 724 panic!("after {n} runs nothing ever gc'd, something is probably wrong"); 725 } 726 727 #[derive(Default)] 728 struct SignalOnDrop { 729 state: Arc<(Mutex<bool>, Condvar)>, 730 thread: Option<std::thread::JoinHandle<()>>, 731 } 732 733 impl SignalOnDrop { 734 fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) { 735 let state = self.state.clone(); 736 let start = Instant::now(); 737 self.thread = Some(std::thread::spawn(move || { 738 // Using our mutex/condvar we wait here for the first of `dur` to 739 // pass or the `SignalOnDrop` instance to get dropped. 740 let (lock, cvar) = &*state; 741 let mut signaled = lock.lock().unwrap(); 742 while !*signaled { 743 // Adjust our requested `dur` based on how much time has passed. 744 let dur = match dur.checked_sub(start.elapsed()) { 745 Some(dur) => dur, 746 None => break, 747 }; 748 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap(); 749 signaled = lock; 750 // If we timed out for sure then there's no need to continue 751 // since we'll just abort on the next `checked_sub` anyway. 752 if result.timed_out() { 753 break; 754 } 755 } 756 drop(signaled); 757 758 closure(); 759 })); 760 } 761 } 762 763 impl Drop for SignalOnDrop { 764 fn drop(&mut self) { 765 if let Some(thread) = self.thread.take() { 766 let (lock, cvar) = &*self.state; 767 // Signal our thread that we've been dropped and wake it up if it's 768 // blocked. 769 let mut g = lock.lock().unwrap(); 770 *g = true; 771 cvar.notify_one(); 772 drop(g); 773 774 // ... and then wait for the thread to exit to ensure we clean up 775 // after ourselves. 776 thread.join().unwrap(); 777 } 778 } 779 } 780 781 /// Set the amount of fuel in a store to a given value 782 pub fn set_fuel<T>(store: &mut Store<T>, fuel: u64) { 783 // Determine the amount of fuel already within the store, if any, and 784 // add/consume as appropriate to set the remaining amount to` fuel`. 785 let remaining = store.consume_fuel(0).unwrap(); 786 if fuel > remaining { 787 store.add_fuel(fuel - remaining).unwrap(); 788 } else { 789 store.consume_fuel(remaining - fuel).unwrap(); 790 } 791 // double-check that the store has the expected amount of fuel remaining 792 assert_eq!(store.consume_fuel(0).unwrap(), fuel); 793 } 794 795 /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create 796 /// arbitrary types and values. 797 pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> { 798 use crate::generators::component_types; 799 use anyhow::Result; 800 use component_fuzz_util::{TestCase, EXPORT_FUNCTION, IMPORT_FUNCTION}; 801 use component_test_util::FuncExt; 802 use wasmtime::component::{Component, Linker, Val}; 803 804 crate::init_fuzzing(); 805 806 let case = input.arbitrary::<TestCase>()?; 807 808 let mut config = component_test_util::config(); 809 config.debug_adapter_modules(input.arbitrary()?); 810 let engine = Engine::new(&config).unwrap(); 811 let mut store = Store::new(&engine, (Vec::new(), None)); 812 let wat = case.declarations().make_component(); 813 let wat = wat.as_bytes(); 814 log_wasm(wat); 815 let component = Component::new(&engine, wat).unwrap(); 816 let mut linker = Linker::new(&engine); 817 818 linker 819 .root() 820 .func_new(&component, IMPORT_FUNCTION, { 821 move |mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>, 822 params: &[Val], 823 results: &mut [Val]| 824 -> Result<()> { 825 log::trace!("received params {params:?}"); 826 let (expected_args, expected_results) = cx.data_mut(); 827 assert_eq!(params.len(), expected_args.len()); 828 for (expected, actual) in expected_args.iter().zip(params) { 829 assert_eq!(expected, actual); 830 } 831 results.clone_from_slice(&expected_results.take().unwrap()); 832 log::trace!("returning results {results:?}"); 833 Ok(()) 834 } 835 }) 836 .unwrap(); 837 838 let instance = linker.instantiate(&mut store, &component).unwrap(); 839 let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap(); 840 let param_tys = func.params(&store); 841 let result_tys = func.results(&store); 842 843 while input.arbitrary()? { 844 let params = param_tys 845 .iter() 846 .map(|ty| component_types::arbitrary_val(ty, input)) 847 .collect::<arbitrary::Result<Vec<_>>>()?; 848 let results = result_tys 849 .iter() 850 .map(|ty| component_types::arbitrary_val(ty, input)) 851 .collect::<arbitrary::Result<Vec<_>>>()?; 852 853 *store.data_mut() = (params.clone(), Some(results.clone())); 854 855 log::trace!("passing params {params:?}"); 856 let mut actual = vec![Val::Bool(false); results.len()]; 857 func.call_and_post_return(&mut store, ¶ms, &mut actual) 858 .unwrap(); 859 log::trace!("received results {actual:?}"); 860 assert_eq!(actual, results); 861 } 862 863 Ok(()) 864 } 865