Revamp memory management of InstanceHandle (#1624)
* Revamp memory management of `InstanceHandle` This commit fixes a known but in Wasmtime where an instance could still be used after it was freed. Unfortunately the fix here is a bit of a hammer, but it's the best that we can do for now. The changes made in this commit are: * A `Store` now stores all `InstanceHandle` objects it ever creates. This keeps all instances alive unconditionally (along with all host functions and such) until the `Store` is itself dropped. Note that a `Store` is reference counted so basically everything has to be dropped to drop anything, there's no longer any partial deallocation of instances. * The `InstanceHandle` type's own reference counting has been removed. This is largely redundant with what's already happening in `Store`, so there's no need to manage two reference counts. * Each `InstanceHandle` no longer tracks its dependencies in terms of instance handles. This set was actually inaccurate due to dynamic updates to tables and such, so we needed to revamp it anyway. * Initialization of an `InstanceHandle` is now deferred until after `InstanceHandle::new`. This allows storing the `InstanceHandle` before side-effectful initialization, such as copying element segments or running the start function, to ensure that regardless of the result of instantiation the underlying `InstanceHandle` is still available to persist in storage. Overall this should fix a known possible way to safely segfault Wasmtime today (yay!) and it should also fix some flaikness I've seen on CI. Turns out one of the spec tests (bulk-memory-operations/partial-init-table-segment.wast) exercises this functionality and we were hitting sporating use-after-free, but only on Windows. * Shuffle some APIs around * Comment weak cycle
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
use crate::externals::{Export, Extern, Global, Memory, Table};
|
||||
use crate::func::Func;
|
||||
use crate::module::Module;
|
||||
use crate::runtime::{Config, Store};
|
||||
use crate::trap::Trap;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use std::any::Any;
|
||||
use wasmtime_jit::{CompiledModule, Resolver};
|
||||
use wasmtime_runtime::{InstanceHandle, InstantiationError, SignatureRegistry};
|
||||
use wasmtime_runtime::{InstantiationError, SignatureRegistry};
|
||||
|
||||
struct SimpleResolver<'a> {
|
||||
imports: &'a [Extern],
|
||||
@@ -21,22 +18,35 @@ impl Resolver for SimpleResolver<'_> {
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
config: &Config,
|
||||
store: &Store,
|
||||
compiled_module: &CompiledModule,
|
||||
imports: &[Extern],
|
||||
sig_registry: &SignatureRegistry,
|
||||
host: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle, Error> {
|
||||
) -> Result<StoreInstanceHandle, Error> {
|
||||
let mut resolver = SimpleResolver { imports };
|
||||
unsafe {
|
||||
let instance = compiled_module
|
||||
.instantiate(
|
||||
let config = store.engine().config();
|
||||
let instance = compiled_module.instantiate(
|
||||
&mut resolver,
|
||||
sig_registry,
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
host,
|
||||
)?;
|
||||
|
||||
// After we've created the `InstanceHandle` we still need to run
|
||||
// initialization to set up data/elements/etc. We do this after adding
|
||||
// the `InstanceHandle` to the store though. This is required for safety
|
||||
// because the start function (for example) may trap, but element
|
||||
// initializers may have run which placed elements into other instance's
|
||||
// tables. This means that from this point on, regardless of whether
|
||||
// initialization is successful, we need to keep the instance alive.
|
||||
let instance = store.add_instance(instance);
|
||||
instance
|
||||
.initialize(
|
||||
config.validating_config.operator_config.enable_bulk_memory,
|
||||
&mut resolver,
|
||||
sig_registry,
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
config.max_wasm_stack,
|
||||
host,
|
||||
&compiled_module.data_initializers(),
|
||||
)
|
||||
.map_err(|e| -> Error {
|
||||
match e {
|
||||
@@ -68,7 +78,7 @@ fn instantiate(
|
||||
/// call any code or execute anything!
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
pub(crate) instance_handle: InstanceHandle,
|
||||
pub(crate) handle: StoreInstanceHandle,
|
||||
module: Module,
|
||||
}
|
||||
|
||||
@@ -137,9 +147,8 @@ impl Instance {
|
||||
}
|
||||
|
||||
let info = module.register_frame_info();
|
||||
let config = store.engine().config();
|
||||
let instance_handle = instantiate(
|
||||
config,
|
||||
let handle = instantiate(
|
||||
store,
|
||||
module.compiled_module(),
|
||||
imports,
|
||||
store.compiler().signatures(),
|
||||
@@ -147,7 +156,7 @@ impl Instance {
|
||||
)?;
|
||||
|
||||
Ok(Instance {
|
||||
instance_handle,
|
||||
handle,
|
||||
module: module.clone(),
|
||||
})
|
||||
}
|
||||
@@ -164,15 +173,11 @@ impl Instance {
|
||||
pub fn exports<'instance>(
|
||||
&'instance self,
|
||||
) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance {
|
||||
let instance_handle = &self.instance_handle;
|
||||
let store = self.module.store();
|
||||
self.instance_handle
|
||||
.exports()
|
||||
.map(move |(name, entity_index)| {
|
||||
let export = instance_handle.lookup_by_declaration(entity_index);
|
||||
let extern_ = Extern::from_wasmtime_export(export, store, instance_handle.clone());
|
||||
Export::new(name, extern_)
|
||||
})
|
||||
self.handle.exports().map(move |(name, entity_index)| {
|
||||
let export = self.handle.lookup_by_declaration(entity_index);
|
||||
let extern_ = Extern::from_wasmtime_export(export, self.handle.clone());
|
||||
Export::new(name, extern_)
|
||||
})
|
||||
}
|
||||
|
||||
/// Looks up an exported [`Extern`] value by name.
|
||||
@@ -182,12 +187,8 @@ impl Instance {
|
||||
///
|
||||
/// Returns `None` if there was no export named `name`.
|
||||
pub fn get_export(&self, name: &str) -> Option<Extern> {
|
||||
let export = self.instance_handle.lookup(&name)?;
|
||||
Some(Extern::from_wasmtime_export(
|
||||
export,
|
||||
self.module.store(),
|
||||
self.instance_handle.clone(),
|
||||
))
|
||||
let export = self.handle.lookup(&name)?;
|
||||
Some(Extern::from_wasmtime_export(export, self.handle.clone()))
|
||||
}
|
||||
|
||||
/// Looks up an exported [`Func`] value by name.
|
||||
@@ -221,9 +222,4 @@ impl Instance {
|
||||
pub fn get_global(&self, name: &str) -> Option<Global> {
|
||||
self.get_export(name)?.into_global()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn handle(&self) -> &InstanceHandle {
|
||||
&self.instance_handle
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user