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