use macro_wasmer_engine_test::engine_test;
#[cfg(feature = "js")]
use wasm_bindgen_test::wasm_bindgen_test;
use wasmer::*;
fn called_through_foreign_frames<R>(f: impl FnOnce() -> R) -> R {
f()
}
fn growable_memory(store: &mut Store) -> Memory {
let shared = MemoryType::new(Pages(1), Some(Pages(4)), true);
let owned = MemoryType::new(Pages(1), Some(Pages(4)), false);
Memory::new(&mut *store, shared)
.or_else(|_| Memory::new(&mut *store, owned))
.expect("create memory")
}
fn run_host_function(store: &mut Store, host: Function) -> Result<(), String> {
let wat = r#"(module
(func $host (import "host" "run"))
(func (export "run") (call $host))
)"#;
let module = Module::new(&*store, wat).map_err(|e| format!("{e:?}"))?;
let imports = imports! { "host" => { "run" => host } };
let instance = Instance::new(&mut *store, &module, &imports).map_err(|e| format!("{e:?}"))?;
instance
.exports
.get_typed_function::<(), ()>(&*store, "run")
.map_err(|e| format!("{e:?}"))?
.call(&mut *store)
.map_err(|e| format!("{e:?}"))
}
#[derive(Default)]
struct Report {
lent_unparked: bool,
lent_parked: bool,
lent_after_park: bool,
same_store: bool,
size_before_grow: Option<Pages>,
}
struct HostEnv {
memory: Memory,
report: Report,
}
#[engine_test]
fn store_is_lent_out_only_while_parked() -> Result<(), String> {
let mut store = Store::default();
let memory = growable_memory(&mut store);
let env = FunctionEnv::new(
&mut store,
HostEnv {
memory: memory.clone(),
report: Report::default(),
},
);
let host =
Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut<HostEnv>| {
let memory = env.data().memory.clone();
let store_id = env.objects_mut().id();
let lent_unparked =
called_through_foreign_frames(|| Store::with_current(|_| ()).is_some());
let mut same_store = false;
let size_before_grow = Some(memory.view(&env).size());
let lent_parked = env
.as_store_mut()
.parked(|| {
called_through_foreign_frames(|| {
Store::with_current(|store| {
same_store = store.objects_mut().id() == store_id
&& memory.is_from_store(&*store);
memory.grow(store, Pages(1)).expect("grow the lent memory");
})
})
})
.is_some();
let lent_after_park =
called_through_foreign_frames(|| Store::with_current(|_| ()).is_some());
env.data_mut().report = Report {
lent_unparked,
lent_parked,
lent_after_park,
same_store,
size_before_grow,
};
});
run_host_function(&mut store, host)?;
let report = &env.as_ref(&store).report;
assert!(
!report.lent_unparked,
"a store still held by the host function must not be lent out"
);
if !report.lent_parked {
return Ok(());
}
assert!(
report.same_store,
"the store lent out must be the one the call is running under"
);
assert!(
!report.lent_after_park,
"the lend must end with the park, not outlive it"
);
assert_eq!(
Some(memory.view(&store).size()),
report.size_before_grow.map(|before| before + Pages(1)),
"a grow through the lent store must be visible to its owner"
);
Ok(())
}
#[engine_test]
fn parks_nest() -> Result<(), String> {
let mut store = Store::default();
let env = FunctionEnv::new(&mut store, Vec::<bool>::new());
let host =
Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut<Vec<bool>>| {
let mut lends = Vec::new();
env.as_store_mut().parked(|| {
Store::with_current(|lent| {
lends.push(Store::with_current(|_| ()).is_some());
lent.parked(|| lends.push(Store::with_current(|_| ()).is_some()));
lends.push(Store::with_current(|_| ()).is_some());
});
});
*env.data_mut() = lends;
});
run_host_function(&mut store, host)?;
let lends = env.as_ref(&store);
if lends.is_empty() {
return Ok(());
}
assert_eq!(
lends.as_slice(),
[false, true, false],
"a lent store is lendable again only while it is itself parked"
);
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
#[engine_test]
fn a_park_that_unwinds_gives_the_borrow_back() -> Result<(), String> {
use std::panic::{AssertUnwindSafe, catch_unwind};
let mut store = Store::default();
let env = FunctionEnv::new(&mut store, None::<bool>);
let host =
Function::new_typed_with_env(&mut store, &env, |mut env: FunctionEnvMut<Option<bool>>| {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let unwound = catch_unwind(AssertUnwindSafe(|| {
env.as_store_mut().parked(|| panic!("boom"))
}))
.is_err();
std::panic::set_hook(hook);
assert!(unwound, "the park must not swallow the panic");
*env.data_mut() = Some(Store::with_current(|_| ()).is_some());
});
run_host_function(&mut store, host)?;
assert_eq!(
*env.as_ref(&store),
Some(false),
"a store whose park unwound must not stay lendable"
);
Ok(())
}
#[engine_test]
fn an_idle_store_is_lent_only_once_parked() -> Result<(), String> {
let mut store = Store::default();
let store_id = store.id();
assert!(
Store::with_current(|_| ()).is_none(),
"an idle store is not lendable on its own"
);
let lent = store
.as_store_mut()
.parked(|| Store::with_current(|store| store.objects_mut().id()));
assert_eq!(lent, Some(store_id), "parking lends the store it parked");
assert!(
Store::with_current(|_| ()).is_none(),
"and the lend ends with the park"
);
Ok(())
}