Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 49f24c1

Browse files
committed
fix memory leak when panicking with detached ComponentInstance
In `ComponentInstance::from_vmctx` and `StoreContextMut::with_detached_instance[_async]` we were leaking memory due to having taken `InstanceData` out of the `Store` and making it unreachable except via a raw pointer, meaning there was nothing responsible for dropping it on panic. This commit stashes the data elsewhere in the store to make sure it gets dropped. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 90e7e16 commit 49f24c1

4 files changed

Lines changed: 50 additions & 42 deletions

File tree

crates/wasmtime/src/runtime/component/concurrent.rs

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -715,21 +715,14 @@ impl<T> StoreContextMut<'_, T> {
715715
}
716716
_ => unreachable!(),
717717
}));
718-
let data = self.0[instance.0].take().unwrap();
719-
let (result, data) = {
720-
// SAFETY: We've taken the instance out of the store, so now we own
721-
// it and can take an exclusive reference to it.
722-
let instance = unsafe { &mut *data.instance_ptr() };
723-
assert!(instance.data.is_none());
724-
instance.data = Some(data);
725-
instance.set_store(None);
726-
let result = fun(self.as_context_mut(), instance);
727-
instance.set_store(Some(VMStoreRawPtr(self.traitobj())));
728-
(result, instance.data.take())
729-
};
730-
if self.0[instance.0].is_none() {
731-
self.0[instance.0] = data;
732-
}
718+
let ptr = self.0.hide_instance(*instance);
719+
// SAFETY: We've taken the instance out of the store, so now we own
720+
// it and can take an exclusive reference to it.
721+
let reference = unsafe { &mut *ptr };
722+
reference.set_store(None);
723+
let result = fun(self.as_context_mut(), reference);
724+
reference.set_store(Some(VMStoreRawPtr(self.traitobj())));
725+
self.0.unhide_instance(*instance);
733726
result
734727
}
735728

@@ -746,21 +739,14 @@ impl<T> StoreContextMut<'_, T> {
746739
}
747740
_ => unreachable!(),
748741
}));
749-
let data = self.0[instance.0].take().unwrap();
750-
let (result, data) = {
751-
// SAFETY: We've taken the instance out of the store, so now we own
752-
// it and can take an exclusive reference to it.
753-
let instance = unsafe { &mut *data.instance_ptr() };
754-
assert!(instance.data.is_none());
755-
instance.data = Some(data);
756-
instance.set_store(None);
757-
let result = fun(self.as_context_mut(), instance).await;
758-
instance.set_store(Some(VMStoreRawPtr(self.traitobj())));
759-
(result, instance.data.take())
760-
};
761-
if self.0[instance.0].is_none() {
762-
self.0[instance.0] = data;
763-
}
742+
let ptr = self.0.hide_instance(*instance);
743+
// SAFETY: We've taken the instance out of the store, so now we own
744+
// it and can take an exclusive reference to it.
745+
let reference = unsafe { &mut *ptr };
746+
reference.set_store(None);
747+
let result = fun(self.as_context_mut(), reference).await;
748+
reference.set_store(Some(VMStoreRawPtr(self.traitobj())));
749+
self.0.unhide_instance(*instance);
764750
result
765751
}
766752

@@ -783,13 +769,13 @@ impl<T> StoreContextMut<'_, T> {
783769
_ => unreachable!(),
784770
}));
785771
if let Some(handle) = instance.instance {
786-
self.0[handle.0] = Some(instance.data.take().unwrap());
772+
self.0.unhide_instance(handle);
787773
}
788774
instance.set_store(Some(VMStoreRawPtr(self.traitobj())));
789775
let result = fun(self.as_context_mut(), instance.instance);
790776
instance.set_store(None);
791777
if let Some(handle) = instance.instance {
792-
instance.data = Some(self.0[handle.0].take().unwrap());
778+
self.0.hide_instance(handle);
793779
}
794780
result
795781
}

