Browse Source

Remove `unchecked_*` methods on `Rooted`s for getting `VMGcRef`s (#8948)

Because they take a shared borrow of the store and return a shared borrow of
the `VMGcRef` with the same lifetime, and because performing a GC requires a
mutable borrow of the store, there actually was no reason to differentiate
between checked and unchecked methods or require `AutoAssertNoGc` arguments.

Fixes https://github.com/bytecodealliance/wasmtime/issues/8940
pull/8952/head
Nick Fitzgerald 4 months ago
committed by GitHub
parent
commit
99b739fb3c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      crates/wasmtime/src/runtime/gc/enabled/anyref.rs
  2. 7
      crates/wasmtime/src/runtime/gc/enabled/externref.rs
  3. 40
      crates/wasmtime/src/runtime/gc/enabled/rooting.rs
  4. 2
      crates/wasmtime/src/runtime/gc/enabled/structref.rs
  5. 2
      crates/wasmtime/src/runtime/store.rs

14
crates/wasmtime/src/runtime/gc/enabled/anyref.rs

@ -253,7 +253,7 @@ impl AnyRef {
} }
pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<HeapType> { pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<HeapType> {
let gc_ref = self.inner.unchecked_try_gc_ref(store)?; let gc_ref = self.inner.try_gc_ref(store)?;
if gc_ref.is_i31() { if gc_ref.is_i31() {
return Ok(HeapType::I31); return Ok(HeapType::I31);
} }
@ -323,9 +323,7 @@ impl AnyRef {
pub(crate) fn _is_i31(&self, store: &StoreOpaque) -> Result<bool> { pub(crate) fn _is_i31(&self, store: &StoreOpaque) -> Result<bool> {
assert!(self.comes_from_same_store(store)); assert!(self.comes_from_same_store(store));
// NB: Can't use `AutoAssertNoGc` here because we only have a shared let gc_ref = self.inner.try_gc_ref(store)?;
// context, not a mutable context.
let gc_ref = self.inner.unchecked_try_gc_ref(store)?;
Ok(gc_ref.is_i31()) Ok(gc_ref.is_i31())
} }
@ -348,9 +346,7 @@ impl AnyRef {
pub(crate) fn _as_i31(&self, store: &StoreOpaque) -> Result<Option<I31>> { pub(crate) fn _as_i31(&self, store: &StoreOpaque) -> Result<Option<I31>> {
assert!(self.comes_from_same_store(store)); assert!(self.comes_from_same_store(store));
// NB: Can't use `AutoAssertNoGc` here because we only have a shared let gc_ref = self.inner.try_gc_ref(store)?;
// context, not a mutable context.
let gc_ref = self.inner.unchecked_try_gc_ref(store)?;
Ok(gc_ref.as_i31().map(Into::into)) Ok(gc_ref.as_i31().map(Into::into))
} }
@ -383,9 +379,7 @@ impl AnyRef {
} }
pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result<bool> { pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result<bool> {
// NB: Can't use `AutoAssertNoGc` here because we only have a shared let gc_ref = self.inner.try_gc_ref(store)?;
// context, not a mutable context.
let gc_ref = self.inner.unchecked_try_gc_ref(store)?;
Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef)) Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef))
} }

7
crates/wasmtime/src/runtime/gc/enabled/externref.rs

