diff --git a/CHANGELOG.md b/CHANGELOG.md index 950d207a918..3e6792196a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1401](https://github.com/wasmerio/wasmer/pull/1401) Make breaking change to `RuntimeError`: `RuntimeError` is now more explicit about its possible error values allowing for better insight into why a call into Wasm failed. - [#1382](https://github.com/wasmerio/wasmer/pull/1382) Refactored test infranstructure (part 2) - [#1380](https://github.com/wasmerio/wasmer/pull/1380) Refactored test infranstructure (part 1) - [#1357](https://github.com/wasmerio/wasmer/pull/1357) Refactored bin commands into separate files diff --git a/examples/callback-guest/callback-guest.wasm b/examples/callback-guest/callback-guest.wasm index 05558297595..61b247c8ec0 100755 Binary files a/examples/callback-guest/callback-guest.wasm and b/examples/callback-guest/callback-guest.wasm differ diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index cb6e147b1a7..c8f78a46748 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -4,9 +4,10 @@ use crate::{ trampoline::Trampolines, }; use libc::c_void; -use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc}; +use std::{cell::Cell, ptr::NonNull, sync::Arc}; use wasmer_runtime_core::{ backend::RunnableModule, + error::RuntimeError, module::ModuleInfo, typed_func::{Trampoline, Wasm}, types::{LocalFuncIndex, SigIndex}, @@ -26,11 +27,9 @@ pub use self::unix::*; pub use self::windows::*; thread_local! { - pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); + pub static TRAP_EARLY_DATA: Cell> = Cell::new(None); } -pub struct CallProtError(pub Box); - pub struct Caller { handler_data: HandlerData, trampolines: Arc, @@ -63,7 +62,7 @@ impl RunnableModule for Caller { func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool { let handler_data = &*invoke_env.unwrap().cast().as_ptr(); @@ -80,7 +79,7 @@ impl RunnableModule for Caller { match res { Err(err) => { - *error_out = Some(err.0); + *error_out = Some(err.into()); false } Ok(()) => true, @@ -101,7 +100,7 @@ impl RunnableModule for Caller { }) } - unsafe fn do_early_trap(&self, data: Box) -> ! { + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { TRAP_EARLY_DATA.with(|cell| cell.set(Some(data))); trigger_trap() } diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index 98571cbbff3..965359e354f 100644 --- a/lib/clif-backend/src/signal/unix.rs +++ b/lib/clif-backend/src/signal/unix.rs @@ -10,7 +10,7 @@ //! unless you have memory unsafety elsewhere in your code. //! use crate::relocation::{TrapCode, TrapData}; -use crate::signal::{CallProtError, HandlerData}; +use crate::signal::HandlerData; use libc::{c_int, c_void, siginfo_t}; use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, @@ -19,6 +19,7 @@ use std::cell::{Cell, UnsafeCell}; use std::ptr; use std::sync::Once; use wasmer_runtime_core::backend::ExceptionCode; +use wasmer_runtime_core::error::InvokeError; extern "C" fn signal_trap_handler( signum: ::nix::libc::c_int, @@ -65,7 +66,7 @@ pub unsafe fn trigger_trap() -> ! { pub fn call_protected( handler_data: &HandlerData, f: impl FnOnce() -> T, -) -> Result { +) -> Result { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -79,16 +80,12 @@ pub fn call_protected( *jmp_buf = prev_jmp_buf; if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(CallProtError(data)) + Err(InvokeError::EarlyTrap(Box::new(data))) } else { let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); - if let Some(TrapData { - trapcode, - srcloc: _, - }) = handler_data.lookup(inst_ptr) - { - Err(CallProtError(Box::new(match Signal::from_c_int(signum) { + if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(inst_ptr) { + let code = match Signal::from_c_int(signum) { Ok(SIGILL) => match trapcode { TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds, TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds, @@ -101,9 +98,10 @@ pub fn call_protected( TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic, TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable, _ => { - return Err(CallProtError(Box::new( - "unknown clif trap code".to_string(), - ))) + return Err(InvokeError::UnknownTrapCode { + trap_code: format!("{:?}", trapcode), + srcloc, + }) } }, Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds, @@ -112,7 +110,8 @@ pub fn call_protected( "ExceptionCode::Unknown signal:{:?}", Signal::from_c_int(signum) ), - }))) + }; + Err(InvokeError::TrapCode { srcloc, code }) } else { let signal = match Signal::from_c_int(signum) { Ok(SIGFPE) => "floating-point exception", @@ -123,8 +122,10 @@ pub fn call_protected( _ => "unknown trapped signal", }; // When the trap-handler is fully implemented, this will return more information. - let s = format!("unknown trap at {:p} - {}", faulting_addr, signal); - Err(CallProtError(Box::new(s))) + Err(InvokeError::UnknownTrap { + address: faulting_addr as usize, + signal, + }) } } } else { diff --git a/lib/clif-backend/src/signal/windows.rs b/lib/clif-backend/src/signal/windows.rs index 7d3e81d695f..46380bbdc77 100644 --- a/lib/clif-backend/src/signal/windows.rs +++ b/lib/clif-backend/src/signal/windows.rs @@ -1,6 +1,6 @@ use crate::{ relocation::{TrapCode, TrapData}, - signal::{CallProtError, HandlerData}, + signal::HandlerData, }; use std::{ cell::Cell, @@ -9,6 +9,7 @@ use std::{ }; use wasmer_runtime_core::{ backend::ExceptionCode, + error::InvokeError, typed_func::Trampoline, vm::{Ctx, Func}, }; @@ -32,6 +33,34 @@ thread_local! { pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null()); } +fn get_signal_name(code: DWORD) -> &'static str { + match code { + EXCEPTION_FLT_DENORMAL_OPERAND + | EXCEPTION_FLT_DIVIDE_BY_ZERO + | EXCEPTION_FLT_INEXACT_RESULT + | EXCEPTION_FLT_INVALID_OPERATION + | EXCEPTION_FLT_OVERFLOW + | EXCEPTION_FLT_STACK_CHECK + | EXCEPTION_FLT_UNDERFLOW => "floating-point exception", + EXCEPTION_ILLEGAL_INSTRUCTION => "illegal instruction", + EXCEPTION_ACCESS_VIOLATION => "segmentation violation", + EXCEPTION_DATATYPE_MISALIGNMENT => "datatype misalignment", + EXCEPTION_BREAKPOINT => "breakpoint", + EXCEPTION_SINGLE_STEP => "single step", + EXCEPTION_ARRAY_BOUNDS_EXCEEDED => "array bounds exceeded", + EXCEPTION_INT_DIVIDE_BY_ZERO => "integer division by zero", + EXCEPTION_INT_OVERFLOW => "integer overflow", + EXCEPTION_PRIV_INSTRUCTION => "privileged instruction", + EXCEPTION_IN_PAGE_ERROR => "in page error", + EXCEPTION_NONCONTINUABLE_EXCEPTION => "non continuable exception", + EXCEPTION_STACK_OVERFLOW => "stack overflow", + EXCEPTION_GUARD_PAGE => "guard page", + EXCEPTION_INVALID_HANDLE => "invalid handle", + EXCEPTION_POSSIBLE_DEADLOCK => "possible deadlock", + _ => "unknown exception code", + } +} + pub fn call_protected( handler_data: &HandlerData, trampoline: Trampoline, @@ -39,7 +68,7 @@ pub fn call_protected( func: NonNull, param_vec: *const u64, return_vec: *mut u64, -) -> Result<(), CallProtError> { +) -> Result<(), InvokeError> { // TODO: trap early // user code error // if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { @@ -58,12 +87,8 @@ pub fn call_protected( instruction_pointer, } = result.unwrap_err(); - if let Some(TrapData { - trapcode, - srcloc: _, - }) = handler_data.lookup(instruction_pointer as _) - { - Err(CallProtError(Box::new(match code as DWORD { + if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(instruction_pointer as _) { + let exception_code = match code as DWORD { EXCEPTION_ACCESS_VIOLATION => ExceptionCode::MemoryOutOfBounds, EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode { TrapCode::BadSignature => ExceptionCode::IncorrectCallIndirectSignature, @@ -71,51 +96,36 @@ pub fn call_protected( TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds, TrapCode::TableOutOfBounds => ExceptionCode::CallIndirectOOB, TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable, - _ => return Err(CallProtError(Box::new("unknown trap code".to_string()))), + _ => { + return Err(InvokeError::UnknownTrapCode { + trap_code: format!("{}", code as DWORD), + srcloc, + }) + } }, EXCEPTION_STACK_OVERFLOW => ExceptionCode::MemoryOutOfBounds, EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => { ExceptionCode::IllegalArithmetic } _ => { - return Err(CallProtError(Box::new( - "unknown exception code".to_string(), - ))) + let signal = get_signal_name(code as DWORD); + return Err(InvokeError::UnknownTrap { + address: exception_address as usize, + signal, + }); } - }))) - } else { - let signal = match code as DWORD { - EXCEPTION_FLT_DENORMAL_OPERAND - | EXCEPTION_FLT_DIVIDE_BY_ZERO - | EXCEPTION_FLT_INEXACT_RESULT - | EXCEPTION_FLT_INVALID_OPERATION - | EXCEPTION_FLT_OVERFLOW - | EXCEPTION_FLT_STACK_CHECK - | EXCEPTION_FLT_UNDERFLOW => "floating-point exception", - EXCEPTION_ILLEGAL_INSTRUCTION => "illegal instruction", - EXCEPTION_ACCESS_VIOLATION => "segmentation violation", - EXCEPTION_DATATYPE_MISALIGNMENT => "datatype misalignment", - EXCEPTION_BREAKPOINT => "breakpoint", - EXCEPTION_SINGLE_STEP => "single step", - EXCEPTION_ARRAY_BOUNDS_EXCEEDED => "array bounds exceeded", - EXCEPTION_INT_DIVIDE_BY_ZERO => "int div by zero", - EXCEPTION_INT_OVERFLOW => "int overflow", - EXCEPTION_PRIV_INSTRUCTION => "priv instruction", - EXCEPTION_IN_PAGE_ERROR => "in page error", - EXCEPTION_NONCONTINUABLE_EXCEPTION => "non continuable exception", - EXCEPTION_STACK_OVERFLOW => "stack overflow", - EXCEPTION_GUARD_PAGE => "guard page", - EXCEPTION_INVALID_HANDLE => "invalid handle", - EXCEPTION_POSSIBLE_DEADLOCK => "possible deadlock", - _ => "unknown exception code", }; + return Err(InvokeError::TrapCode { + srcloc, + code: exception_code, + }); + } else { + let signal = get_signal_name(code as DWORD); - let s = format!( - "unhandled trap at {:x} - code #{:x}: {}", - exception_address, code, signal, - ); - - Err(CallProtError(Box::new(s))) + Err(InvokeError::UnknownTrap { + address: exception_address as usize, + signal, + }) } } diff --git a/lib/llvm-backend/cpp/object_loader.cpp b/lib/llvm-backend/cpp/object_loader.cpp index c4fc6d93628..5688f9d81df 100644 --- a/lib/llvm-backend/cpp/object_loader.cpp +++ b/lib/llvm-backend/cpp/object_loader.cpp @@ -13,7 +13,7 @@ MemoryManager::~MemoryManager() { callbacks.dealloc_memory(read_section.base, read_section.size); callbacks.dealloc_memory(readwrite_section.base, readwrite_section.size); } -void unwinding_setjmp(jmp_buf stack_out, void (*func)(void *), void *userdata) { +void unwinding_setjmp(jmp_buf &stack_out, void (*func)(void *), void *userdata) { if (setjmp(stack_out)) { } else { diff --git a/lib/llvm-backend/cpp/object_loader.hh b/lib/llvm-backend/cpp/object_loader.hh index 53bd2b0df85..0d3e573be87 100644 --- a/lib/llvm-backend/cpp/object_loader.hh +++ b/lib/llvm-backend/cpp/object_loader.hh @@ -49,9 +49,7 @@ typedef struct { visit_fde_t visit_fde; } callbacks_t; -typedef struct { - size_t data, vtable; -} box_any_t; +typedef struct runtime_error_t_privdecl runtime_error_t; enum WasmTrapType { Unreachable = 0, @@ -65,6 +63,9 @@ enum WasmTrapType { extern "C" void callback_trampoline(void *, void *); +extern "C" void copy_runtime_error(runtime_error_t *src, runtime_error_t *dst); +extern "C" void free_runtime_error_without_drop(runtime_error_t*); + struct MemoryManager : llvm::RuntimeDyld::MemoryManager { public: MemoryManager(callbacks_t callbacks) : callbacks(callbacks) {} @@ -121,7 +122,7 @@ private: struct WasmErrorSink { WasmTrapType *trap_out; - box_any_t *user_error; + runtime_error_t *user_error; }; struct WasmException : std::exception { @@ -149,17 +150,25 @@ public: struct UserException : UncatchableException { public: - UserException(size_t data, size_t vtable) : error_data({data, vtable}) {} + UserException(size_t data) { + error_data = reinterpret_cast(data); + } + + UserException(const UserException &) = delete; + + ~UserException() { + free_runtime_error_without_drop(error_data); + } virtual std::string description() const noexcept override { return "user exception"; } - // The parts of a `Box`. - box_any_t error_data; + // The pointer to `Option`. + runtime_error_t *error_data; virtual void write_error(WasmErrorSink &out) const noexcept override { - *out.user_error = error_data; + copy_runtime_error(error_data, out.user_error); } }; @@ -274,10 +283,10 @@ result_t module_load(const uint8_t *mem_ptr, size_t mem_size, void module_delete(WasmModule *module) { delete module; } -// Throw a fat pointer that's assumed to be `*mut dyn Any` on the rust +// Throw a pointer that's assumed to be `*mut Option` on the rust // side. -[[noreturn]] void throw_any(size_t data, size_t vtable) { - unsafe_unwind(new UserException(data, vtable)); +[[noreturn]] void throw_runtime_error(size_t data) { + unsafe_unwind(new UserException(data)); } // Throw a pointer that's assumed to be codegen::BreakpointHandler on the @@ -288,7 +297,7 @@ void module_delete(WasmModule *module) { delete module; } bool cxx_invoke_trampoline(trampoline_t trampoline, void *ctx, void *func, void *params, void *results, WasmTrapType *trap_out, - box_any_t *user_error, void *invoke_env) noexcept { + runtime_error_t *user_error, void *invoke_env) noexcept { try { catch_unwind([trampoline, ctx, func, params, results]() { trampoline(ctx, func, params, results); diff --git a/lib/llvm-backend/src/backend.rs b/lib/llvm-backend/src/backend.rs index a2ac15a940c..0b582a44993 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -11,7 +11,7 @@ use inkwell::{ }; use libc::c_char; use std::{ - any::Any, + alloc, cell::RefCell, ffi::{c_void, CString}, mem, @@ -27,6 +27,7 @@ use wasmer_runtime_core::{ CacheGen, ExceptionCode, RunnableModule, }, cache::Error as CacheError, + error::{InvokeError, RuntimeError}, module::ModuleInfo, state::ModuleStateMap, structures::TypedIndex, @@ -52,11 +53,9 @@ extern "C" { fn throw_trap(ty: i32) -> !; fn throw_breakpoint(ty: i64) -> !; - /// This should be the same as spliting up the fat pointer into two arguments, - /// but this is cleaner, I think? #[cfg_attr(nightly, unwind(allowed))] #[allow(improper_ctypes)] - fn throw_any(data: *mut dyn Any) -> !; + fn throw_runtime_error(data: *mut Option) -> !; #[allow(improper_ctypes)] fn cxx_invoke_trampoline( @@ -66,11 +65,49 @@ extern "C" { params: *const u64, results: *mut u64, trap_out: *mut i32, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool; } +/// Unsafe copy of `RuntimeError`. For use from C++. +/// +/// This copy is unsafe because `RuntimeError` contains non-`Clone` types such as +/// `Box`. +/// +/// This function should only be used when the ownership can be manually tracked. +/// +/// For example, this is safe* when used indirectly through the C++ API with a pointer +/// from `do_early_trap` because `do_early_trap` fully owns the `RuntimeError` and +/// creates and leaks the `Box` itself. +/// +/// *: it is only safe provided the following invariants are upheld: +/// 1. The versions of memory that these 2 pointers point to is only dropped once; +/// the memory itself can be freed provided the inner type is not dropped. +/// 2. The duplicated memory is not brought back into Rust to violate standard +/// mutable aliasing/ownership rules. +#[no_mangle] +pub unsafe extern "C" fn copy_runtime_error( + src: *mut Option, + dst: *mut Option, +) { + assert_eq!(src as usize % mem::align_of::>(), 0); + assert_eq!(dst as usize % mem::align_of::>(), 0); + ptr::copy::>(src, dst, 1); +} + +/// Frees the memory of a `Option` without calling its destructor. +/// For use from C++ to safely clean up after `copy_runtime_error`. +#[no_mangle] +pub unsafe extern "C" fn free_runtime_error_without_drop(rte: *mut Option) { + let rte_layout = alloc::Layout::from_size_align( + mem::size_of::>(), + mem::align_of::>(), + ) + .expect("layout of `Option`"); + alloc::dealloc(rte as *mut u8, rte_layout) +} + /// `invoke_trampoline` is a wrapper around `cxx_invoke_trampoline`, for fixing up the obsoleted /// `trap_out` in the C++ part. unsafe extern "C" fn invoke_trampoline( @@ -79,7 +116,7 @@ unsafe extern "C" fn invoke_trampoline( func_ptr: NonNull, params: *const u64, results: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, invoke_env: Option>, ) -> bool { let mut trap_out: i32 = -1; @@ -95,15 +132,22 @@ unsafe extern "C" fn invoke_trampoline( ); // Translate trap code if an error occurred. if !ret && (*error_out).is_none() && trap_out != -1 { - *error_out = Some(Box::new(match trap_out { - 0 => ExceptionCode::Unreachable, - 1 => ExceptionCode::IncorrectCallIndirectSignature, - 2 => ExceptionCode::MemoryOutOfBounds, - 3 => ExceptionCode::CallIndirectOOB, - 4 => ExceptionCode::IllegalArithmetic, - 5 => ExceptionCode::MisalignedAtomicAccess, - _ => return ret, - })); + *error_out = { + let exception_code = match trap_out { + 0 => ExceptionCode::Unreachable, + 1 => ExceptionCode::IncorrectCallIndirectSignature, + 2 => ExceptionCode::MemoryOutOfBounds, + 3 => ExceptionCode::CallIndirectOOB, + 4 => ExceptionCode::IllegalArithmetic, + 5 => ExceptionCode::MisalignedAtomicAccess, + _ => return ret, + }; + Some(RuntimeError::InvokeError(InvokeError::TrapCode { + code: exception_code, + // TODO: + srcloc: 0, + })) + }; } ret } @@ -467,8 +511,8 @@ impl RunnableModule for LLVMBackend { self.msm.clone() } - unsafe fn do_early_trap(&self, data: Box) -> ! { - throw_any(Box::leak(data)) + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { + throw_runtime_error(Box::into_raw(Box::new(Some(data)))) } } diff --git a/lib/llvm-backend/src/code.rs b/lib/llvm-backend/src/code.rs index 554628e254b..385cef47d18 100644 --- a/lib/llvm-backend/src/code.rs +++ b/lib/llvm-backend/src/code.rs @@ -35,6 +35,7 @@ use wasmer_runtime_core::{ backend::{CacheGen, CompilerConfig, Token}, cache::{Artifact, Error as CacheError}, codegen::*, + error::RuntimeError, memory::MemoryType, module::{ModuleInfo, ModuleInner}, parse::{wp_type_to_type, LoadError}, @@ -940,12 +941,11 @@ pub struct CodegenError { // prevents unused function elimination. #[no_mangle] pub unsafe extern "C" fn callback_trampoline( - b: *mut Option>, + b: *mut Option, callback: *mut BreakpointHandler, ) { let callback = Box::from_raw(callback); - let result: Result<(), Box> = - callback(BreakpointInfo { fault: None }); + let result: Result<(), RuntimeError> = callback(BreakpointInfo { fault: None }); match result { Ok(()) => *b = None, Err(e) => *b = Some(e), diff --git a/lib/middleware-common/src/metering.rs b/lib/middleware-common/src/metering.rs index 409035d4ba7..d4db44f905e 100644 --- a/lib/middleware-common/src/metering.rs +++ b/lib/middleware-common/src/metering.rs @@ -1,5 +1,6 @@ use wasmer_runtime_core::{ codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, + error::RuntimeError, module::ModuleInfo, vm::{Ctx, InternalField}, wasmparser::{Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType}, @@ -96,7 +97,9 @@ impl FunctionMiddleware for Metering { ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType), })); sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new(|_| { - Err(Box::new(ExecutionLimitExceededError)) + Err(RuntimeError::Metering(Box::new( + ExecutionLimitExceededError, + ))) })))); sink.push(Event::WasmOwned(Operator::End)); } diff --git a/lib/runtime-c-api/src/import/mod.rs b/lib/runtime-c-api/src/import/mod.rs index 79631de1ced..c6d724f51e0 100644 --- a/lib/runtime-c-api/src/import/mod.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -21,6 +21,7 @@ use wasmer::import::{ImportObject, ImportObjectIterator}; use wasmer::vm::Ctx; use wasmer::wasm::{Export, FuncSig, Global, Memory, Module, Table, Type}; use wasmer_runtime_core::{ + error::RuntimeError, export::{Context, FuncPointer}, module::ImportName, }; @@ -723,7 +724,7 @@ pub unsafe extern "C" fn wasmer_trap( (&*ctx.module) .runnable_module - .do_early_trap(Box::new(error_message)); // never returns + .do_early_trap(RuntimeError::User(Box::new(error_message))); // never returns // cbindgen does not generate a binding for a function that // returns `!`. Since we also need to error in some cases, the diff --git a/lib/runtime-c-api/tests/test-import-trap.c b/lib/runtime-c-api/tests/test-import-trap.c index 0e28208eccc..c81df2311dc 100644 --- a/lib/runtime-c-api/tests/test-import-trap.c +++ b/lib/runtime-c-api/tests/test-import-trap.c @@ -69,7 +69,7 @@ int main() wasmer_last_error_message(error_str, error_len); printf("Error str: `%s`\n", error_str); - assert(0 == strcmp(error_str, "Call error: \"Hello\"")); + assert(0 == strcmp(error_str, "Call error: User supplied error: \"Hello\"")); printf("Destroying func\n"); wasmer_import_func_destroy(func); diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 1e7d9296d74..6262d8e8d0f 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -924,7 +924,7 @@ void *wasmer_instance_context_data_get(const wasmer_instance_context_t *ctx); * // Allocate them and set them on the given instance. * my_data *data = malloc(sizeof(my_data)); * data->… = …; - * wasmer_instance_context_data_set(instance, (void*) my_data); + * wasmer_instance_context_data_set(instance, (void*) data); * * // You can read your data. * { diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index 0bbf819ccca..647e637d03f 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -736,7 +736,7 @@ void *wasmer_instance_context_data_get(const wasmer_instance_context_t *ctx); /// // Allocate them and set them on the given instance. /// my_data *data = malloc(sizeof(my_data)); /// data->… = …; -/// wasmer_instance_context_data_set(instance, (void*) my_data); +/// wasmer_instance_context_data_set(instance, (void*) data); /// /// // You can read your data. /// { diff --git a/lib/runtime-core/src/backend.rs b/lib/runtime-core/src/backend.rs index c6235475d8d..cbef1db3bc0 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -1,5 +1,5 @@ use crate::{ - error::CompileResult, + error::{CompileResult, RuntimeError}, module::ModuleInner, state::ModuleStateMap, typed_func::Wasm, @@ -282,7 +282,7 @@ pub trait RunnableModule: Send + Sync { fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option; /// Trap an error. - unsafe fn do_early_trap(&self, data: Box) -> !; + unsafe fn do_early_trap(&self, data: RuntimeError) -> !; /// Returns the machine code associated with this module. fn get_code(&self) -> Option<&[u8]> { diff --git a/lib/runtime-core/src/codegen.rs b/lib/runtime-core/src/codegen.rs index 4e2bf74a4a2..6ff5c596c03 100644 --- a/lib/runtime-core/src/codegen.rs +++ b/lib/runtime-core/src/codegen.rs @@ -6,13 +6,12 @@ use crate::{ backend::RunnableModule, backend::{CacheGen, Compiler, CompilerConfig, Features, Token}, cache::{Artifact, Error as CacheError}, - error::{CompileError, CompileResult}, + error::{CompileError, CompileResult, RuntimeError}, module::{ModuleInfo, ModuleInner}, structures::Map, types::{FuncIndex, FuncSig, SigIndex}, }; use smallvec::SmallVec; -use std::any::Any; use std::collections::HashMap; use std::fmt; use std::fmt::Debug; @@ -23,7 +22,7 @@ use wasmparser::{Operator, Type as WpType}; /// A type that defines a function pointer, which is called when breakpoints occur. pub type BreakpointHandler = - Box Result<(), Box> + Send + Sync + 'static>; + Box Result<(), RuntimeError> + Send + Sync + 'static>; /// Maps instruction pointers to their breakpoint handlers. pub type BreakpointMap = Arc>; diff --git a/lib/runtime-core/src/error.rs b/lib/runtime-core/src/error.rs index a685a8238f2..51255243325 100644 --- a/lib/runtime-core/src/error.rs +++ b/lib/runtime-core/src/error.rs @@ -173,13 +173,99 @@ impl std::fmt::Display for LinkError { impl std::error::Error for LinkError {} -/// This is the error type returned when calling -/// a WebAssembly function. -/// -/// The main way to do this is `Instance.call`. +/// An error that happened while invoking a Wasm function. +#[derive(Debug)] +pub enum InvokeError { + /// Indicates an exceptional circumstance such as a bug in Wasmer (please file an issue!) + /// or a hardware failure. + FailedWithNoError, + /// Indicates that a trap occurred that is not known to Wasmer. + UnknownTrap { + /// The address that the trap occurred at. + address: usize, + /// The name of the signal. + signal: &'static str, + }, + /// A trap that Wasmer knows about occurred. + TrapCode { + /// The type of exception. + code: ExceptionCode, + /// Where in the Wasm file this trap orginated from. + srcloc: u32, + }, + /// A trap occurred that Wasmer knows about but it had a trap code that + /// we weren't expecting or that we do not handle. This error may be backend-specific. + UnknownTrapCode { + /// The trap code we saw but did not recognize. + trap_code: String, + /// Where in the Wasm file this trap orginated from. + srcloc: u32, + }, + /// An "early trap" occurred. TODO: document this properly + EarlyTrap(Box), + /// Indicates that a breakpoint was hit. The inner value is dependent upon + /// the middleware or backend being used. + Breakpoint(Box), +} + +impl From for RuntimeError { + fn from(other: InvokeError) -> RuntimeError { + match other { + InvokeError::EarlyTrap(re) | InvokeError::Breakpoint(re) => *re, + _ => RuntimeError::InvokeError(other), + } + } +} + +impl std::error::Error for InvokeError {} + +impl std::fmt::Display for InvokeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + InvokeError::FailedWithNoError => write!(f, "Invoke failed with no error"), + InvokeError::UnknownTrap { address, signal } => write!( + f, + "An unknown trap (`{}`) occured at 0x{:X}", + signal, address + ), + InvokeError::TrapCode { code, srcloc } => { + write!(f, "A `{}` trap was thrown at code offset {}", code, srcloc) + } + InvokeError::UnknownTrapCode { trap_code, srcloc } => write!( + f, + "A trap with an unknown trap code (`{}`) was thrown at code offset {}", + trap_code, srcloc + ), + InvokeError::EarlyTrap(rte) => write!(f, "Early trap: {}", rte), + InvokeError::Breakpoint(rte) => write!(f, "Breakpoint hit: {}", rte), + } + } +} + +/// An `InternalError` is an error that happened inside of Wasmer and is a +/// catch-all for errors that would otherwise be returned as +/// `RuntimeError(Box::new())`. /// -/// Comparing two `RuntimeError`s always evaluates to false. -pub struct RuntimeError(pub Box); +/// This type provides greater visibility into the kinds of things that may fail +/// and improves the ability of users to handle them, though these errors may be +/// extremely rare and impossible to handle. +#[derive(Debug)] +pub enum RuntimeError { + /// An error relating to the invocation of a Wasm function. + InvokeError(InvokeError), + /// A metering triggered error value. + /// + /// An error of this type indicates that it was returned by the metering system. + Metering(Box), + /// A frozen state of Wasm used to pause and resume execution. Not strictly an + /// "error", but this happens while executing and therefore is a `RuntimeError` + /// from the persective of the caller that expected the code to fully execute. + InstanceImage(Box), + /// A user triggered error value. + /// + /// An error returned from a host function. + User(Box), +} impl PartialEq for RuntimeError { fn eq(&self, _other: &RuntimeError) -> bool { @@ -187,29 +273,33 @@ impl PartialEq for RuntimeError { } } +impl std::error::Error for RuntimeError {} + impl std::fmt::Display for RuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let data = &*self.0; - if let Some(s) = data.downcast_ref::() { - write!(f, "\"{}\"", s) - } else if let Some(s) = data.downcast_ref::<&str>() { - write!(f, "\"{}\"", s) - } else if let Some(exc_code) = data.downcast_ref::() { - write!(f, "Caught exception of type \"{:?}\".", exc_code) - } else { - write!(f, "unknown error") + match self { + RuntimeError::InvokeError(ie) => write!(f, "Error when calling invoke: {}", ie), + RuntimeError::Metering(_) => write!(f, "unknown metering error type"), + RuntimeError::InstanceImage(_) => write!( + f, + "Execution interrupted by a suspend signal: instance image returned" + ), + RuntimeError::User(user_error) => { + write!(f, "User supplied error: ")?; + if let Some(s) = user_error.downcast_ref::() { + write!(f, "\"{}\"", s) + } else if let Some(s) = user_error.downcast_ref::<&str>() { + write!(f, "\"{}\"", s) + } else if let Some(n) = user_error.downcast_ref::() { + write!(f, "{}", n) + } else { + write!(f, "unknown user error type") + } + } } } } -impl std::fmt::Debug for RuntimeError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self) - } -} - -impl std::error::Error for RuntimeError {} - /// This error type is produced by resolving a wasm function /// given its name. /// diff --git a/lib/runtime-core/src/fault.rs b/lib/runtime-core/src/fault.rs index d8c985e09b9..4d7d919acf8 100644 --- a/lib/runtime-core/src/fault.rs +++ b/lib/runtime-core/src/fault.rs @@ -28,6 +28,7 @@ pub mod raw { } use crate::codegen::{BreakpointInfo, BreakpointMap}; +use crate::error::{InvokeError, RuntimeError}; use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR}; use crate::state::{CodeVersion, ExecutionStateImage}; use crate::vm; @@ -36,7 +37,6 @@ use nix::sys::signal::{ sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTRAP, }; -use std::any::Any; use std::cell::{Cell, RefCell, UnsafeCell}; use std::ffi::c_void; use std::process; @@ -61,7 +61,7 @@ type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN]; struct UnwindInfo { jmpbuf: SetJmpBuffer, // in breakpoints: Option, - payload: Option>, // out + payload: Option>, // out } /// A store for boundary register preservation. @@ -182,7 +182,7 @@ pub unsafe fn clear_wasm_interrupt() { pub unsafe fn catch_unsafe_unwind R>( f: F, breakpoints: Option, -) -> Result> { +) -> Result { let unwind = UNWIND.with(|x| x.get()); let old = (*unwind).take(); *unwind = Some(UnwindInfo { @@ -195,7 +195,7 @@ pub unsafe fn catch_unsafe_unwind R>( // error let ret = (*unwind).as_mut().unwrap().payload.take().unwrap(); *unwind = old; - Err(ret) + Err(*ret) } else { let ret = f(); // implicit control flow to the error case... @@ -205,7 +205,7 @@ pub unsafe fn catch_unsafe_unwind R>( } /// Begins an unsafe unwind. -pub unsafe fn begin_unsafe_unwind(e: Box) -> ! { +pub unsafe fn begin_unsafe_unwind(e: Box) -> ! { let unwind = UNWIND.with(|x| x.get()); let inner = (*unwind) .as_mut() @@ -279,7 +279,11 @@ extern "C" fn signal_trap_handler( static ARCH: Architecture = Architecture::Aarch64; let mut should_unwind = false; - let mut unwind_result: Box = Box::new(()); + let mut unwind_result: Option> = None; + let get_unwind_result = |uw_result: Option>| -> Box { + uw_result + .unwrap_or_else(|| Box::new(RuntimeError::InvokeError(InvokeError::FailedWithNoError))) + }; unsafe { let fault = get_fault_info(siginfo as _, ucontext); @@ -302,7 +306,7 @@ extern "C" fn signal_trap_handler( ) { match ib.ty { InlineBreakpointType::Middleware => { - let out: Option>> = + let out: Option> = with_breakpoint_map(|bkpt_map| { bkpt_map.and_then(|x| x.get(&ip)).map(|x| { x(BreakpointInfo { @@ -313,7 +317,7 @@ extern "C" fn signal_trap_handler( if let Some(Ok(())) = out { } else if let Some(Err(e)) = out { should_unwind = true; - unwind_result = e; + unwind_result = Some(Box::new(e)); } } } @@ -328,7 +332,7 @@ extern "C" fn signal_trap_handler( }) }); if should_unwind { - begin_unsafe_unwind(unwind_result); + begin_unsafe_unwind(get_unwind_result(unwind_result)); } if early_return { return; @@ -342,20 +346,22 @@ extern "C" fn signal_trap_handler( match Signal::from_c_int(signum) { Ok(SIGTRAP) => { // breakpoint - let out: Option>> = - with_breakpoint_map(|bkpt_map| { - bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| { - x(BreakpointInfo { - fault: Some(&fault), - }) - }) + let out: Option> = + with_breakpoint_map(|bkpt_map| -> Option> { + bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map( + |x| -> Result<(), RuntimeError> { + x(BreakpointInfo { + fault: Some(&fault), + }) + }, + ) }); match out { Some(Ok(())) => { return false; } Some(Err(e)) => { - unwind_result = e; + unwind_result = Some(Box::new(e)); return true; } None => {} @@ -387,7 +393,7 @@ extern "C" fn signal_trap_handler( if is_suspend_signal { // If this is a suspend signal, we parse the runtime state and return the resulting image. let image = build_instance_image(ctx, es_image); - unwind_result = Box::new(image); + unwind_result = Some(Box::new(RuntimeError::InstanceImage(Box::new(image)))); } else { // Otherwise, this is a real exception and we just throw it to the caller. if !es_image.frames.is_empty() { @@ -415,7 +421,12 @@ extern "C" fn signal_trap_handler( None }); if let Some(code) = exc_code { - unwind_result = Box::new(code); + unwind_result = + Some(Box::new(RuntimeError::InvokeError(InvokeError::TrapCode { + code, + // TODO: + srcloc: 0, + }))); } } @@ -423,7 +434,7 @@ extern "C" fn signal_trap_handler( }); if should_unwind { - begin_unsafe_unwind(unwind_result); + begin_unsafe_unwind(get_unwind_result(unwind_result)); } } } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 48e59d4ec65..7fa7a687261 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -5,7 +5,7 @@ use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, - error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError}, + error::{CallResult, InvokeError, ResolveError, ResolveResult, Result, RuntimeError}, export::{Context, Export, ExportIter, Exportable, FuncPointer}, global::Global, import::{ImportObject, LikeNamespace}, @@ -584,25 +584,30 @@ pub(crate) fn call_func_with_index_inner( invoke_env, } = wasm; - let run_wasm = |result_space: *mut u64| unsafe { + let run_wasm = |result_space: *mut u64| -> CallResult<()> { let mut error_out = None; - let success = invoke( - trampoline, - ctx_ptr, - func_ptr, - raw_args.as_ptr(), - result_space, - &mut error_out, - invoke_env, - ); + let success = unsafe { + invoke( + trampoline, + ctx_ptr, + func_ptr, + raw_args.as_ptr(), + result_space, + &mut error_out, + invoke_env, + ) + }; if success { Ok(()) } else { - Err(error_out - .map(RuntimeError) - .unwrap_or_else(|| RuntimeError(Box::new("invoke(): Unknown error".to_string())))) + let error: RuntimeError = error_out.map_or_else( + || RuntimeError::InvokeError(InvokeError::FailedWithNoError), + Into::into, + ); + dbg!(&error); + Err(error.into()) } }; diff --git a/lib/runtime-core/src/state.rs b/lib/runtime-core/src/state.rs index 55809e965d7..d7d5a6ee1a8 100644 --- a/lib/runtime-core/src/state.rs +++ b/lib/runtime-core/src/state.rs @@ -699,13 +699,13 @@ pub mod x64 { pub use super::x64_decl::*; use super::*; use crate::codegen::BreakpointMap; + use crate::error::RuntimeError; use crate::fault::{ catch_unsafe_unwind, get_boundary_register_preservation, run_on_alternative_stack, }; use crate::structures::TypedIndex; use crate::types::LocalGlobalIndex; use crate::vm::Ctx; - use std::any::Any; #[allow(clippy::cast_ptr_alignment)] unsafe fn compute_vmctx_deref(vmctx: *const Ctx, seq: &[usize]) -> u64 { @@ -738,7 +738,7 @@ pub mod x64 { image: InstanceImage, vmctx: &mut Ctx, breakpoints: Option, - ) -> Result> { + ) -> Result { let mut stack: Vec = vec![0; 1048576 * 8 / 8]; // 8MB stack let mut stack_offset: usize = stack.len(); diff --git a/lib/runtime-core/src/tiering.rs b/lib/runtime-core/src/tiering.rs index 83ebfd47159..2aa324232f4 100644 --- a/lib/runtime-core/src/tiering.rs +++ b/lib/runtime-core/src/tiering.rs @@ -2,6 +2,7 @@ //! as runtime. use crate::backend::{Compiler, CompilerConfig}; use crate::compile_with_config; +use crate::error::RuntimeError; use crate::fault::{ catch_unsafe_unwind, ensure_sighandler, pop_code_version, push_code_version, with_ctx, }; @@ -223,23 +224,26 @@ pub unsafe fn run_tiering ShellExitOperation>( } }); if let Err(e) = ret { - if let Ok(new_image) = e.downcast::() { + match e { // Tier switch event - if !was_sigint_triggered_fault() && opt_state.outcome.lock().unwrap().is_some() { - resume_image = Some(*new_image); - continue; - } - let op = interactive_shell(InteractiveShellContext { - image: Some(*new_image), - patched: n_versions.get() > 1, - }); - match op { - ShellExitOperation::ContinueWith(new_image) => { - resume_image = Some(new_image); + RuntimeError::InstanceImage(ii_value) => { + let new_image = ii_value.downcast::().unwrap(); + if !was_sigint_triggered_fault() && opt_state.outcome.lock().unwrap().is_some() + { + resume_image = Some(*new_image); + continue; + } + let op = interactive_shell(InteractiveShellContext { + image: Some(*new_image), + patched: n_versions.get() > 1, + }); + match op { + ShellExitOperation::ContinueWith(new_image) => { + resume_image = Some(new_image); + } } } - } else { - return Err("Error while executing WebAssembly".into()); + _ => return Err("Error while executing WebAssembly".into()), } } else { return Ok(()); diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index a6337bc5888..5cef19ca4dc 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -1,7 +1,7 @@ //! The typed func module implements a way of representing a wasm function //! with the correct types from rust. Function calls using a typed func have a low overhead. use crate::{ - error::RuntimeError, + error::{InvokeError, RuntimeError}, export::{Context, Export, FuncPointer}, import::IsExport, types::{FuncSig, NativeWasmType, Type, WasmExternType}, @@ -37,7 +37,7 @@ pub type Invoke = unsafe extern "C" fn( func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, extra: Option>, ) -> bool; @@ -340,7 +340,9 @@ impl<'a> DynamicFunc<'a> { Err(e) => { // At this point, there is an error that needs to be trapped. drop(args); // Release the Vec which will leak otherwise. - (&*vmctx.module).runnable_module.do_early_trap(e) + (&*vmctx.module) + .runnable_module + .do_early_trap(RuntimeError::User(e)) } } } @@ -588,9 +590,7 @@ macro_rules! impl_traits { ) { Ok(Rets::from_ret_array(rets)) } else { - Err(error_out.map(RuntimeError).unwrap_or_else(|| { - RuntimeError(Box::new("invoke(): Unknown error".to_string())) - })) + Err(error_out.map_or_else(|| RuntimeError::InvokeError(InvokeError::FailedWithNoError), Into::into)) } } } @@ -678,9 +678,10 @@ macro_rules! impl_traits { Ok(Ok(returns)) => return returns.into_c_struct(), Ok(Err(err)) => { let b: Box<_> = err.into(); - b as Box + RuntimeError::User(b as Box) }, - Err(err) => err, + // TODO(blocking): this line is wrong! + Err(err) => RuntimeError::User(err), }; // At this point, there is an error that needs to @@ -791,9 +792,10 @@ macro_rules! impl_traits { Ok(Ok(returns)) => return returns.into_c_struct(), Ok(Err(err)) => { let b: Box<_> = err.into(); - b as Box + RuntimeError::User(b as Box) }, - Err(err) => err, + // TODO(blocking): this line is wrong! + Err(err) => RuntimeError::User(err), }; // At this point, there is an error that needs to diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index a9a1a2ad9b0..874d18dc9b9 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -1079,10 +1079,10 @@ mod vm_ctx_tests { use super::Func; use crate::backend::{sys::Memory, CacheGen, RunnableModule}; use crate::cache::Error as CacheError; + use crate::error::RuntimeError; use crate::typed_func::Wasm; use crate::types::{LocalFuncIndex, SigIndex}; use indexmap::IndexMap; - use std::any::Any; use std::collections::HashMap; use std::ptr::NonNull; struct Placeholder; @@ -1098,7 +1098,7 @@ mod vm_ctx_tests { fn get_trampoline(&self, _module: &ModuleInfo, _sig_index: SigIndex) -> Option { unimplemented!("generate_module::get_trampoline") } - unsafe fn do_early_trap(&self, _: Box) -> ! { + unsafe fn do_early_trap(&self, _: RuntimeError) -> ! { unimplemented!("generate_module::do_early_trap") } } diff --git a/lib/runtime/examples/call.rs b/lib/runtime/examples/call.rs index 46ccc5aa693..2d6ee562de2 100644 --- a/lib/runtime/examples/call.rs +++ b/lib/runtime/examples/call.rs @@ -71,8 +71,11 @@ fn main() -> Result<(), error::Error> { println!("result: {:?}", result); if let Err(e) = result { - if let Ok(exit_code) = e.0.downcast::() { + if let RuntimeError::User(ue) = e { + let exit_code = ue.downcast_ref::().unwrap(); println!("exit code: {:?}", exit_code); + } else { + panic!("Found error that wasn't a user error!: {}", e) } } diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index e51db5b7c5b..bc666d1ff96 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -10,7 +10,6 @@ use dynasmrt::x64::Assembler; use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; use smallvec::SmallVec; use std::{ - any::Any, collections::{BTreeMap, HashMap}, ffi::c_void, iter, mem, @@ -27,6 +26,7 @@ use wasmer_runtime_core::{ }, cache::{Artifact, Error as CacheError}, codegen::*, + error::{InvokeError, RuntimeError}, fault::{self, raw::register_preservation_trampoline}, loader::CodeMemory, memory::MemoryType, @@ -214,7 +214,7 @@ pub struct X64FunctionCode { breakpoints: Option< HashMap< AssemblyOffset, - Box Result<(), Box> + Send + Sync + 'static>, + Box Result<(), RuntimeError> + Send + Sync + 'static>, >, >, returns: SmallVec<[WpType; 1]>, @@ -507,7 +507,7 @@ impl RunnableModule for X64ExecutionContext { func: NonNull, args: *const u64, rets: *mut u64, - error_out: *mut Option>, + error_out: *mut Option, num_params_plus_one: Option>, ) -> bool { let rm: &Box = &(&*(*ctx).module).runnable_module; @@ -655,7 +655,7 @@ impl RunnableModule for X64ExecutionContext { true } Err(err) => { - *error_out = Some(err); + *error_out = Some(InvokeError::Breakpoint(Box::new(err)).into()); false } }; @@ -680,8 +680,8 @@ impl RunnableModule for X64ExecutionContext { }) } - unsafe fn do_early_trap(&self, data: Box) -> ! { - fault::begin_unsafe_unwind(data); + unsafe fn do_early_trap(&self, data: RuntimeError) -> ! { + fault::begin_unsafe_unwind(Box::new(data)); } fn get_code(&self) -> Option<&[u8]> { diff --git a/src/commands/run.rs b/src/commands/run.rs index b58075fd777..12d3335e90d 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -21,6 +21,7 @@ use wasmer_runtime::{ use wasmer_runtime_core::{ self, backend::{Compiler, CompilerConfig, MemoryBoundCheckMode}, + error::RuntimeError, loader::{Instance as LoadedInstance, LocalLoader}, Module, }; @@ -437,9 +438,12 @@ fn execute_wasi( } if let Err(ref err) = result { - if let Some(error_code) = err.0.downcast_ref::() { - std::process::exit(error_code.code as i32) + if let RuntimeError::User(user_error) = err { + if let Some(error_code) = user_error.downcast_ref::() { + std::process::exit(error_code.code as i32) + } } + return Err(format!("error: {:?}", err)); } } diff --git a/tests/high_level_api.rs b/tests/high_level_api.rs index 389e20c411b..5d9ae70fb03 100644 --- a/tests/high_level_api.rs +++ b/tests/high_level_api.rs @@ -268,7 +268,7 @@ wasmer_backends! { let result = foo.call(); - if let Err(RuntimeError(e)) = result { + if let Err(RuntimeError::User(e)) = result { let exit_code = e.downcast::().unwrap(); assert_eq!(exit_code.code, 42); } else { diff --git a/tests/imports.rs b/tests/imports.rs index e4ce076719c..ed1a2da81d7 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -52,7 +52,7 @@ macro_rules! call_and_assert { expected_value, concat!("Expected right when calling `", stringify!($function), "`.") ), - (Err(RuntimeError(data)), Err(RuntimeError(expected_data))) => { + (Err(RuntimeError::User(data)), Err(RuntimeError::User(expected_data))) => { if let (Some(data), Some(expected_data)) = ( data.downcast_ref::<&str>(), expected_data.downcast_ref::<&str>(), @@ -406,7 +406,7 @@ wasmer_backends! { test!( test_fn, function_fn(i32) -> i32, (1) == Ok(2)); test!( test_closure, function_closure(i32) -> i32, (1) == Ok(2)); test!( test_fn_dynamic, function_fn_dynamic(i32) -> i32, (1) == Ok(2)); - test!( test_fn_dynamic_panic, function_fn_dynamic_panic(i32) -> i32, (1) == Err(RuntimeError(Box::new("test")))); + test!( test_fn_dynamic_panic, function_fn_dynamic_panic(i32) -> i32, (1) == Err(RuntimeError::User(Box::new("test")))); test!( test_closure_dynamic_0, @@ -460,31 +460,31 @@ wasmer_backends! { test_fn_trap, function_fn_trap(i32) -> i32, - (1) == Err(RuntimeError(Box::new(format!("foo {}", 2)))) + (1) == Err(RuntimeError::User(Box::new(format!("foo {}", 2)))) ); test!( test_closure_trap, function_closure_trap(i32) -> i32, - (1) == Err(RuntimeError(Box::new(format!("bar {}", 2)))) + (1) == Err(RuntimeError::User(Box::new(format!("bar {}", 2)))) ); test!( test_fn_trap_with_vmctx, function_fn_trap_with_vmctx(i32) -> i32, - (1) == Err(RuntimeError(Box::new(format!("baz {}", 2 + SHIFT)))) + (1) == Err(RuntimeError::User(Box::new(format!("baz {}", 2 + SHIFT)))) ); test!( test_closure_trap_with_vmctx, function_closure_trap_with_vmctx(i32) -> i32, - (1) == Err(RuntimeError(Box::new(format!("qux {}", 2 + SHIFT)))) + (1) == Err(RuntimeError::User(Box::new(format!("qux {}", 2 + SHIFT)))) ); test!( test_closure_trap_with_vmctx_and_env, function_closure_trap_with_vmctx_and_env(i32) -> i32, - (1) == Err(RuntimeError(Box::new(format!("! {}", 2 + shift + SHIFT)))) + (1) == Err(RuntimeError::User(Box::new(format!("! {}", 2 + shift + SHIFT)))) ); #[test] diff --git a/tests/middleware_common.rs b/tests/middleware_common.rs index 90516af0c30..ca423661df9 100644 --- a/tests/middleware_common.rs +++ b/tests/middleware_common.rs @@ -6,6 +6,7 @@ use wasmer::wasm::Func; use wasmer_middleware_common::metering::*; use wasmer_runtime_core::codegen::ModuleCodeGenerator; use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; +use wasmer_runtime_core::error::RuntimeError; use wasmer_runtime_core::fault::{pop_code_version, push_code_version}; use wasmer_runtime_core::state::CodeVersion; @@ -179,10 +180,13 @@ fn middleware_test_traps_after_costly_call(backend: &'static str, compiler: impl } let err = result.unwrap_err(); - assert!(err - .0 - .downcast_ref::() - .is_some()); + if let RuntimeError::Metering(metering_err) = err { + assert!(metering_err + .downcast_ref::() + .is_some()); + } else { + assert!(false, "metering error not found"); + } // verify it used the correct number of points assert_eq!(get_points_used(&instance), 109); // Used points will be slightly more than `limit` because of the way we do gas checking.