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 pub mod dummy; 14 15 use arbitrary::Arbitrary; 16 use log::debug; 17 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; 18 use std::sync::{Arc, Condvar, Mutex}; 19 use std::time::{Duration, Instant}; 20 use wasmtime::*; 21 use wasmtime_wast::WastContext; 22 23 static CNT: AtomicUsize = AtomicUsize::new(0); 24 25 fn log_wasm(wasm: &[u8]) { 26 if !log::log_enabled!(log::Level::Debug) { 27 return; 28 } 29 30 let i = CNT.fetch_add(1, SeqCst); 31 let name = format!("testcase{}.wasm", i); 32 std::fs::write(&name, wasm).expect("failed to write wasm file"); 33 log::debug!("wrote wasm file to `{}`", name); 34 let wat = format!("testcase{}.wat", i); 35 match wasmprinter::print_bytes(wasm) { 36 Ok(s) => std::fs::write(&wat, s).expect("failed to write wat file"), 37 // If wasmprinter failed remove a `*.wat` file, if any, to avoid 38 // confusing a preexisting one with this wasm which failed to get 39 // printed. 40 Err(_) => drop(std::fs::remove_file(&wat)), 41 } 42 } 43 44 fn create_store(engine: &Engine) -> Store<StoreLimits> { 45 let mut store = Store::new( 46 &engine, 47 StoreLimits { 48 // Limits tables/memories within a store to at most 1gb for now to 49 // exercise some larger address but not overflow various limits. 50 remaining_memory: 1 << 30, 51 oom: false, 52 }, 53 ); 54 store.limiter(|s| s as &mut dyn ResourceLimiter); 55 return store; 56 } 57 58 struct StoreLimits { 59 /// Remaining memory, in bytes, left to allocate 60 remaining_memory: usize, 61 /// Whether or not an allocation request has been denied 62 oom: bool, 63 } 64 65 impl StoreLimits { 66 fn alloc(&mut self, amt: usize) -> bool { 67 match self.remaining_memory.checked_sub(amt) { 68 Some(mem) => { 69 self.remaining_memory = mem; 70 true 71 } 72 None => { 73 self.oom = true; 74 false 75 } 76 } 77 } 78 } 79 80 impl ResourceLimiter for StoreLimits { 81 fn memory_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool { 82 // Units provided are in wasm pages, so adjust them to bytes to see if 83 // we are ok to allocate this much. 84 self.alloc((desired - current) as usize * 16 * 1024) 85 } 86 87 fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool { 88 // Units provided are in table elements, and for now we allocate one 89 // pointer per table element, so use that size for an adjustment into 90 // bytes. 91 let delta = (desired - current) as usize * std::mem::size_of::<usize>(); 92 self.alloc(delta) 93 } 94 } 95 96 /// Methods of timing out execution of a WebAssembly module 97 #[derive(Debug)] 98 pub enum Timeout { 99 /// No timeout is used, it should be guaranteed via some other means that 100 /// the input does not infinite loop. 101 None, 102 /// A time-based timeout is used with a sleeping thread sending a signal 103 /// after the specified duration. 104 Time(Duration), 105 /// Fuel-based timeouts are used where the specified fuel is all that the 106 /// provided wasm module is allowed to consume. 107 Fuel(u64), 108 } 109 110 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected 111 /// panic or segfault or anything else that can be detected "passively". 112 /// 113 /// Performs initial validation, and returns early if the Wasm is invalid. 114 /// 115 /// You can control which compiler is used via passing a `Strategy`. 116 pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) { 117 // Explicitly disable module linking for now since it's a breaking change to 118 // pre-module-linking modules due to imports 119 let mut cfg = crate::fuzz_default_config(strategy).unwrap(); 120 cfg.wasm_module_linking(false); 121 instantiate_with_config(wasm, known_valid, cfg, Timeout::None); 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 /// 129 /// See also `instantiate` functions. 130 pub fn instantiate_with_config( 131 wasm: &[u8], 132 known_valid: bool, 133 mut config: Config, 134 timeout: Timeout, 135 ) { 136 crate::init_fuzzing(); 137 138 config.interruptable(match &timeout { 139 Timeout::Time(_) => true, 140 _ => false, 141 }); 142 config.consume_fuel(match &timeout { 143 Timeout::Fuel(_) => true, 144 _ => false, 145 }); 146 let engine = Engine::new(&config).unwrap(); 147 let mut store = create_store(&engine); 148 149 let mut timeout_state = SignalOnDrop::default(); 150 match timeout { 151 Timeout::Fuel(fuel) => store.add_fuel(fuel).unwrap(), 152 // If a timeout is requested then we spawn a helper thread to wait for 153 // the requested time and then send us a signal to get interrupted. We 154 // also arrange for the thread's sleep to get interrupted if we return 155 // early (or the wasm returns within the time limit), which allows the 156 // thread to get torn down. 157 // 158 // This prevents us from creating a huge number of sleeping threads if 159 // this function is executed in a loop, like it does on nightly fuzzing 160 // infrastructure. 161 Timeout::Time(timeout) => { 162 let handle = store.interrupt_handle().unwrap(); 163 timeout_state.spawn_timeout(timeout, move || handle.interrupt()); 164 } 165 Timeout::None => {} 166 } 167 168 log_wasm(wasm); 169 let module = match Module::new(&engine, wasm) { 170 Ok(module) => module, 171 Err(_) if !known_valid => return, 172 Err(e) => panic!("failed to compile module: {:?}", e), 173 }; 174 175 instantiate_with_dummy(&mut store, &module); 176 } 177 178 fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> { 179 // Creation of imports can fail due to resource limit constraints, and then 180 // instantiation can naturally fail for a number of reasons as well. Bundle 181 // the two steps together to match on the error below. 182 let instance = 183 dummy::dummy_linker(store, module).and_then(|l| l.instantiate(&mut *store, module)); 184 185 let e = match instance { 186 Ok(i) => return Some(i), 187 Err(e) => e, 188 }; 189 190 // If the instantiation hit OOM for some reason then that's ok, it's 191 // expected that fuzz-generated programs try to allocate lots of 192 // stuff. 193 if store.data().oom { 194 return None; 195 } 196 197 // Allow traps which can happen normally with `unreachable` or a 198 // timeout or such 199 if e.downcast_ref::<Trap>().is_some() { 200 return None; 201 } 202 203 let string = e.to_string(); 204 // Also allow errors related to fuel consumption 205 if string.contains("all fuel consumed") 206 // Currently we instantiate with a `Linker` which can't instantiate 207 // every single module under the sun due to using name-based resolution 208 // rather than positional-based resolution 209 || string.contains("incompatible import type") 210 { 211 return None; 212 } 213 214 // Everything else should be a bug in the fuzzer or a bug in wasmtime 215 panic!("failed to instantiate {:?}", e); 216 } 217 218 /// Compile the Wasm buffer, and implicitly fail if we have an unexpected 219 /// panic or segfault or anything else that can be detected "passively". 220 /// 221 /// Performs initial validation, and returns early if the Wasm is invalid. 222 /// 223 /// You can control which compiler is used via passing a `Strategy`. 224 pub fn compile(wasm: &[u8], strategy: Strategy) { 225 crate::init_fuzzing(); 226 227 let engine = Engine::new(&crate::fuzz_default_config(strategy).unwrap()).unwrap(); 228 log_wasm(wasm); 229 let _ = Module::new(&engine, wasm); 230 } 231 232 /// Instantiate the given Wasm module with each `Config` and call all of its 233 /// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are 234 /// or aren't enabled for different configs, we should get the same results when 235 /// we call the exported functions for all of our different configs. 236 pub fn differential_execution( 237 module: &crate::generators::GeneratedModule, 238 configs: &[crate::generators::DifferentialConfig], 239 ) { 240 use std::collections::{HashMap, HashSet}; 241 242 crate::init_fuzzing(); 243 244 // We need at least two configs. 245 if configs.len() < 2 246 // And all the configs should be unique. 247 || configs.iter().collect::<HashSet<_>>().len() != configs.len() 248 { 249 return; 250 } 251 252 let configs: Vec<_> = match configs.iter().map(|c| c.to_wasmtime_config()).collect() { 253 Ok(cs) => cs, 254 // If the config is trying to use something that was turned off at 255 // compile time, eg lightbeam, just continue to the next fuzz input. 256 Err(_) => return, 257 }; 258 259 let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default(); 260 let wasm = module.to_bytes(); 261 log_wasm(&wasm); 262 263 for mut config in configs { 264 // Disable module linking since it isn't enabled by default for 265 // `GeneratedModule` but is enabled by default for our fuzz config. 266 // Since module linking is currently a breaking change this is required 267 // to accept modules that would otherwise be broken by module linking. 268 config.wasm_module_linking(false); 269 270 let engine = Engine::new(&config).unwrap(); 271 let mut store = create_store(&engine); 272 273 let module = Module::new(&engine, &wasm).unwrap(); 274 275 // TODO: we should implement tracing versions of these dummy imports 276 // that record a trace of the order that imported functions were called 277 // in and with what values. Like the results of exported functions, 278 // calls to imports should also yield the same values for each 279 // configuration, and we should assert that. 280 let instance = match instantiate_with_dummy(&mut store, &module) { 281 Some(instance) => instance, 282 None => continue, 283 }; 284 285 let exports = instance 286 .exports(&mut store) 287 .filter_map(|e| { 288 let name = e.name().to_string(); 289 e.into_func().map(|f| (name, f)) 290 }) 291 .collect::<Vec<_>>(); 292 for (name, f) in exports { 293 // Always call the hang limit initializer first, so that we don't 294 // infinite loop when calling another export. 295 init_hang_limit(&mut store, instance); 296 297 let ty = f.ty(&store); 298 let params = dummy::dummy_values(ty.params()); 299 let this_result = f 300 .call(&mut store, ¶ms) 301 .map_err(|e| e.downcast::<Trap>().unwrap()); 302 303 let existing_result = export_func_results 304 .entry(name.to_string()) 305 .or_insert_with(|| this_result.clone()); 306 assert_same_export_func_result(&existing_result, &this_result, &name); 307 } 308 } 309 310 fn init_hang_limit<T>(store: &mut Store<T>, instance: Instance) { 311 match instance.get_export(&mut *store, "hangLimitInitializer") { 312 None => return, 313 Some(Extern::Func(f)) => { 314 f.call(store, &[]) 315 .expect("initializing the hang limit should not fail"); 316 } 317 Some(_) => panic!("unexpected hangLimitInitializer export"), 318 } 319 } 320 321 fn assert_same_export_func_result( 322 lhs: &Result<Box<[Val]>, Trap>, 323 rhs: &Result<Box<[Val]>, Trap>, 324 func_name: &str, 325 ) { 326 let fail = || { 327 panic!( 328 "differential fuzzing failed: exported func {} returned two \ 329 different results: {:?} != {:?}", 330 func_name, lhs, rhs 331 ) 332 }; 333 334 match (lhs, rhs) { 335 (Err(_), Err(_)) => {} 336 (Ok(lhs), Ok(rhs)) => { 337 if lhs.len() != rhs.len() { 338 fail(); 339 } 340 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) { 341 match (lhs, rhs) { 342 (Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue, 343 (Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue, 344 (Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue, 345 (Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue, 346 (Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue, 347 (Val::ExternRef(_), Val::ExternRef(_)) 348 | (Val::FuncRef(_), Val::FuncRef(_)) => continue, 349 _ => fail(), 350 } 351 } 352 } 353 _ => fail(), 354 } 355 } 356 } 357 358 fn f32_equal(a: u32, b: u32) -> bool { 359 let a = f32::from_bits(a); 360 let b = f32::from_bits(b); 361 a == b || (a.is_nan() && b.is_nan()) 362 } 363 364 fn f64_equal(a: u64, b: u64) -> bool { 365 let a = f64::from_bits(a); 366 let b = f64::from_bits(b); 367 a == b || (a.is_nan() && b.is_nan()) 368 } 369 370 /// Invoke the given API calls. 371 pub fn make_api_calls(api: crate::generators::api::ApiCalls) { 372 use crate::generators::api::ApiCall; 373 use std::collections::HashMap; 374 375 crate::init_fuzzing(); 376 377 let mut config: Option<Config> = None; 378 let mut engine: Option<Engine> = None; 379 let mut store: Option<Store<StoreLimits>> = None; 380 let mut modules: HashMap<usize, Module> = Default::default(); 381 let mut instances: HashMap<usize, Instance> = Default::default(); 382 383 for call in api.calls { 384 match call { 385 ApiCall::ConfigNew => { 386 log::trace!("creating config"); 387 assert!(config.is_none()); 388 config = Some(crate::fuzz_default_config(wasmtime::Strategy::Cranelift).unwrap()); 389 } 390 391 ApiCall::ConfigDebugInfo(b) => { 392 log::trace!("enabling debuginfo"); 393 config.as_mut().unwrap().debug_info(b); 394 } 395 396 ApiCall::ConfigInterruptable(b) => { 397 log::trace!("enabling interruption"); 398 config.as_mut().unwrap().interruptable(b); 399 } 400 401 ApiCall::EngineNew => { 402 log::trace!("creating engine"); 403 assert!(engine.is_none()); 404 engine = Some(Engine::new(config.as_ref().unwrap()).unwrap()); 405 } 406 407 ApiCall::StoreNew => { 408 log::trace!("creating store"); 409 assert!(store.is_none()); 410 store = Some(create_store(engine.as_ref().unwrap())); 411 } 412 413 ApiCall::ModuleNew { id, wasm } => { 414 log::debug!("creating module: {}", id); 415 let wasm = wasm.to_bytes(); 416 log_wasm(&wasm); 417 let module = match Module::new(engine.as_ref().unwrap(), &wasm) { 418 Ok(m) => m, 419 Err(_) => continue, 420 }; 421 let old = modules.insert(id, module); 422 assert!(old.is_none()); 423 } 424 425 ApiCall::ModuleDrop { id } => { 426 log::trace!("dropping module: {}", id); 427 drop(modules.remove(&id)); 428 } 429 430 ApiCall::InstanceNew { id, module } => { 431 log::trace!("instantiating module {} as {}", module, id); 432 let module = match modules.get(&module) { 433 Some(m) => m, 434 None => continue, 435 }; 436 437 let store = store.as_mut().unwrap(); 438 if let Some(instance) = instantiate_with_dummy(store, module) { 439 instances.insert(id, instance); 440 } 441 } 442 443 ApiCall::InstanceDrop { id } => { 444 log::trace!("dropping instance {}", id); 445 drop(instances.remove(&id)); 446 } 447 448 ApiCall::CallExportedFunc { instance, nth } => { 449 log::trace!("calling instance export {} / {}", instance, nth); 450 let instance = match instances.get(&instance) { 451 Some(i) => i, 452 None => { 453 // Note that we aren't guaranteed to instantiate valid 454 // modules, see comments in `InstanceNew` for details on 455 // that. But the API call generator can't know if 456 // instantiation failed, so we might not actually have 457 // this instance. When that's the case, just skip the 458 // API call and keep going. 459 continue; 460 } 461 }; 462 let store = store.as_mut().unwrap(); 463 464 let funcs = instance 465 .exports(&mut *store) 466 .filter_map(|e| match e.into_extern() { 467 Extern::Func(f) => Some(f.clone()), 468 _ => None, 469 }) 470 .collect::<Vec<_>>(); 471 472 if funcs.is_empty() { 473 continue; 474 } 475 476 let nth = nth % funcs.len(); 477 let f = &funcs[nth]; 478 let ty = f.ty(&store); 479 let params = dummy::dummy_values(ty.params()); 480 let _ = f.call(store, ¶ms); 481 } 482 } 483 } 484 } 485 486 /// Executes the wast `test` spectest with the `config` specified. 487 /// 488 /// Ensures that spec tests pass regardless of the `Config`. 489 pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators::SpecTest) { 490 crate::init_fuzzing(); 491 log::debug!("running {:?} with {:?}", test.file, fuzz_config); 492 let mut config = fuzz_config.to_wasmtime(); 493 config.wasm_reference_types(false); 494 config.wasm_bulk_memory(false); 495 config.wasm_module_linking(false); 496 config.wasm_multi_memory(false); 497 let mut store = create_store(&Engine::new(&config).unwrap()); 498 if fuzz_config.consume_fuel { 499 store.add_fuel(u64::max_value()).unwrap(); 500 } 501 let mut wast_context = WastContext::new(store); 502 wast_context.register_spectest().unwrap(); 503 wast_context 504 .run_buffer(test.file, test.contents.as_bytes()) 505 .unwrap(); 506 } 507 508 /// Execute a series of `table.get` and `table.set` operations. 509 pub fn table_ops( 510 fuzz_config: crate::generators::Config, 511 ops: crate::generators::table_ops::TableOps, 512 ) { 513 let _ = env_logger::try_init(); 514 515 let num_dropped = Arc::new(AtomicUsize::new(0)); 516 517 { 518 let mut config = fuzz_config.to_wasmtime(); 519 config.wasm_reference_types(true); 520 let engine = Engine::new(&config).unwrap(); 521 let mut store = create_store(&engine); 522 if fuzz_config.consume_fuel { 523 store.add_fuel(u64::max_value()).unwrap(); 524 } 525 526 let wasm = ops.to_wasm_binary(); 527 log_wasm(&wasm); 528 let module = match Module::new(&engine, &wasm) { 529 Ok(m) => m, 530 Err(_) => return, 531 }; 532 533 // To avoid timeouts, limit the number of explicit GCs we perform per 534 // test case. 535 const MAX_GCS: usize = 5; 536 537 let num_gcs = AtomicUsize::new(0); 538 let gc = Func::wrap(&mut store, move |mut caller: Caller<'_, StoreLimits>| { 539 if num_gcs.fetch_add(1, SeqCst) < MAX_GCS { 540 caller.gc(); 541 } 542 }); 543 544 let instance = Instance::new(&mut store, &module, &[gc.into()]).unwrap(); 545 let run = instance.get_func(&mut store, "run").unwrap(); 546 547 let args: Vec<_> = (0..ops.num_params()) 548 .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone()))))) 549 .collect(); 550 let _ = run.call(&mut store, &args); 551 } 552 553 assert_eq!(num_dropped.load(SeqCst), ops.num_params() as usize); 554 return; 555 556 struct CountDrops(Arc<AtomicUsize>); 557 558 impl Drop for CountDrops { 559 fn drop(&mut self) { 560 self.0.fetch_add(1, SeqCst); 561 } 562 } 563 } 564 565 /// Configuration options for wasm-smith such that generated modules always 566 /// conform to certain specifications. 567 #[derive(Default, Debug, Arbitrary, Clone)] 568 pub struct DifferentialWasmiModuleConfig; 569 570 impl wasm_smith::Config for DifferentialWasmiModuleConfig { 571 fn allow_start_export(&self) -> bool { 572 false 573 } 574 575 fn min_funcs(&self) -> usize { 576 1 577 } 578 579 fn max_funcs(&self) -> usize { 580 1 581 } 582 583 fn min_memories(&self) -> u32 { 584 1 585 } 586 587 fn max_memories(&self) -> usize { 588 1 589 } 590 591 fn max_imports(&self) -> usize { 592 0 593 } 594 595 fn min_exports(&self) -> usize { 596 2 597 } 598 599 fn max_memory_pages(&self, _is_64: bool) -> u64 { 600 1 601 } 602 603 fn memory_max_size_required(&self) -> bool { 604 true 605 } 606 } 607 608 /// Perform differential execution between Cranelift and wasmi, diffing the 609 /// resulting memory image when execution terminates. This relies on the 610 /// module-under-test to be instrumented to bound the execution time. Invoke 611 /// with a module generated by `wasm-smith` using the 612 /// `DiferentialWasmiModuleConfig` configuration type for best results. 613 /// 614 /// May return `None` if we early-out due to a rejected fuzz config; these 615 /// should be rare if modules are generated appropriately. 616 pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> { 617 crate::init_fuzzing(); 618 619 // Instantiate wasmi module and instance. 620 let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?; 621 let wasmi_instance = 622 wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?; 623 let wasmi_instance = wasmi_instance.assert_no_start(); 624 625 // TODO(paritytech/wasmi#19): wasmi does not currently canonicalize NaNs. To avoid spurious 626 // fuzz failures, for now let's fuzz only integer Wasm programs. 627 if wasmi_module.deny_floating_point().is_err() { 628 return None; 629 } 630 631 // Instantiate wasmtime module and instance. 632 let mut wasmtime_config = config.to_wasmtime(); 633 wasmtime_config.cranelift_nan_canonicalization(true); 634 let wasmtime_engine = Engine::new(&wasmtime_config).unwrap(); 635 let mut wasmtime_store = create_store(&wasmtime_engine); 636 if config.consume_fuel { 637 wasmtime_store.add_fuel(u64::max_value()).unwrap(); 638 } 639 let wasmtime_module = 640 Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module"); 641 let wasmtime_instance = Instance::new(&mut wasmtime_store, &wasmtime_module, &[]) 642 .expect("Wasmtime can instantiate module"); 643 644 // Introspect wasmtime module to find name of an exported function and of an 645 // exported memory. Stop when we have one of each. (According to the config 646 // above, there should be at most one of each.) 647 let (func_name, memory_name) = { 648 let mut func_name = None; 649 let mut memory_name = None; 650 for e in wasmtime_module.exports() { 651 match e.ty() { 652 wasmtime::ExternType::Func(..) => func_name = Some(e.name().to_string()), 653 wasmtime::ExternType::Memory(..) => memory_name = Some(e.name().to_string()), 654 _ => {} 655 } 656 if func_name.is_some() && memory_name.is_some() { 657 break; 658 } 659 } 660 (func_name?, memory_name?) 661 }; 662 663 let wasmi_mem_export = wasmi_instance.export_by_name(&memory_name[..]).unwrap(); 664 let wasmi_mem = wasmi_mem_export.as_memory().unwrap(); 665 let wasmi_main_export = wasmi_instance.export_by_name(&func_name[..]).unwrap(); 666 let wasmi_main = wasmi_main_export.as_func().unwrap(); 667 let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals); 668 669 let wasmtime_mem = wasmtime_instance 670 .get_memory(&mut wasmtime_store, &memory_name[..]) 671 .expect("memory export is present"); 672 let wasmtime_main = wasmtime_instance 673 .get_func(&mut wasmtime_store, &func_name[..]) 674 .expect("function export is present"); 675 let wasmtime_vals = wasmtime_main.call(&mut wasmtime_store, &[]); 676 let wasmtime_val = wasmtime_vals.map(|v| v.iter().next().cloned()); 677 678 debug!( 679 "Successful execution: wasmi returned {:?}, wasmtime returned {:?}", 680 wasmi_val, wasmtime_val 681 ); 682 683 let show_wat = || { 684 if let Ok(s) = wasmprinter::print_bytes(&wasm[..]) { 685 eprintln!("wat:\n{}\n", s); 686 } 687 }; 688 689 match (&wasmi_val, &wasmtime_val) { 690 (&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {} 691 (&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b)))) 692 if f32_equal(a.to_bits(), b) => {} 693 (&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {} 694 (&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b)))) 695 if f64_equal(a.to_bits(), b) => {} 696 (&Ok(None), &Ok(None)) => {} 697 (&Err(_), &Err(_)) => {} 698 _ => { 699 show_wat(); 700 panic!( 701 "Values do not match: wasmi returned {:?}; wasmtime returned {:?}", 702 wasmi_val, wasmtime_val 703 ); 704 } 705 } 706 707 if wasmi_mem.current_size().0 != wasmtime_mem.size(&wasmtime_store) as usize { 708 show_wat(); 709 panic!("resulting memories are not the same size"); 710 } 711 712 // Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk. 713 let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size(&wasmtime_store)]; 714 wasmi_mem 715 .get_into(0, &mut wasmi_buf[..]) 716 .expect("can access wasmi memory"); 717 718 let wasmtime_slice = wasmtime_mem.data(&wasmtime_store); 719 720 if wasmi_buf.len() >= 64 { 721 debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]); 722 debug!( 723 "-> First 64 bytes of Wasmtime heap: {:?}", 724 &wasmtime_slice[0..64] 725 ); 726 } 727 728 if &wasmi_buf[..] != &wasmtime_slice[..] { 729 show_wat(); 730 panic!("memory contents are not equal"); 731 } 732 733 Some(()) 734 } 735 736 #[derive(Default)] 737 struct SignalOnDrop { 738 state: Arc<(Mutex<bool>, Condvar)>, 739 thread: Option<std::thread::JoinHandle<()>>, 740 } 741 742 impl SignalOnDrop { 743 fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) { 744 let state = self.state.clone(); 745 let start = Instant::now(); 746 self.thread = Some(std::thread::spawn(move || { 747 // Using our mutex/condvar we wait here for the first of `dur` to 748 // pass or the `SignalOnDrop` instance to get dropped. 749 let (lock, cvar) = &*state; 750 let mut signaled = lock.lock().unwrap(); 751 while !*signaled { 752 // Adjust our requested `dur` based on how much time has passed. 753 let dur = match dur.checked_sub(start.elapsed()) { 754 Some(dur) => dur, 755 None => break, 756 }; 757 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap(); 758 signaled = lock; 759 // If we timed out for sure then there's no need to continue 760 // since we'll just abort on the next `checked_sub` anyway. 761 if result.timed_out() { 762 break; 763 } 764 } 765 drop(signaled); 766 767 closure(); 768 })); 769 } 770 } 771 772 impl Drop for SignalOnDrop { 773 fn drop(&mut self) { 774 if let Some(thread) = self.thread.take() { 775 let (lock, cvar) = &*self.state; 776 // Signal our thread that we've been dropped and wake it up if it's 777 // blocked. 778 let mut g = lock.lock().unwrap(); 779 *g = true; 780 cvar.notify_one(); 781 drop(g); 782 783 // ... and then wait for the thread to exit to ensure we clean up 784 // after ourselves. 785 thread.join().unwrap(); 786 } 787 } 788 } 789