Browse Source

Update may_enter flag handling in components (#4354)

This commit updates the management of the `may_enter` flag in line with
WebAssembly/component-model#57. Namely the `may_enter` flag is now
exclusively managed in the `canon lift` function (which is
`TypedFunc::call`) and is only unset after post-return completes
successfully. This implements semantics where if any trap happens for
any reason (lifting, lowering, execution, imports, etc) then the
instance is considered permanently poisoned and can no longer be
entered.

Tests needed many updates to create new instances where previously the
same instance was reused after it had an erroneous state.
pull/4355/head
Alex Crichton 2 years ago
committed by GitHub
parent
commit
e179e736b9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      crates/wasmtime/src/component/func/host.rs
  2. 65
      crates/wasmtime/src/component/func/typed.rs
  3. 188
      tests/all/component_model/func.rs
  4. 14
      tests/all/component_model/import.rs
  5. 43
      tests/all/component_model/post_return.rs

33
crates/wasmtime/src/component/func/host.rs

@ -154,10 +154,6 @@ where
bail!("cannot leave component instance"); bail!("cannot leave component instance");
} }
// While we're lifting and lowering this instance cannot be reentered, so
// unset the flag here. This is also reset back to `true` on exit.
let _reset_may_enter = unset_and_reset_on_drop(flags, VMComponentFlags::set_may_enter);
// There's a 2x2 matrix of whether parameters and results are stored on the // There's a 2x2 matrix of whether parameters and results are stored on the
// stack or on the heap. Each of the 4 branches here have a different // stack or on the heap. Each of the 4 branches here have a different
// representation of the storage of arguments/returns which is represented // representation of the storage of arguments/returns which is represented
@ -168,13 +164,12 @@ where
// trivially DCE'd by LLVM. Perhaps one day with enough const programming in // trivially DCE'd by LLVM. Perhaps one day with enough const programming in
// Rust we can make monomorphizations of this function codegen only one // Rust we can make monomorphizations of this function codegen only one
// branch, but today is not that day. // branch, but today is not that day.
let reset_may_leave;
if Params::flatten_count() <= MAX_STACK_PARAMS { if Params::flatten_count() <= MAX_STACK_PARAMS {
if Return::flatten_count() <= MAX_STACK_RESULTS { if Return::flatten_count() <= MAX_STACK_RESULTS {
let storage = cast_storage::<ReturnStack<Params::Lower, Return::Lower>>(storage); let storage = cast_storage::<ReturnStack<Params::Lower, Return::Lower>>(storage);
let params = Params::lift(cx.0, &options, &storage.assume_init_ref().args)?; let params = Params::lift(cx.0, &options, &storage.assume_init_ref().args)?;
let ret = closure(cx.as_context_mut(), params)?; let ret = closure(cx.as_context_mut(), params)?;
reset_may_leave = unset_and_reset_on_drop(flags, VMComponentFlags::set_may_leave); (*flags).set_may_leave(false);
ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?; ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?;
} else { } else {
let storage = cast_storage::<ReturnPointer<Params::Lower>>(storage).assume_init_ref(); let storage = cast_storage::<ReturnPointer<Params::Lower>>(storage).assume_init_ref();
@ -182,7 +177,7 @@ where
let ret = closure(cx.as_context_mut(), params)?; let ret = closure(cx.as_context_mut(), params)?;
let mut memory = MemoryMut::new(cx.as_context_mut(), &options); let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?; let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?;
reset_may_leave = unset_and_reset_on_drop(flags, VMComponentFlags::set_may_leave); (*flags).set_may_leave(false);
ret.store(&mut memory, ptr)?; ret.store(&mut memory, ptr)?;
} }
} else { } else {
@ -193,7 +188,7 @@ where
validate_inbounds::<Params>(memory.as_slice(), &storage.assume_init_ref().args)?; validate_inbounds::<Params>(memory.as_slice(), &storage.assume_init_ref().args)?;
let params = Params::load(&memory, &memory.as_slice()[ptr..][..Params::SIZE32])?; let params = Params::load(&memory, &memory.as_slice()[ptr..][..Params::SIZE32])?;
let ret = closure(cx.as_context_mut(), params)?; let ret = closure(cx.as_context_mut(), params)?;
reset_may_leave = unset_and_reset_on_drop(flags, VMComponentFlags::set_may_leave); (*flags).set_may_leave(false);
ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?; ret.lower(&mut cx, &options, map_maybe_uninit!(storage.ret))?;
} else { } else {
let storage = cast_storage::<ReturnPointer<ValRaw>>(storage).assume_init_ref(); let storage = cast_storage::<ReturnPointer<ValRaw>>(storage).assume_init_ref();
@ -202,32 +197,14 @@ where
let ret = closure(cx.as_context_mut(), params)?; let ret = closure(cx.as_context_mut(), params)?;
let mut memory = MemoryMut::new(cx.as_context_mut(), &options); let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?; let ptr = validate_inbounds::<Return>(memory.as_slice_mut(), &storage.retptr)?;
reset_may_leave = unset_and_reset_on_drop(flags, VMComponentFlags::set_may_leave); (*flags).set_may_leave(false);
ret.store(&mut memory, ptr)?; ret.store(&mut memory, ptr)?;
} }
} }
drop(reset_may_leave); (*flags).set_may_leave(true);
return Ok(()); return Ok(());
unsafe fn unset_and_reset_on_drop(
slot: *mut VMComponentFlags,
set: fn(&mut VMComponentFlags, bool),
) -> impl Drop {
set(&mut *slot, false);
return Reset(slot, set);
struct Reset(*mut VMComponentFlags, fn(&mut VMComponentFlags, bool));
impl Drop for Reset {
fn drop(&mut self) {
unsafe {
(self.1)(&mut *self.0, true);
}
}
}
}
} }
fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> {

65
crates/wasmtime/src/component/func/typed.rs

@ -333,11 +333,19 @@ where
let flags = instance.flags(component_instance); let flags = instance.flags(component_instance);
unsafe { unsafe {
// Test the "may enter" flag which is a "lock" on this instance.
// This is immediately set to `false` afterwards and note that
// there's no on-cleanup setting this flag back to true. That's an
// intentional design aspect where if anything goes wrong internally
// from this point on the instance is considered "poisoned" and can
// never be entered again. The only time this flag is set to `true`
// again is after post-return logic has completed successfully.
if !(*flags).may_enter() { if !(*flags).may_enter() {
bail!("cannot reenter component instance"); bail!("cannot reenter component instance");
} }
debug_assert!((*flags).may_leave()); (*flags).set_may_enter(false);
debug_assert!((*flags).may_leave());
(*flags).set_may_leave(false); (*flags).set_may_leave(false);
let result = lower(store, &options, params, map_maybe_uninit!(space.params)); let result = lower(store, &options, params, map_maybe_uninit!(space.params));
(*flags).set_may_leave(true); (*flags).set_may_leave(true);
@ -370,25 +378,12 @@ where
// Lift the result into the host while managing post-return state // Lift the result into the host while managing post-return state
// here as well. // here as well.
// //
// Initially the `may_enter` flag is set to `false` for this // After a successful lift the return value of the function, which
// component instance and additionally we set a flag indicating that // is currently required to be 0 or 1 values according to the
// a post-return is required. This isn't specified by the component // canonical ABI, is saved within the `Store`'s `FuncData`. This'll
// model itself but is used for our implementation of the API of // later get used in post-return.
// `post_return` as a separate function call.
//
// FIXME(WebAssembly/component-model#55) it's not really clear what
// the semantics should be in the face of a lift error/trap. For now
// the flags are reset so the instance can continue to be reused in
// tests but that probably isn't what's desired.
//
// Otherwise though after a successful lift the return value of the
// function, which is currently required to be 0 or 1 values
// according to the canonical ABI, is saved within the `Store`'s
// `FuncData`. This'll later get used in post-return.
(*flags).set_may_enter(false);
(*flags).set_needs_post_return(true); (*flags).set_needs_post_return(true);
match lift(store.0, &options, ret) { let val = lift(store.0, &options, ret)?;
Ok(val) => {
let ret_slice = cast_storage(ret); let ret_slice = cast_storage(ret);
let data = &mut store.0[self.func.0]; let data = &mut store.0[self.func.0];
assert!(data.post_return_arg.is_none()); assert!(data.post_return_arg.is_none());
@ -399,13 +394,6 @@ where
} }
return Ok(val); return Ok(val);
} }
Err(err) => {
(*flags).set_may_enter(true);
(*flags).set_needs_post_return(false);
return Err(err);
}
}
}
unsafe fn cast_storage<T>(storage: &T) -> &[ValRaw] { unsafe fn cast_storage<T>(storage: &T) -> &[ValRaw] {
assert!(std::mem::size_of_val(storage) % std::mem::size_of::<ValRaw>() == 0); assert!(std::mem::size_of_val(storage) % std::mem::size_of::<ValRaw>() == 0);
@ -480,17 +468,18 @@ where
// This is a sanity-check assert which shouldn't ever trip. // This is a sanity-check assert which shouldn't ever trip.
assert!(!(*flags).may_enter()); assert!(!(*flags).may_enter());
// With the state of the world validated these flags are updated to // Unset the "needs post return" flag now that post-return is being
// their component-model-defined states. // processed. This will cause future invocations of this method to
(*flags).set_may_enter(true); // panic, even if the function call below traps.
(*flags).set_needs_post_return(false); (*flags).set_needs_post_return(false);
// And finally if the function actually had a `post-return` // If the function actually had a `post-return` configured in its
// configured in its canonical options that's executed here. // canonical options that's executed here.
let (func, trampoline) = match post_return { //
Some(pair) => pair, // Note that if this traps (returns an error) this function
None => return Ok(()), // intentionally leaves the instance in a "poisoned" state where it
}; // can no longer be entered because `may_enter` is `false`.
if let Some((func, trampoline)) = post_return {
crate::Func::call_unchecked_raw( crate::Func::call_unchecked_raw(
&mut store, &mut store,
func.anyfunc, func.anyfunc,
@ -498,6 +487,12 @@ where
&post_return_arg as *const ValRaw as *mut ValRaw, &post_return_arg as *const ValRaw as *mut ValRaw,
)?; )?;
} }
// And finally if everything completed successfully then the "may
// enter" flag is set to `true` again here which enables further use
// of the component.
(*flags).set_may_enter(true);
}
Ok(()) Ok(())
} }
} }