crates/wasmtime/src/runtime/store.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ pub struct StoreOpaque {
410410
#[cfg(feature = "component-model-async")]
411411
concurrent_async_state: concurrent::AsyncState,
412412

413+
#[cfg(feature = "component-model")]
414+
hidden_instances: Vec<Option<Box<crate::component::InstanceData>>>,
415+
413416
/// State related to the executor of wasm code.
414417
///
415418
/// For example if Pulley is enabled and configured then this will store a
@@ -587,6 +590,8 @@ impl<T: 'static> Store<T> {
587590
component_calls: Default::default(),
588591
#[cfg(feature = "component-model")]
589592
host_resource_data: Default::default(),
593+
#[cfg(feature = "component-model")]
594+
hidden_instances: Default::default(),
590595
#[cfg(feature = "component-model-async")]
591596
concurrent_async_state: Default::default(),
592597
#[cfg(has_host_compiler_backend)]
@@ -1183,6 +1188,29 @@ impl StoreOpaque {
11831188
self.store_data.id()
11841189
}
11851190

1191+
#[cfg(feature = "component-model")]
1192+
pub(crate) fn hide_instance(
1193+
&mut self,
1194+
handle: crate::component::Instance,
1195+
) -> *mut crate::vm::component::ComponentInstance {
1196+
if self.hidden_instances.len() <= handle.0.index() {
1197+
self.hidden_instances
1198+
.resize_with(handle.0.index() + 1, || None);
1199+
}
1200+
let data = self[handle.0].take().unwrap();
1201+
let ptr = data.instance_ptr();
1202+
self.hidden_instances[handle.0.index()] = Some(data);
1203+
ptr
1204+
}
1205+
1206+
#[cfg(feature = "component-model")]
1207+
pub(crate) fn unhide_instance(&mut self, handle: crate::component::Instance) {
1208+
if let Some(data) = self.hidden_instances[handle.0.index()].take() {
1209+
assert!(self[handle.0].is_none());
1210+
self[handle.0] = Some(data);
1211+
}
1212+
}
1213+
11861214
pub fn bump_resource_counts(&mut self, module: &Module) -> Result<()> {
11871215
fn bump(slot: &mut usize, max: usize, amt: usize, desc: &str) -> Result<()> {
11881216
let new = slot.saturating_add(amt);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ impl<T> Stored<T> {
296296
self.store_id.assert_belongs_to(store)
297297
}
298298

299-
fn index(&self) -> usize {
299+
pub(crate) fn index(&self) -> usize {
300300
self.index
301301
}
302302
}

crates/wasmtime/src/runtime/vm/component.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! Eventually it's intended that module-to-module calls, which would be
77
//! cranelift-compiled adapters, will use this `VMComponentContext` as well.
88
9-
use crate::component::{Instance, InstanceData, ResourceType};
9+
use crate::component::{Instance, ResourceType};
1010
use crate::prelude::*;
1111
use crate::runtime::vm::{
1212
SendSyncPtr, VMArrayCallFunction, VMContext, VMFuncRef, VMGlobalDefinition, VMMemoryDefinition,
@@ -78,8 +78,6 @@ pub struct ComponentInstance {
7878

7979
pub(crate) instance: Option<Instance>,
8080

81-
pub(crate) data: Option<Box<InstanceData>>,
82-
8381
/// A zero-sized field which represents the end of the struct for the actual
8482
/// `VMComponentContext` to be allocated behind.
8583
vmctx: VMComponentContext,
@@ -189,16 +187,13 @@ impl ComponentInstance {
189187
let reference = ptr.as_mut();
190188
let store = &mut *reference.store();
191189
if let Some(instance) = reference.instance {
192-
assert!(reference.data.is_none());
193-
reference.data = Some(store[instance.0].take().unwrap());
190+
store.hide_instance(instance);
194191
}
195192
reference.set_store(None);
196193
let result = f(store, reference);
197194
reference.set_store(Some(VMStoreRawPtr(store.traitobj())));
198195
if let Some(instance) = reference.instance {
199-
if store[instance.0].is_none() {
200-
store[instance.0] = reference.data.take();
201-
}
196+
store.unhide_instance(instance);
202197
}
203198
result
204199
}
@@ -265,7 +260,6 @@ impl ComponentInstance {
265260
vmctx: VMComponentContext {
266261
_marker: marker::PhantomPinned,
267262
},
268-
data: None,
269263
instance: None,
270264
#[cfg(feature = "component-model-async")]
271265
concurrent_state,

0 commit comments

Comments
 (0)