* Reel in unsafety around `InstanceHandle` This commit is an attempt, or at least is targeted at being a start, at reeling in the unsafety around the `InstanceHandle` type. Currently this type represents a sort of moral `Rc<Instance>` but is a bit more specialized since the underlying memory is allocated through mmap. Additionally, though, `InstanceHandle` exposes a fundamental flaw in its safety by safetly allowing mutable access so long as you have `&mut InstanceHandle`. This type, however, is trivially created by simply cloning a `InstanceHandle` to get an owned reference. This means that `&mut InstanceHandle` does not actually provide any guarantees about uniqueness, so there's no more safety than `&InstanceHandle` itself. This commit removes all `&mut self` APIs from `InstanceHandle`, additionally removing some where `&self` was `unsafe` and `&mut self` was safe (since it was trivial to subvert this "safety"). In doing so interior mutability patterns are now used much more extensively through structures such as `Table` and `Memory`. Additionally a number of methods were refactored to be a bit clearer and use helper functions where possible. This is a relatively large commit unfortunately, but it snowballed very quickly into touching quite a few places. My hope though is that this will prevent developers working on wasmtime internals as well as developers still yet to migrate to the `wasmtime` crate from falling into trivial unsafe traps by accidentally using `&mut` when they can't. All existing users relying on `&mut` will need to migrate to some form of interior mutability, such as using `RefCell` or `Cell`. This commit also additionally marks `InstanceHandle::new` as an `unsafe` function. The rationale for this is that the `&mut`-safety is only the beginning for the safety of `InstanceHandle`. In general the wasmtime internals are extremely unsafe and haven't been audited for appropriate usage of `unsafe`. Until that's done it's hoped that we can warn users with this `unsafe` constructor and otherwise push users to the `wasmtime` crate which we know is safe. * Fix windows build * Wrap up mutable memory state in one structure Rather than having separate fields * Use `Cell::set`, not `Cell::replace`, where possible * Add a helper function for offsets from VMContext * Fix a typo from merging * rustfmt * Use try_from, not as * Tweak style of some setters
229 lines
7.4 KiB
Rust
229 lines
7.4 KiB
Rust
use crate::action::{get, inspect_memory, invoke};
|
|
use crate::{
|
|
instantiate, ActionError, ActionOutcome, CompilationStrategy, CompiledModule, Compiler,
|
|
InstanceHandle, Namespace, RuntimeValue, SetupError,
|
|
};
|
|
use thiserror::Error;
|
|
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
|
|
use wasmtime_environ::isa::TargetIsa;
|
|
|
|
/// Indicates an unknown instance was specified.
|
|
#[derive(Error, Debug)]
|
|
#[error("no instance {instance_name} present")]
|
|
pub struct UnknownInstance {
|
|
instance_name: String,
|
|
}
|
|
|
|
/// Error message used by `WastContext`.
|
|
#[derive(Error, Debug)]
|
|
pub enum ContextError {
|
|
/// An unknown instance name was used.
|
|
#[error("An error occured due to an unknown instance being specified")]
|
|
Instance(#[from] UnknownInstance),
|
|
/// An error occured while performing an action.
|
|
#[error("An error occurred while performing an action")]
|
|
Action(#[from] ActionError),
|
|
}
|
|
|
|
/// The collection of features configurable during compilation
|
|
#[derive(Clone, Default)]
|
|
pub struct Features {
|
|
/// marks whether the proposed thread feature is enabled or disabled
|
|
pub threads: bool,
|
|
/// marks whether the proposed reference type feature is enabled or disabled
|
|
pub reference_types: bool,
|
|
/// marks whether the proposed SIMD feature is enabled or disabled
|
|
pub simd: bool,
|
|
/// marks whether the proposed bulk memory feature is enabled or disabled
|
|
pub bulk_memory: bool,
|
|
/// marks whether the proposed multi-value feature is enabled or disabled
|
|
pub multi_value: bool,
|
|
}
|
|
|
|
impl Into<ValidatingParserConfig> for Features {
|
|
fn into(self) -> ValidatingParserConfig {
|
|
ValidatingParserConfig {
|
|
operator_config: OperatorValidatorConfig {
|
|
enable_threads: self.threads,
|
|
enable_reference_types: self.reference_types,
|
|
enable_bulk_memory: self.bulk_memory,
|
|
enable_simd: self.simd,
|
|
enable_multi_value: self.multi_value,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A convenient context for compiling and executing WebAssembly instances.
|
|
pub struct Context {
|
|
namespace: Namespace,
|
|
compiler: Box<Compiler>,
|
|
debug_info: bool,
|
|
features: Features,
|
|
}
|
|
|
|
impl Context {
|
|
/// Construct a new instance of `Context`.
|
|
pub fn new(compiler: Box<Compiler>) -> Self {
|
|
Self {
|
|
namespace: Namespace::new(),
|
|
compiler,
|
|
debug_info: false,
|
|
features: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Get debug_info settings.
|
|
pub fn debug_info(&self) -> bool {
|
|
self.debug_info
|
|
}
|
|
|
|
/// Set debug_info settings.
|
|
pub fn set_debug_info(&mut self, value: bool) {
|
|
self.debug_info = value;
|
|
}
|
|
|
|
/// Construct a new instance of `Context` with the given target.
|
|
pub fn with_isa(isa: Box<dyn TargetIsa>, strategy: CompilationStrategy) -> Self {
|
|
Self::new(Box::new(Compiler::new(isa, strategy)))
|
|
}
|
|
|
|
/// Retrieve the context features
|
|
pub fn features(&self) -> &Features {
|
|
&self.features
|
|
}
|
|
|
|
/// Construct a new instance with the given features from the current `Context`
|
|
pub fn with_features(self, features: Features) -> Self {
|
|
Self { features, ..self }
|
|
}
|
|
|
|
fn validate(&mut self, data: &[u8]) -> Result<(), String> {
|
|
// TODO: Fix Cranelift to be able to perform validation itself, rather
|
|
// than calling into wasmparser ourselves here.
|
|
validate(data, Some(self.features.clone().into()))
|
|
.map_err(|e| format!("module did not validate: {}", e.to_string()))
|
|
}
|
|
|
|
unsafe fn instantiate(&mut self, data: &[u8]) -> Result<InstanceHandle, SetupError> {
|
|
self.validate(&data).map_err(SetupError::Validate)?;
|
|
let debug_info = self.debug_info();
|
|
|
|
instantiate(&mut *self.compiler, &data, &mut self.namespace, debug_info)
|
|
}
|
|
|
|
/// Return the instance associated with the given name.
|
|
pub fn get_instance(
|
|
&mut self,
|
|
instance_name: &str,
|
|
) -> Result<&mut InstanceHandle, UnknownInstance> {
|
|
self.namespace
|
|
.get_instance(instance_name)
|
|
.ok_or_else(|| UnknownInstance {
|
|
instance_name: instance_name.to_string(),
|
|
})
|
|
}
|
|
|
|
/// Instantiate a module instance and register the instance.
|
|
///
|
|
/// # Unsafety
|
|
///
|
|
/// See `InstanceHandle::new`
|
|
pub unsafe fn instantiate_module(
|
|
&mut self,
|
|
instance_name: Option<String>,
|
|
data: &[u8],
|
|
) -> Result<InstanceHandle, ActionError> {
|
|
let instance = self.instantiate(data).map_err(ActionError::Setup)?;
|
|
self.optionally_name_instance(instance_name, instance.clone());
|
|
Ok(instance)
|
|
}
|
|
|
|
/// Compile a module.
|
|
pub fn compile_module(&mut self, data: &[u8]) -> Result<CompiledModule, SetupError> {
|
|
self.validate(&data).map_err(SetupError::Validate)?;
|
|
let debug_info = self.debug_info();
|
|
|
|
CompiledModule::new(&mut *self.compiler, data, debug_info)
|
|
}
|
|
|
|
/// If `name` isn't None, register it for the given instance.
|
|
pub fn optionally_name_instance(&mut self, name: Option<String>, instance: InstanceHandle) {
|
|
if let Some(name) = name {
|
|
self.namespace.name_instance(name, instance);
|
|
}
|
|
}
|
|
|
|
/// Register a name for the given instance.
|
|
pub fn name_instance(&mut self, name: String, instance: InstanceHandle) {
|
|
self.namespace.name_instance(name, instance);
|
|
}
|
|
|
|
/// Register an additional name for an existing registered instance.
|
|
pub fn alias(&mut self, name: &str, as_name: String) -> Result<(), UnknownInstance> {
|
|
let instance = self.get_instance(&name)?.clone();
|
|
self.name_instance(as_name, instance);
|
|
Ok(())
|
|
}
|
|
|
|
/// Invoke an exported function from a named instance.
|
|
pub fn invoke_named(
|
|
&mut self,
|
|
instance_name: &str,
|
|
field: &str,
|
|
args: &[RuntimeValue],
|
|
) -> Result<ActionOutcome, ContextError> {
|
|
let mut instance = self
|
|
.get_instance(&instance_name)
|
|
.map_err(ContextError::Instance)?
|
|
.clone();
|
|
self.invoke(&mut instance, field, args)
|
|
.map_err(ContextError::Action)
|
|
}
|
|
|
|
/// Invoke an exported function from an instance.
|
|
pub fn invoke(
|
|
&mut self,
|
|
instance: &mut InstanceHandle,
|
|
field: &str,
|
|
args: &[RuntimeValue],
|
|
) -> Result<ActionOutcome, ActionError> {
|
|
invoke(&mut *self.compiler, instance, field, &args)
|
|
}
|
|
|
|
/// Get the value of an exported global variable from an instance.
|
|
pub fn get_named(
|
|
&mut self,
|
|
instance_name: &str,
|
|
field: &str,
|
|
) -> Result<ActionOutcome, ContextError> {
|
|
let instance = self
|
|
.get_instance(&instance_name)
|
|
.map_err(ContextError::Instance)?
|
|
.clone();
|
|
self.get(&instance, field).map_err(ContextError::Action)
|
|
}
|
|
|
|
/// Get the value of an exported global variable from an instance.
|
|
pub fn get(
|
|
&mut self,
|
|
instance: &InstanceHandle,
|
|
field: &str,
|
|
) -> Result<ActionOutcome, ActionError> {
|
|
get(instance, field).map(|value| ActionOutcome::Returned {
|
|
values: vec![value],
|
|
})
|
|
}
|
|
|
|
/// Get a slice of memory from an instance.
|
|
pub fn inspect_memory<'instance>(
|
|
&self,
|
|
instance: &'instance InstanceHandle,
|
|
field_name: &str,
|
|
start: usize,
|
|
len: usize,
|
|
) -> Result<&'instance [u8], ActionError> {
|
|
inspect_memory(instance, field_name, start, len)
|
|
}
|
|
}
|