@ -319,7 +319,7 @@ impl ExternRef {
T: 'a, T: 'a,
{ {
let store = store.into().0; let store = store.into().0;
let gc_ref = self.inner.unchecked_try_gc_ref(&store)?; let gc_ref = self.inner.try_gc_ref(&store)?;
let externref = gc_ref.as_externref_unchecked(); let externref = gc_ref.as_externref_unchecked();
Ok(store.gc_store()?.externref_host_data(externref)) Ok(store.gc_store()?.externref_host_data(externref))
} }
@ -359,7 +359,10 @@ impl ExternRef {
T: 'a, T: 'a,
{ {
let store = store.into().0; let store = store.into().0;
let gc_ref = self.inner.unchecked_try_gc_ref(store)?.unchecked_copy(); // NB: need to do an unchecked copy to release the borrow on the store
// so that we can get the store's GC store. But importantly we cannot
// trigger a GC while we are working with `gc_ref` here.
let gc_ref = self.inner.try_gc_ref(store)?.unchecked_copy();
let externref = gc_ref.as_externref_unchecked(); let externref = gc_ref.as_externref_unchecked();
Ok(store.gc_store_mut()?.externref_host_data_mut(externref)) Ok(store.gc_store_mut()?.externref_host_data_mut(externref))
} }

40
crates/wasmtime/src/runtime/gc/enabled/rooting.rs

@ -209,13 +209,23 @@ impl GcRootIndex {
self.store_id == store.id() self.store_id == store.id()
} }
/// Same as `RootedGcRefImpl::get_gc_ref` but doesn't check that the raw GC /// Same as `RootedGcRefImpl::get_gc_ref` but not associated with any
/// ref is only used during the scope of an `AutoAssertNoGc`. /// particular `T: GcRef`.
/// ///
/// It is up to callers to avoid triggering a GC while holding onto the /// We must avoid triggering a GC while holding onto the resulting raw
/// resulting raw `VMGcRef`. Failure to uphold this invariant is memory safe /// `VMGcRef` to avoid use-after-free bugs and similar. The `'a` lifetime
/// but will lead to general incorrectness such as panics and wrong results. /// threaded from the `store` to the result will normally prevent GCs
pub(crate) fn unchecked_get_gc_ref<'a>(&self, store: &'a StoreOpaque) -> Option<&'a VMGcRef> { /// statically, at compile time, since performing a GC requires a mutable
/// borrow of the store. However, if you call `VMGcRef::unchecked_copy` on
/// the resulting GC reference, then all bets are off and this invariant is
/// up to you to manually uphold. Failure to uphold this invariant is memory
/// safe but will lead to general incorrectness such as panics and wrong
/// results.
///
/// # Panics
///
/// Panics if `self` is not associated with the given store.
pub(crate) fn get_gc_ref<'a>(&self, store: &'a StoreOpaque) -> Option<&'a VMGcRef> {
assert!( assert!(
self.comes_from_same_store(store), self.comes_from_same_store(store),
"object used with wrong store" "object used with wrong store"
@ -236,27 +246,13 @@ impl GcRootIndex {
} }
} }
/// Same as `RootedGcRefImpl::get_gc_ref` but not associated with any /// Same as `get_gc_ref` but returns an error instead of `None` if
/// particular `T: GcRef`.
pub(crate) fn get_gc_ref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Option<&'a VMGcRef> {
self.unchecked_get_gc_ref(store)
}
/// Same as `unchecked_get_gc_ref` but returns an error instead of `None` if
/// the GC reference has been unrooted. /// the GC reference has been unrooted.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `self` is not associated with the given store. /// Panics if `self` is not associated with the given store.
pub(crate) fn unchecked_try_gc_ref<'a>(&self, store: &'a StoreOpaque) -> Result<&'a VMGcRef> { pub(crate) fn try_gc_ref<'a>(&self, store: &'a StoreOpaque) -> Result<&'a VMGcRef> {
self.unchecked_get_gc_ref(store).ok_or_else(|| {
anyhow!("attempted to use a garbage-collected object that has been unrooted")
})
}
/// Same as `get_gc_ref` but returns an error instead of `None` if the GC
/// reference has been unrooted.
pub(crate) fn try_gc_ref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcRef> {
self.get_gc_ref(store).ok_or_else(|| { self.get_gc_ref(store).ok_or_else(|| {
anyhow!("attempted to use a garbage-collected object that has been unrooted") anyhow!("attempted to use a garbage-collected object that has been unrooted")
}) })

2
crates/wasmtime/src/runtime/gc/enabled/structref.rs

@ -534,7 +534,7 @@ impl StructRef {
} }
pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> { pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
let gc_ref = self.inner.unchecked_try_gc_ref(store)?; let gc_ref = self.inner.try_gc_ref(store)?;
let header = store.gc_store()?.header(gc_ref); let header = store.gc_store()?.header(gc_ref);
debug_assert!(header.kind().matches(VMGcKind::StructRef)); debug_assert!(header.kind().matches(VMGcKind::StructRef));
Ok(header.ty().expect("structrefs should have concrete types")) Ok(header.ty().expect("structrefs should have concrete types"))

2
crates/wasmtime/src/runtime/store.rs

@ -2614,7 +2614,7 @@ unsafe impl<T> crate::runtime::vm::Store for StoreInner<T> {
None => None, None => None,
Some(r) => { Some(r) => {
let r = r let r = r
.unchecked_get_gc_ref(store) .get_gc_ref(store)
.expect("still in scope") .expect("still in scope")
.unchecked_copy(); .unchecked_copy();
Some(store.gc_store_mut()?.clone_gc_ref(&r)) Some(store.gc_store_mut()?.clone_gc_ref(&r))

Loading…
Cancel
Save