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