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