diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 3bff9803ea..64afecd760 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -12,6 +12,7 @@ use crate::vmcontext::{ VMInterrupts, VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport, }; use crate::{ExportFunction, ExportGlobal, ExportMemory, ExportTable, Store}; +use anyhow::Error; use memoffset::offset_of; use more_asserts::assert_lt; use std::alloc::Layout; @@ -348,7 +349,11 @@ impl Instance { /// Returns `None` if memory can't be grown by the specified amount /// of pages. Returns `Some` with the old size in bytes if growth was /// successful. - pub(crate) fn memory_grow(&mut self, index: MemoryIndex, delta: u64) -> Option { + pub(crate) fn memory_grow( + &mut self, + index: MemoryIndex, + delta: u64, + ) -> Result, Error> { let (idx, instance) = if let Some(idx) = self.module.defined_memory_index(index) { (idx, self) } else { @@ -387,7 +392,7 @@ impl Instance { table_index: TableIndex, delta: u32, init_value: TableElement, - ) -> Option { + ) -> Result, Error> { let (defined_table_index, instance) = self.get_defined_table_index_and_instance(table_index); instance.defined_table_grow(defined_table_index, delta, init_value) @@ -398,7 +403,7 @@ impl Instance { table_index: DefinedTableIndex, delta: u32, init_value: TableElement, - ) -> Option { + ) -> Result, Error> { let store = unsafe { &mut *self.store() }; let table = self .tables diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index d3aee4b75e..4f531d4220 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -20,7 +20,7 @@ ) )] -use std::error::Error; +use anyhow::Error; mod export; mod externref; @@ -91,15 +91,25 @@ pub unsafe trait Store { ) -> (&mut VMExternRefActivationsTable, &dyn ModuleInfoLookup); /// Callback invoked to allow the store's resource limiter to reject a memory grow operation. - fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option) -> bool; + fn memory_growing( + &mut self, + current: usize, + desired: usize, + maximum: Option, + ) -> Result; /// Callback invoked to notify the store's resource limiter that a memory grow operation has /// failed. - fn memory_grow_failed(&mut self, error: &anyhow::Error); + fn memory_grow_failed(&mut self, error: &Error); /// Callback invoked to allow the store's resource limiter to reject a table grow operation. - fn table_growing(&mut self, current: u32, desired: u32, maximum: Option) -> bool; + fn table_growing( + &mut self, + current: u32, + desired: u32, + maximum: Option, + ) -> Result; /// Callback invoked whenever fuel runs out by a wasm instance. If an error /// is returned that's raised as a trap. Otherwise wasm execution will /// continue as normal. - fn out_of_gas(&mut self) -> Result<(), Box>; + fn out_of_gas(&mut self) -> Result<(), Error>; } diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index f307a4dbfe..067e0b8503 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -193,8 +193,9 @@ pub unsafe extern "C" fn wasmtime_memory32_grow( let instance = (*vmctx).instance_mut(); let memory_index = MemoryIndex::from_u32(memory_index); match instance.memory_grow(memory_index, delta) { - Some(size_in_bytes) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize), - None => usize::max_value(), + Ok(Some(size_in_bytes)) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize), + Ok(None) => usize::max_value(), + Err(err) => crate::traphandlers::raise_user_trap(err), } } @@ -220,9 +221,11 @@ pub unsafe extern "C" fn wasmtime_table_grow( init_value.into() } }; - instance - .table_grow(table_index, delta, element) - .unwrap_or(-1_i32 as u32) + match instance.table_grow(table_index, delta, element) { + Ok(Some(r)) => r, + Ok(None) => -1_i32 as u32, + Err(err) => crate::traphandlers::raise_user_trap(err), + } } /// Implementation of `table.fill`. @@ -436,15 +439,6 @@ pub unsafe extern "C" fn wasmtime_externref_global_set( drop(old); } -#[derive(Debug)] -struct Unimplemented(&'static str); -impl std::error::Error for Unimplemented {} -impl std::fmt::Display for Unimplemented { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "unimplemented: {}", self.0) - } -} - /// Implementation of `memory.atomic.notify` for locally defined memories. pub unsafe extern "C" fn wasmtime_memory_atomic_notify( vmctx: *mut VMContext, @@ -460,9 +454,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_notify( // just to be sure. let addr_to_check = addr.checked_add(4).unwrap(); validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| { - Err(Trap::User(Box::new(Unimplemented( - "wasm atomics (fn wasmtime_memory_atomic_notify) unsupported", - )))) + Err(Trap::User(anyhow::anyhow!( + "unimplemented: wasm atomics (fn wasmtime_memory_atomic_notify) unsupported", + ))) }) }; match result { @@ -486,9 +480,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_wait32( // but we still double-check let addr_to_check = addr.checked_add(4).unwrap(); validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| { - Err(Trap::User(Box::new(Unimplemented( - "wasm atomics (fn wasmtime_memory_atomic_wait32) unsupported", - )))) + Err(Trap::User(anyhow::anyhow!( + "unimplemented: wasm atomics (fn wasmtime_memory_atomic_wait32) unsupported", + ))) }) }; match result { @@ -512,9 +506,9 @@ pub unsafe extern "C" fn wasmtime_memory_atomic_wait64( // but we still double-check let addr_to_check = addr.checked_add(8).unwrap(); validate_atomic_addr(instance, memory, addr_to_check).and_then(|()| { - Err(Trap::User(Box::new(Unimplemented( - "wasm atomics (fn wasmtime_memory_atomic_wait64) unsupported", - )))) + Err(Trap::User(anyhow::anyhow!( + "unimplemented: wasm atomics (fn wasmtime_memory_atomic_wait64) unsupported", + ))) }) }; match result { diff --git a/crates/runtime/src/memory.rs b/crates/runtime/src/memory.rs index 2561038b81..7e2180e5d3 100644 --- a/crates/runtime/src/memory.rs +++ b/crates/runtime/src/memory.rs @@ -5,6 +5,7 @@ use crate::mmap::Mmap; use crate::vmcontext::VMMemoryDefinition; use crate::Store; +use anyhow::Error; use anyhow::{bail, format_err, Result}; use more_asserts::{assert_ge, assert_le}; use std::convert::TryFrom; @@ -315,7 +316,7 @@ impl Memory { // calculation overflowed. This means that the `minimum` we're informing // the limiter is lossy and may not be 100% accurate, but for now the // expected uses of limiter means that's ok. - if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum) { + if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? { bail!( "memory minimum size of {} pages exceeds memory limits", plan.memory.minimum @@ -377,11 +378,15 @@ impl Memory { /// /// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates /// this unsafety. - pub unsafe fn grow(&mut self, delta_pages: u64, store: &mut dyn Store) -> Option { + pub unsafe fn grow( + &mut self, + delta_pages: u64, + store: &mut dyn Store, + ) -> Result, Error> { let old_byte_size = self.byte_size(); // Wasm spec: when growing by 0 pages, always return the current size. if delta_pages == 0 { - return Some(old_byte_size); + return Ok(Some(old_byte_size)); } // largest wasm-page-aligned region of memory it is possible to @@ -402,15 +407,15 @@ impl Memory { let maximum = self.maximum_byte_size(); // Store limiter gets first chance to reject memory_growing. - if !store.memory_growing(old_byte_size, new_byte_size, maximum) { - return None; + if !store.memory_growing(old_byte_size, new_byte_size, maximum)? { + return Ok(None); } // Never exceed maximum, even if limiter permitted it. if let Some(max) = maximum { if new_byte_size > max { store.memory_grow_failed(&format_err!("Memory maximum size exceeded")); - return None; + return Ok(None); } } @@ -418,7 +423,10 @@ impl Memory { { if self.is_static() { // Reset any faulted guard pages before growing the memory. - self.reset_guard_pages().ok()?; + if let Err(e) = self.reset_guard_pages() { + store.memory_grow_failed(&e); + return Ok(None); + } } } @@ -432,24 +440,27 @@ impl Memory { // Never exceed static memory size if new_byte_size > base.len() { store.memory_grow_failed(&format_err!("static memory size exceeded")); - return None; + return Ok(None); } // Operating system can fail to make memory accessible - let r = make_accessible( + if let Err(e) = make_accessible( base.as_mut_ptr().add(old_byte_size), new_byte_size - old_byte_size, - ); - r.map_err(|e| store.memory_grow_failed(&e)).ok()?; - + ) { + store.memory_grow_failed(&e); + return Ok(None); + } *size = new_byte_size; } Memory::Dynamic(mem) => { - let r = mem.grow_to(new_byte_size); - r.map_err(|e| store.memory_grow_failed(&e)).ok()?; + if let Err(e) = mem.grow_to(new_byte_size) { + store.memory_grow_failed(&e); + return Ok(None); + } } } - Some(old_byte_size) + Ok(Some(old_byte_size)) } /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index c5fbfe3f67..7e927700ed 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -4,6 +4,7 @@ use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition}; use crate::{Store, Trap, VMExternRef}; +use anyhow::Error; use anyhow::{bail, Result}; use std::convert::{TryFrom, TryInto}; use std::ops::Range; @@ -168,7 +169,7 @@ impl Table { } fn limit_new(plan: &TablePlan, store: &mut dyn Store) -> Result<()> { - if !store.table_growing(0, plan.table.minimum, plan.table.maximum) { + if !store.table_growing(0, plan.table.minimum, plan.table.maximum)? { bail!( "table minimum size of {} elements exceeds table limits", plan.table.minimum @@ -288,17 +289,20 @@ impl Table { delta: u32, init_value: TableElement, store: &mut dyn Store, - ) -> Option { + ) -> Result, Error> { let old_size = self.size(); - let new_size = old_size.checked_add(delta)?; + let new_size = match old_size.checked_add(delta) { + Some(s) => s, + None => return Ok(None), + }; - if !store.table_growing(old_size, new_size, self.maximum()) { - return None; + if !store.table_growing(old_size, new_size, self.maximum())? { + return Ok(None); } if let Some(max) = self.maximum() { if new_size > max { - return None; + return Ok(None); } } @@ -320,7 +324,7 @@ impl Table { self.fill(old_size, init_value, delta) .expect("table should not be out of bounds"); - Some(old_size) + Ok(Some(old_size)) } /// Get reference to the specified element. diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index ad78dcb05b..3a1bfac8ed 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -2,10 +2,10 @@ //! signalhandling mechanisms. use crate::{VMContext, VMInterrupts}; +use anyhow::Error; use backtrace::Backtrace; use std::any::Any; use std::cell::{Cell, UnsafeCell}; -use std::error::Error; use std::mem::MaybeUninit; use std::ptr; use std::sync::atomic::Ordering::SeqCst; @@ -80,7 +80,7 @@ pub fn init_traps(is_wasm_pc: fn(usize) -> bool) { /// Only safe to call when wasm code is on the stack, aka `catch_traps` must /// have been previously called. Additionally no Rust destructors can be on the /// stack. They will be skipped and not executed. -pub unsafe fn raise_user_trap(data: Box) -> ! { +pub unsafe fn raise_user_trap(data: Error) -> ! { tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data))) } @@ -114,7 +114,7 @@ pub unsafe fn resume_panic(payload: Box) -> ! { #[derive(Debug)] pub enum Trap { /// A user-raised trap through `raise_user_trap`. - User(Box), + User(Error), /// A trap raised from jit code Jit { @@ -206,7 +206,7 @@ pub struct CallThreadState { enum UnwindReason { Panic(Box), - UserTrap(Box), + UserTrap(Error), LibTrap(Trap), JitTrap { backtrace: Backtrace, pc: usize }, }