188
tests/all/component_model/func.rs

@ -188,7 +188,8 @@ fn integers() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let new_instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
let instance = new_instance(&mut store)?;
// Passing in 100 is valid for all primitives // Passing in 100 is valid for all primitives
instance instance
@ -217,42 +218,42 @@ fn integers() -> Result<()> {
.call_and_post_return(&mut store, (100,))?; .call_and_post_return(&mut store, (100,))?;
// This specific wasm instance traps if any value other than 100 is passed // This specific wasm instance traps if any value other than 100 is passed
instance new_instance(&mut store)?
.get_typed_func::<(u8,), (), _>(&mut store, "take-u8")? .get_typed_func::<(u8,), (), _>(&mut store, "take-u8")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(i8,), (), _>(&mut store, "take-s8")? .get_typed_func::<(i8,), (), _>(&mut store, "take-s8")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(u16,), (), _>(&mut store, "take-u16")? .get_typed_func::<(u16,), (), _>(&mut store, "take-u16")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(i16,), (), _>(&mut store, "take-s16")? .get_typed_func::<(i16,), (), _>(&mut store, "take-s16")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(u32,), (), _>(&mut store, "take-u32")? .get_typed_func::<(u32,), (), _>(&mut store, "take-u32")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(i32,), (), _>(&mut store, "take-s32")? .get_typed_func::<(i32,), (), _>(&mut store, "take-s32")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(u64,), (), _>(&mut store, "take-u64")? .get_typed_func::<(u64,), (), _>(&mut store, "take-u64")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
instance new_instance(&mut store)?
.get_typed_func::<(i64,), (), _>(&mut store, "take-s64")? .get_typed_func::<(i64,), (), _>(&mut store, "take-s64")?
.call(&mut store, (101,)) .call(&mut store, (101,))
.unwrap_err() .unwrap_err()
@ -606,13 +607,26 @@ fn chars() -> Result<()> {
roundtrip('\n')?; roundtrip('\n')?;
roundtrip('💝')?; roundtrip('💝')?;
let err = u32_to_char.call(&mut store, (0xd800,)).unwrap_err(); let u32_to_char = |store: &mut Store<()>| {
Linker::new(&engine)
.instantiate(&mut *store, &component)?
.get_typed_func::<(u32,), char, _>(&mut *store, "u32-to-char")
};
let err = u32_to_char(&mut store)?
.call(&mut store, (0xd800,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err); assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (0xdfff,)).unwrap_err(); let err = u32_to_char(&mut store)?
.call(&mut store, (0xdfff,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err); assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (0x110000,)).unwrap_err(); let err = u32_to_char(&mut store)?
.call(&mut store, (0x110000,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err); assert!(err.to_string().contains("integer out of range"), "{}", err);
let err = u32_to_char.call(&mut store, (u32::MAX,)).unwrap_err(); let err = u32_to_char(&mut store)?
.call(&mut store, (u32::MAX,))
.unwrap_err();
assert!(err.to_string().contains("integer out of range"), "{}", err); assert!(err.to_string().contains("integer out of range"), "{}", err);
Ok(()) Ok(())
@ -1068,10 +1082,10 @@ fn some_traps() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
// This should fail when calling the allocator function for the argument // This should fail when calling the allocator function for the argument
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-unreachable")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-unreachable")?
.call(&mut store, (&[],)) .call(&mut store, (&[],))
.unwrap_err() .unwrap_err()
@ -1079,7 +1093,7 @@ fn some_traps() -> Result<()> {
assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached)); assert_eq!(err.trap_code(), Some(TrapCode::UnreachableCodeReached));
// This should fail when calling the allocator function for the argument // This should fail when calling the allocator function for the argument
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-unreachable")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-unreachable")?
.call(&mut store, ("",)) .call(&mut store, ("",))
.unwrap_err() .unwrap_err()
@ -1088,7 +1102,7 @@ fn some_traps() -> Result<()> {
// This should fail when calling the allocator function for the space // This should fail when calling the allocator function for the space
// to store the arguments (before arguments are even lowered) // to store the arguments (before arguments are even lowered)
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store, &mut store,
"take-many-unreachable", "take-many-unreachable",
@ -1113,27 +1127,27 @@ fn some_traps() -> Result<()> {
err, err,
); );
} }
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
.call(&mut store, (&[],)) .call(&mut store, (&[],))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-base-oob")?
.call(&mut store, (&[1],)) .call(&mut store, (&[1],))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
.call(&mut store, ("",)) .call(&mut store, ("",))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-base-oob")?
.call(&mut store, ("x",)) .call(&mut store, ("x",))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store, &mut store,
"take-many-base-oob", "take-many-base-oob",
@ -1145,29 +1159,29 @@ fn some_traps() -> Result<()> {
// Test here that when the returned pointer from malloc is one byte from the // Test here that when the returned pointer from malloc is one byte from the
// end of memory that empty things are fine, but larger things are not. // end of memory that empty things are fine, but larger things are not.
instance instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call_and_post_return(&mut store, (&[],))?; .call_and_post_return(&mut store, (&[],))?;
instance instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call_and_post_return(&mut store, (&[1, 2, 3, 4],))?; .call_and_post_return(&mut store, (&[1, 2, 3, 4],))?;
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[1, 2, 3, 4, 5],)) .call(&mut store, (&[1, 2, 3, 4, 5],))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
instance instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call_and_post_return(&mut store, ("",))?; .call_and_post_return(&mut store, ("",))?;
instance instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call_and_post_return(&mut store, ("abcd",))?; .call_and_post_return(&mut store, ("abcd",))?;
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("abcde",)) .call(&mut store, ("abcde",))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store, &mut store,
"take-many-end-oob", "take-many-end-oob",
@ -1179,7 +1193,7 @@ fn some_traps() -> Result<()> {
// For this function the first allocation, the space to store all the // For this function the first allocation, the space to store all the
// arguments, is in-bounds but then all further allocations, such as for // arguments, is in-bounds but then all further allocations, such as for
// each individual string, are all out of bounds. // each individual string, are all out of bounds.
let err = instance let err = instance(&mut store)?
.get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>( .get_typed_func::<(&str, &str, &str, &str, &str, &str, &str, &str, &str, &str), (), _>(
&mut store, &mut store,
"take-many-second-oob", "take-many-second-oob",
@ -1304,9 +1318,12 @@ fn string_list_oob() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let ret_list_u8 = Linker::new(&engine)
let ret_list_u8 = instance.get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?; .instantiate(&mut store, &component)?
let ret_string = instance.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?; .get_typed_func::<(), WasmList<u8>, _>(&mut store, "ret-list-u8")?;
let ret_string = Linker::new(&engine)
.instantiate(&mut store, &component)?
.get_typed_func::<(), WasmStr, _>(&mut store, "ret-string")?;
let err = ret_list_u8.call(&mut store, ()).err().unwrap(); let err = ret_list_u8.call(&mut store, ()).err().unwrap();
assert!(err.to_string().contains("out of bounds"), "{}", err); assert!(err.to_string().contains("out of bounds"), "{}", err);
@ -1460,7 +1477,8 @@ fn option() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
let option_unit_to_u32 = let option_unit_to_u32 =
instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?; instance.get_typed_func::<(Option<()>,), u32, _>(&mut store, "option-unit-to-u32")?;
assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0); assert_eq!(option_unit_to_u32.call(&mut store, (None,))?, 0);
@ -1506,6 +1524,7 @@ fn option() -> Result<()> {
assert_eq!(b.to_str(&store)?, "hello"); assert_eq!(b.to_str(&store)?, "hello");
option_string_to_tuple.post_return(&mut store)?; option_string_to_tuple.post_return(&mut store)?;
let instance = linker.instantiate(&mut store, &component)?;
let to_option_unit = let to_option_unit =
instance.get_typed_func::<(u32,), Option<()>, _>(&mut store, "to-option-unit")?; instance.get_typed_func::<(u32,), Option<()>, _>(&mut store, "to-option-unit")?;
assert_eq!(to_option_unit.call(&mut store, (0,))?, None); assert_eq!(to_option_unit.call(&mut store, (0,))?, None);
@ -1515,6 +1534,7 @@ fn option() -> Result<()> {
let err = to_option_unit.call(&mut store, (2,)).unwrap_err(); let err = to_option_unit.call(&mut store, (2,)).unwrap_err();
assert!(err.to_string().contains("invalid option"), "{}", err); assert!(err.to_string().contains("invalid option"), "{}", err);
let instance = linker.instantiate(&mut store, &component)?;
let to_option_u8 = let to_option_u8 =
instance.get_typed_func::<(u32, u32), Option<u8>, _>(&mut store, "to-option-u8")?; instance.get_typed_func::<(u32, u32), Option<u8>, _>(&mut store, "to-option-u8")?;
assert_eq!(to_option_u8.call(&mut store, (0x00_00, 0))?, None); assert_eq!(to_option_u8.call(&mut store, (0x00_00, 0))?, None);
@ -1525,6 +1545,7 @@ fn option() -> Result<()> {
to_option_u8.post_return(&mut store)?; to_option_u8.post_return(&mut store)?;
assert!(to_option_u8.call(&mut store, (0x00_02, 0)).is_err()); assert!(to_option_u8.call(&mut store, (0x00_02, 0)).is_err());
let instance = linker.instantiate(&mut store, &component)?;
let to_option_u32 = let to_option_u32 =
instance.get_typed_func::<(u32, u32), Option<u32>, _>(&mut store, "to-option-u32")?; instance.get_typed_func::<(u32, u32), Option<u32>, _>(&mut store, "to-option-u32")?;
assert_eq!(to_option_u32.call(&mut store, (0, 0))?, None); assert_eq!(to_option_u32.call(&mut store, (0, 0))?, None);
@ -1538,6 +1559,7 @@ fn option() -> Result<()> {
to_option_u32.post_return(&mut store)?; to_option_u32.post_return(&mut store)?;
assert!(to_option_u32.call(&mut store, (2, 0)).is_err()); assert!(to_option_u32.call(&mut store, (2, 0)).is_err());
let instance = linker.instantiate(&mut store, &component)?;
let to_option_string = instance let to_option_string = instance
.get_typed_func::<(u32, &str), Option<WasmStr>, _>(&mut store, "to-option-string")?; .get_typed_func::<(u32, &str), Option<WasmStr>, _>(&mut store, "to-option-string")?;
let ret = to_option_string.call(&mut store, (0, ""))?; let ret = to_option_string.call(&mut store, (0, ""))?;
@ -1637,7 +1659,8 @@ fn expected() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
let take_expected_unit = let take_expected_unit =
instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?; instance.get_typed_func::<(Result<(), ()>,), u32, _>(&mut store, "take-expected-unit")?;
assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0); assert_eq!(take_expected_unit.call(&mut store, (Ok(()),))?, 0);
@ -1669,6 +1692,7 @@ fn expected() -> Result<()> {
assert_eq!(b.to_str(&store)?, "goodbye"); assert_eq!(b.to_str(&store)?, "goodbye");
take_expected_string.post_return(&mut store)?; take_expected_string.post_return(&mut store)?;
let instance = linker.instantiate(&mut store, &component)?;
let to_expected_unit = let to_expected_unit =
instance.get_typed_func::<(u32,), Result<(), ()>, _>(&mut store, "to-expected-unit")?; instance.get_typed_func::<(u32,), Result<(), ()>, _>(&mut store, "to-expected-unit")?;
assert_eq!(to_expected_unit.call(&mut store, (0,))?, Ok(())); assert_eq!(to_expected_unit.call(&mut store, (0,))?, Ok(()));
@ -1678,6 +1702,7 @@ fn expected() -> Result<()> {
let err = to_expected_unit.call(&mut store, (2,)).unwrap_err(); let err = to_expected_unit.call(&mut store, (2,)).unwrap_err();
assert!(err.to_string().contains("invalid expected"), "{}", err); assert!(err.to_string().contains("invalid expected"), "{}", err);
let instance = linker.instantiate(&mut store, &component)?;
let to_expected_s16_f32 = instance let to_expected_s16_f32 = instance
.get_typed_func::<(u32, u32), Result<i16, f32>, _>(&mut store, "to-expected-s16-f32")?; .get_typed_func::<(u32, u32), Result<i16, f32>, _>(&mut store, "to-expected-s16-f32")?;
assert_eq!(to_expected_s16_f32.call(&mut store, (0, 0))?, Ok(0)); assert_eq!(to_expected_s16_f32.call(&mut store, (0, 0))?, Ok(0));
@ -1869,9 +1894,9 @@ fn invalid_alignment() -> Result<()> {
let engine = super::engine(); let engine = super::engine();
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?; let instance = |store: &mut Store<()>| Linker::new(&engine).instantiate(store, &component);
let err = instance let err = instance(&mut store)?
.get_typed_func::<( .get_typed_func::<(
&str, &str,
&str, &str,
@ -1895,7 +1920,7 @@ fn invalid_alignment() -> Result<()> {
err err
); );
let err = instance let err = instance(&mut store)?
.get_typed_func::<(), WasmStr, _>(&mut store, "string-ret")? .get_typed_func::<(), WasmStr, _>(&mut store, "string-ret")?
.call(&mut store, ()) .call(&mut store, ())
.err() .err()
@ -1906,7 +1931,7 @@ fn invalid_alignment() -> Result<()> {
err err
); );
let err = instance let err = instance(&mut store)?
.get_typed_func::<(), WasmList<u32>, _>(&mut store, "list-u32-ret")? .get_typed_func::<(), WasmList<u32>, _>(&mut store, "list-u32-ret")?
.call(&mut store, ()) .call(&mut store, ())
.err() .err()
@ -2253,3 +2278,88 @@ fn lower_then_lift() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn errors_that_poison_instance() -> Result<()> {
let component = format!(
r#"
(component $c
(core module $m1
(func (export "f1") unreachable)
(func (export "f2"))
)
(core instance $m1 (instantiate $m1))
(func (export "f1") (canon lift (core func $m1 "f1")))
(func (export "f2") (canon lift (core func $m1 "f2")))
(core module $m2
(func (export "f") (param i32 i32))
(func (export "r") (param i32 i32 i32 i32) (result i32) unreachable)
(memory (export "m") 1)
)
(core instance $m2 (instantiate $m2))
(func (export "f3") (param string)
(canon lift (core func $m2 "f") (realloc (func $m2 "r")) (memory $m2 "m"))
)
(core module $m3
(func (export "f") (result i32) i32.const 1)
(memory (export "m") 1)
)
(core instance $m3 (instantiate $m3))
(func (export "f4") (result string)
(canon lift (core func $m3 "f") (memory $m3 "m"))
)
)
"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let linker = Linker::new(&engine);
let instance = linker.instantiate(&mut store, &component)?;
let f1 = instance.get_typed_func::<(), (), _>(&mut store, "f1")?;
let f2 = instance.get_typed_func::<(), (), _>(&mut store, "f2")?;
assert_unreachable(f1.call(&mut store, ()));
assert_poisoned(f1.call(&mut store, ()));
assert_poisoned(f2.call(&mut store, ()));
let instance = linker.instantiate(&mut store, &component)?;
let f3 = instance.get_typed_func::<(&str,), (), _>(&mut store, "f3")?;
assert_unreachable(f3.call(&mut store, ("x",)));
assert_poisoned(f3.call(&mut store, ("x",)));
let instance = linker.instantiate(&mut store, &component)?;
let f4 = instance.get_typed_func::<(), WasmStr, _>(&mut store, "f4")?;
assert!(f4.call(&mut store, ()).is_err());
assert_poisoned(f4.call(&mut store, ()));
return Ok(());
#[track_caller]
fn assert_unreachable<T>(err: Result<T>) {
let err = match err {
Ok(_) => panic!("expected an error"),
Err(e) => e,
};
assert_eq!(
err.downcast::<Trap>().unwrap().trap_code(),
Some(TrapCode::UnreachableCodeReached)
);
}
#[track_caller]
fn assert_poisoned<T>(err: Result<T>) {
let err = match err {
Ok(_) => panic!("expected an error"),
Err(e) => e,
};
assert!(
err.to_string()
.contains("cannot reenter component instance"),
"{}",
err,
);
}
}

14
tests/all/component_model/import.rs

@ -221,11 +221,11 @@ fn attempt_to_leave_during_malloc() -> Result<()> {
})?; })?;
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
// Assert that during a host import if we return values to wasm that a trap // Assert that during a host import if we return values to wasm that a trap
// happens if we try to leave the instance. // happens if we try to leave the instance.
let trap = instance let trap = linker
.instantiate(&mut store, &component)?
.get_typed_func::<(), (), _>(&mut store, "run")? .get_typed_func::<(), (), _>(&mut store, "run")?
.call(&mut store, ()) .call(&mut store, ())
.unwrap_err() .unwrap_err()
@ -261,7 +261,8 @@ fn attempt_to_leave_during_malloc() -> Result<()> {
// In addition to the above trap also ensure that when we enter a wasm // In addition to the above trap also ensure that when we enter a wasm
// component if we try to leave while lowering then that's also a dynamic // component if we try to leave while lowering then that's also a dynamic
// trap. // trap.
let trap = instance let trap = linker
.instantiate(&mut store, &component)?
.get_typed_func::<(&str,), (), _>(&mut store, "take-string")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string")?
.call(&mut store, ("x",)) .call(&mut store, ("x",))
.unwrap_err() .unwrap_err()
@ -599,14 +600,15 @@ fn bad_import_alignment() -> Result<()> {
)?; )?;
let component = Component::new(&engine, component)?; let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ()); let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?; let trap = linker
let trap = instance .instantiate(&mut store, &component)?
.get_typed_func::<(), (), _>(&mut store, "unaligned-retptr")? .get_typed_func::<(), (), _>(&mut store, "unaligned-retptr")?
.call(&mut store, ()) .call(&mut store, ())
.unwrap_err() .unwrap_err()
.downcast::<Trap>()?; .downcast::<Trap>()?;
assert!(trap.to_string().contains("pointer not aligned"), "{}", trap); assert!(trap.to_string().contains("pointer not aligned"), "{}", trap);
let trap = instance let trap = linker
.instantiate(&mut store, &component)?
.get_typed_func::<(), (), _>(&mut store, "unaligned-argptr")? .get_typed_func::<(), (), _>(&mut store, "unaligned-argptr")?
.call(&mut store, ()) .call(&mut store, ())
.unwrap_err() .unwrap_err()

43
tests/all/component_model/post_return.rs

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use wasmtime::component::*; use wasmtime::component::*;
use wasmtime::{Store, StoreContextMut}; use wasmtime::{Store, StoreContextMut, Trap, TrapCode};
#[test] #[test]
fn invalid_api() -> Result<()> { fn invalid_api() -> Result<()> {
@ -257,3 +257,44 @@ fn post_return_string() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn trap_in_post_return_poisons_instance() -> Result<()> {
let component = r#"
(component
(core module $m
(func (export "f"))
(func (export "post") unreachable)
)
(core instance $i (instantiate $m))
(func (export "f")
(canon lift
(core func $i "f")
(post-return (func $i "post"))
)
)
)
"#;
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let f = instance.get_typed_func::<(), (), _>(&mut store, "f")?;
f.call(&mut store, ())?;
let trap = f.post_return(&mut store).unwrap_err().downcast::<Trap>()?;
assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached));
let err = f.call(&mut store, ()).unwrap_err();
assert!(
err.to_string()
.contains("cannot reenter component instance"),
"{}",
err
);
assert_panics(
|| drop(f.post_return(&mut store)),
"can only be called after",
);
Ok(())
}

Loading…
Cancel
Save