diff --git a/lib/clif-backend/src/lib.rs b/lib/clif-backend/src/lib.rs index c1d9576c2..0d5df6d58 100644 --- a/lib/clif-backend/src/lib.rs +++ b/lib/clif-backend/src/lib.rs @@ -11,7 +11,11 @@ use cranelift_codegen::{ settings::{self, Configurable}, }; use target_lexicon::Triple; -use wasmer_runtime::{backend::Compiler, module::ModuleInner}; +use wasmer_runtime::{ + backend::Compiler, + error::{CompileError, CompileResult}, + module::ModuleInner, +}; use wasmparser::{self, WasmDecoder}; pub struct CraneliftCompiler {} @@ -24,7 +28,7 @@ impl CraneliftCompiler { impl Compiler for CraneliftCompiler { // Compiles wasm binary to a wasmer module. - fn compile(&self, wasm: &[u8]) -> Result { + fn compile(&self, wasm: &[u8]) -> CompileResult { validate(wasm)?; let isa = get_isa(); @@ -53,15 +57,15 @@ fn get_isa() -> Box { isa::lookup(Triple::host()).unwrap().finish(flags) } -fn validate(bytes: &[u8]) -> Result<(), String> { +fn validate(bytes: &[u8]) -> CompileResult<()> { let mut parser = wasmparser::ValidatingParser::new(bytes, None); loop { let state = parser.read(); match *state { wasmparser::ParserState::EndWasm => break Ok(()), - wasmparser::ParserState::Error(err) => { - return Err(format!("Validation error: {}", err.message)); - } + wasmparser::ParserState::Error(err) => Err(CompileError::ValidationError { + msg: err.message.to_string(), + })?, _ => {} } } diff --git a/lib/clif-backend/src/module.rs b/lib/clif-backend/src/module.rs index 2e5f01dca..f5acc796f 100644 --- a/lib/clif-backend/src/module.rs +++ b/lib/clif-backend/src/module.rs @@ -10,6 +10,7 @@ use std::{ use wasmer_runtime::{ backend::FuncResolver, backend::SigRegistry, + error::CompileResult, module::ModuleInner, structures::{Map, TypedIndex}, types::{ @@ -67,7 +68,7 @@ impl Module { mut self, isa: &isa::TargetIsa, functions: Map, - ) -> Result { + ) -> CompileResult { // we have to deduplicate `module.func_assoc` let func_assoc = &mut self.module.func_assoc; let sig_registry = &self.module.sig_registry; diff --git a/lib/clif-backend/src/module_env.rs b/lib/clif-backend/src/module_env.rs index 7adb9ea0f..a8476f284 100644 --- a/lib/clif-backend/src/module_env.rs +++ b/lib/clif-backend/src/module_env.rs @@ -5,6 +5,7 @@ use crate::{ use cranelift_codegen::{ir, isa}; use cranelift_wasm::{self, translate_module, FuncTranslator, ModuleEnvironment}; use wasmer_runtime::{ + error::{CompileError, CompileResult}, module::{DataInitializer, ExportIndex, ImportName, TableInitializer}, structures::{Map, TypedIndex}, types::{ @@ -32,8 +33,9 @@ impl<'module, 'isa> ModuleEnv<'module, 'isa> { } } - pub fn translate(mut self, wasm: &[u8]) -> Result, String> { - translate_module(wasm, &mut self).map_err(|e| e.to_string())?; + pub fn translate(mut self, wasm: &[u8]) -> CompileResult> { + translate_module(wasm, &mut self) + .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; Ok(self.func_bodies) } } diff --git a/lib/clif-backend/src/resolver.rs b/lib/clif-backend/src/resolver.rs index c56d90263..6cc783be6 100644 --- a/lib/clif-backend/src/resolver.rs +++ b/lib/clif-backend/src/resolver.rs @@ -7,6 +7,7 @@ use std::ptr::{write_unaligned, NonNull}; use wasmer_runtime::{ self, backend::{self, Mmap, Protect}, + error::{CompileError, CompileResult}, structures::Map, types::LocalFuncIndex, vm, vmcalls, @@ -23,7 +24,7 @@ impl FuncResolverBuilder { pub fn new( isa: &isa::TargetIsa, function_bodies: Map, - ) -> Result { + ) -> CompileResult { let mut compiled_functions: Vec> = Vec::with_capacity(function_bodies.len()); let mut relocations = Map::with_capacity(function_bodies.len()); let mut trap_sinks = Map::with_capacity(function_bodies.len()); @@ -38,7 +39,7 @@ impl FuncResolverBuilder { let mut trap_sink = TrapSink::new(); ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) - .map_err(|e| format!("compile error: {}", e.to_string()))?; + .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; ctx.clear(); // Round up each function's size to pointer alignment. total_size += round_up(code_buf.len(), mem::size_of::()); @@ -48,11 +49,24 @@ impl FuncResolverBuilder { trap_sinks.push(trap_sink); } - let mut memory = Mmap::with_size(total_size)?; + let mut memory = Mmap::with_size(total_size) + .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; unsafe { - memory.protect(0..memory.size(), Protect::ReadWrite)?; + memory + .protect(0..memory.size(), Protect::ReadWrite) + .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; } + // Normally, excess memory due to alignment and page-rounding would + // be filled with null-bytes. On x86 (and x86_64), + // "\x00\x00" disassembles to "add byte ptr [eax],al". + // + // If the instruction pointer falls out of its designated area, + // it would be better if it would immediately crash instead of + // continuing on and causing non-local issues. + // + // "\xCC" disassembles to "int3", which will immediately cause + // an interrupt that we can catch if we want. for i in unsafe { memory.as_slice_mut() } { *i = 0xCC; } @@ -77,7 +91,7 @@ impl FuncResolverBuilder { }) } - pub fn finalize(mut self) -> Result { + pub fn finalize(mut self) -> CompileResult { for (index, relocs) in self.relocations.iter() { for ref reloc in relocs { let target_func_address: isize = match reloc.target { @@ -98,11 +112,17 @@ impl FuncResolverBuilder { ir::LibCall::NearestF64 => libcalls::nearbyintf64 as isize, ir::LibCall::Probestack => libcalls::__rust_probestack as isize, _ => { - panic!("unexpected libcall {}", libcall); + Err(CompileError::InternalError { + msg: format!("unexpected libcall: {}", libcall), + })? + // panic!("unexpected libcall {}", libcall); } }, RelocationType::Intrinsic(ref name) => { - panic!("unexpected intrinsic {}", name); + Err(CompileError::InternalError { + msg: format!("unexpected intrinsic: {}", name), + })? + // panic!("unexpected intrinsic {}", name); } RelocationType::VmCall(vmcall) => match vmcall { VmCall::LocalStaticMemoryGrow => vmcalls::local_static_memory_grow as _, @@ -141,7 +161,9 @@ impl FuncResolverBuilder { (target_func_address - reloc_address + reloc_addend) as i32; write_unaligned(reloc_address as *mut i32, reloc_delta_i32); }, - _ => panic!("unsupported reloc kind"), + _ => Err(CompileError::InternalError { + msg: format!("unsupported reloc kind: {}", reloc.reloc), + })?, } } } @@ -149,7 +171,8 @@ impl FuncResolverBuilder { unsafe { self.resolver .memory - .protect(0..self.resolver.memory.size(), Protect::ReadExec)?; + .protect(0..self.resolver.memory.size(), Protect::ReadExec) + .map_err(|e| CompileError::InternalError { msg: e.to_string() })?;; } Ok(self.resolver) diff --git a/lib/runtime/src/backend.rs b/lib/runtime/src/backend.rs index 5b7951bc2..4cc67a5fc 100644 --- a/lib/runtime/src/backend.rs +++ b/lib/runtime/src/backend.rs @@ -1,4 +1,4 @@ -use crate::{module::ModuleInner, types::LocalFuncIndex, vm}; +use crate::{error::CompileResult, module::ModuleInner, types::LocalFuncIndex, vm}; use std::ptr::NonNull; pub use crate::mmap::{Mmap, Protect}; @@ -6,7 +6,7 @@ pub use crate::sig_registry::SigRegistry; pub trait Compiler { /// Compiles a `Module` from WebAssembly binary format - fn compile(&self, wasm: &[u8]) -> Result; + fn compile(&self, wasm: &[u8]) -> CompileResult; } pub trait FuncResolver { diff --git a/lib/runtime/src/backing.rs b/lib/runtime/src/backing.rs index f84670d3d..289588176 100644 --- a/lib/runtime/src/backing.rs +++ b/lib/runtime/src/backing.rs @@ -1,4 +1,5 @@ use crate::{ + error::{LinkError, LinkResult}, export::{Context, Export}, import::Imports, memory::LinearMemory, @@ -102,7 +103,6 @@ impl LocalBacking { LocalOrImport::Local(local_memory_index) => { let memory_desc = &module.memories[local_memory_index]; let data_top = init_base + init.data.len(); - println!("data_top: {}", data_top); assert!((memory_desc.min * LinearMemory::PAGE_SIZE) as usize >= data_top); let mem: &mut LinearMemory = &mut memories[local_memory_index]; @@ -305,7 +305,7 @@ impl ImportBacking { module: &ModuleInner, imports: &mut Imports, vmctx: *mut vm::Ctx, - ) -> Result { + ) -> LinkResult { Ok(ImportBacking { functions: import_functions(module, imports, vmctx)?, memories: import_memories(module, imports, vmctx)?, @@ -323,7 +323,7 @@ fn import_functions( module: &ModuleInner, imports: &mut Imports, vmctx: *mut vm::Ctx, -) -> Result, String> { +) -> LinkResult> { let mut functions = Map::with_capacity(module.imported_functions.len()); for (index, ImportName { namespace, name }) in &module.imported_functions { let sig_index = module.func_assoc[index.convert_up(module)]; @@ -346,18 +346,33 @@ fn import_functions( }, }); } else { - return Err(format!( - "unexpected signature for {:?}:{:?}", - namespace, name - )); + Err(LinkError::IncorrectImportSignature { + namespace: namespace.clone(), + name: name.clone(), + expected: expected_sig.clone(), + found: signature.clone(), + })? } } - Some(_) => { - return Err(format!("incorrect import type for {}:{}", namespace, name)); - } - None => { - return Err(format!("import not found: {}:{}", namespace, name)); + Some(export_type) => { + let export_type_name = match export_type { + Export::Function { .. } => "function", + Export::Memory { .. } => "memory", + Export::Table { .. } => "table", + Export::Global { .. } => "global", + } + .to_string(); + Err(LinkError::IncorrectImportType { + namespace: namespace.clone(), + name: name.clone(), + expected: "function".to_string(), + found: export_type_name, + })? } + None => Err(LinkError::ImportNotFound { + namespace: namespace.clone(), + name: name.clone(), + })?, } } Ok(functions.into_boxed_map()) @@ -367,7 +382,7 @@ fn import_memories( module: &ModuleInner, imports: &mut Imports, vmctx: *mut vm::Ctx, -) -> Result, String> { +) -> LinkResult> { let mut memories = Map::with_capacity(module.imported_memories.len()); for (_index, (ImportName { namespace, name }, expected_memory_desc)) in &module.imported_memories @@ -390,18 +405,33 @@ fn import_memories( }, }); } else { - return Err(format!( - "incorrect memory description for {}:{}", - namespace, name, - )); + Err(LinkError::IncorrectMemoryDescription { + namespace: namespace.clone(), + name: name.clone(), + expected: expected_memory_desc.clone(), + found: memory_desc.clone(), + })? } } - Some(_) => { - return Err(format!("incorrect import type for {}:{}", namespace, name)); - } - None => { - return Err(format!("import not found: {}:{}", namespace, name)); + Some(export_type) => { + let export_type_name = match export_type { + Export::Function { .. } => "function", + Export::Memory { .. } => "memory", + Export::Table { .. } => "table", + Export::Global { .. } => "global", + } + .to_string(); + Err(LinkError::IncorrectImportType { + namespace: namespace.clone(), + name: name.clone(), + expected: "memory".to_string(), + found: export_type_name, + })? } + None => Err(LinkError::ImportNotFound { + namespace: namespace.clone(), + name: name.clone(), + })?, } } Ok(memories.into_boxed_map()) @@ -411,7 +441,7 @@ fn import_tables( module: &ModuleInner, imports: &mut Imports, vmctx: *mut vm::Ctx, -) -> Result, String> { +) -> LinkResult> { let mut tables = Map::with_capacity(module.imported_tables.len()); for (_index, (ImportName { namespace, name }, expected_table_desc)) in &module.imported_tables { let table_import = imports @@ -432,18 +462,33 @@ fn import_tables( }, }); } else { - return Err(format!( - "incorrect table description for {}:{}", - namespace, name, - )); + Err(LinkError::IncorrectTableDescription { + namespace: namespace.clone(), + name: name.clone(), + expected: expected_table_desc.clone(), + found: table_desc.clone(), + })? } } - Some(_) => { - return Err(format!("incorrect import type for {}:{}", namespace, name)); - } - None => { - return Err(format!("import not found: {}:{}", namespace, name)); + Some(export_type) => { + let export_type_name = match export_type { + Export::Function { .. } => "function", + Export::Memory { .. } => "memory", + Export::Table { .. } => "table", + Export::Global { .. } => "global", + } + .to_string(); + Err(LinkError::IncorrectImportType { + namespace: namespace.clone(), + name: name.clone(), + expected: "table".to_string(), + found: export_type_name, + })? } + None => Err(LinkError::ImportNotFound { + namespace: namespace.clone(), + name: name.clone(), + })?, } } Ok(tables.into_boxed_map()) @@ -452,7 +497,7 @@ fn import_tables( fn import_globals( module: &ModuleInner, imports: &mut Imports, -) -> Result, String> { +) -> LinkResult> { let mut globals = Map::with_capacity(module.imported_globals.len()); for (_, (ImportName { namespace, name }, imported_global_desc)) in &module.imported_globals { let import = imports @@ -465,18 +510,33 @@ fn import_globals( global: local.inner(), }); } else { - return Err(format!( - "unexpected global description for {:?}:{:?}", - namespace, name - )); + Err(LinkError::IncorrectGlobalDescription { + namespace: namespace.clone(), + name: name.clone(), + expected: imported_global_desc.clone(), + found: global.clone(), + })? } } - Some(_) => { - return Err(format!("incorrect import type for {}:{}", namespace, name)); - } - None => { - return Err(format!("import not found: {}:{}", namespace, name)); + Some(export_type) => { + let export_type_name = match export_type { + Export::Function { .. } => "function", + Export::Memory { .. } => "memory", + Export::Table { .. } => "table", + Export::Global { .. } => "global", + } + .to_string(); + Err(LinkError::IncorrectImportType { + namespace: namespace.clone(), + name: name.clone(), + expected: "global".to_string(), + found: export_type_name, + })? } + None => Err(LinkError::ImportNotFound { + namespace: namespace.clone(), + name: name.clone(), + })?, } } Ok(globals.into_boxed_map()) diff --git a/lib/runtime/src/error.rs b/lib/runtime/src/error.rs new file mode 100644 index 000000000..8977fd270 --- /dev/null +++ b/lib/runtime/src/error.rs @@ -0,0 +1,116 @@ +use crate::types::{FuncSig, GlobalDesc, Memory, MemoryIndex, Table, TableIndex, Type}; + +pub type Result = std::result::Result>; +pub type CompileResult = std::result::Result>; +pub type LinkResult = std::result::Result>; +pub type RuntimeResult = std::result::Result>; +pub type CallResult = std::result::Result>; + +/// This is returned when the chosen compiler is unable to +/// successfully compile the provided webassembly module into +/// a `Module`. +#[derive(Debug, Clone)] +pub enum CompileError { + ValidationError { msg: String }, + InternalError { msg: String }, +} + +/// This is returned when the runtime is unable to +/// correctly link the module with the provided imports. +#[derive(Debug, Clone)] +pub enum LinkError { + IncorrectImportType { + namespace: String, + name: String, + expected: String, + found: String, + }, + IncorrectImportSignature { + namespace: String, + name: String, + expected: FuncSig, + found: FuncSig, + }, + ImportNotFound { + namespace: String, + name: String, + }, + IncorrectMemoryDescription { + namespace: String, + name: String, + expected: Memory, + found: Memory, + }, + IncorrectTableDescription { + namespace: String, + name: String, + expected: Table, + found: Table, + }, + IncorrectGlobalDescription { + namespace: String, + name: String, + expected: GlobalDesc, + found: GlobalDesc, + }, +} + +/// This is the error type returned when calling +/// a webassembly function. +/// +/// The main way to do this is `Instance.call`. +#[derive(Debug, Clone)] +pub enum RuntimeError { + OutOfBoundsAccess { memory: MemoryIndex, addr: u32 }, + IndirectCallSignature { table: TableIndex }, + IndirectCallToNull { table: TableIndex }, + Unknown { msg: String }, +} + +/// This error type is produced by calling a wasm function +/// exported from a module. +/// +/// If the module traps in some way while running, this will +/// be the `CallError::Runtime(RuntimeError)` variant. +#[derive(Debug, Clone)] +pub enum CallError { + Signature { expected: FuncSig, found: Vec }, + NoSuchExport { name: String }, + ExportNotFunc { name: String }, + Runtime(Box), +} + +/// The amalgamation of all errors that can occur +/// during the compilation, instantiation, or execution +/// of a webassembly module. +#[derive(Debug, Clone)] +pub enum Error { + CompileError(Box), + LinkError(Box), + RuntimeError(Box), + CallError(Box), +} + +impl From> for Box { + fn from(compile_err: Box) -> Self { + Box::new(Error::CompileError(compile_err)) + } +} + +impl From> for Box { + fn from(link_err: Box) -> Self { + Box::new(Error::LinkError(link_err)) + } +} + +impl From> for Box { + fn from(runtime_err: Box) -> Self { + Box::new(Error::RuntimeError(runtime_err)) + } +} + +impl From> for Box { + fn from(call_err: Box) -> Self { + Box::new(Error::CallError(call_err)) + } +} diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index e9b23a7bd..080feeb14 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -1,6 +1,7 @@ use crate::recovery::call_protected; use crate::{ backing::{ImportBacking, LocalBacking}, + error::{CallError, CallResult, Result}, export::{ Context, Export, ExportIter, FuncPointer, GlobalPointer, MemoryPointer, TablePointer, }, @@ -31,10 +32,7 @@ pub struct Instance { } impl Instance { - pub(crate) fn new( - module: Rc, - mut imports: Box, - ) -> Result { + pub(crate) fn new(module: Rc, mut imports: Box) -> Result { // We need the backing and import_backing to create a vm::Ctx, but we need // a vm::Ctx to create a backing and an import_backing. The solution is to create an // uninitialized vm::Ctx and then initialize it in-place. @@ -73,17 +71,22 @@ impl Instance { /// /// This will eventually return `Result>, String>` in /// order to support multi-value returns. - pub fn call(&mut self, name: &str, args: &[Value]) -> Result, String> { - let export_index = self - .module - .exports - .get(name) - .ok_or_else(|| format!("there is no export with that name: {}", name))?; + pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult> { + let export_index = + self.module + .exports + .get(name) + .ok_or_else(|| CallError::NoSuchExport { + name: name.to_string(), + })?; let func_index = if let ExportIndex::Func(func_index) = export_index { *func_index } else { - return Err("that export is not a function".to_string()); + return Err(CallError::ExportNotFunc { + name: name.to_string(), + } + .into()); }; self.call_with_index(func_index, args) @@ -103,7 +106,7 @@ impl Instance { &mut self, func_index: FuncIndex, args: &[Value], - ) -> Result, String> { + ) -> CallResult> { let (func_ref, ctx, signature) = self.inner.get_func_from_index(&self.module, func_index); let func_ptr = CodePtr::from_ptr(func_ref.inner() as _); @@ -118,7 +121,10 @@ impl Instance { ); if !signature.check_sig(args) { - return Err("incorrect signature".to_string()); + Err(CallError::Signature { + expected: signature.clone(), + found: args.iter().map(|val| val.ty()).collect(), + })? } let libffi_args: Vec<_> = args @@ -150,6 +156,7 @@ impl Instance { None }) }) + .map_err(|e| CallError::Runtime(e).into()) } } diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 7a48de2dd..b3803a963 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -7,6 +7,7 @@ mod macros; #[doc(hidden)] pub mod backend; mod backing; +pub mod error; pub mod export; pub mod import; mod instance; @@ -23,13 +24,14 @@ pub mod vm; #[doc(hidden)] pub mod vmcalls; +use self::error::CompileResult; pub use self::instance::Instance; #[doc(inline)] pub use self::module::Module; use std::rc::Rc; /// Compile a webassembly module using the provided compiler. -pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> Result { +pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult { compiler .compile(wasm) .map(|inner| module::Module::new(Rc::new(inner))) diff --git a/lib/runtime/src/module.rs b/lib/runtime/src/module.rs index 7c40c7ef0..8a4d890be 100644 --- a/lib/runtime/src/module.rs +++ b/lib/runtime/src/module.rs @@ -1,5 +1,6 @@ use crate::{ backend::FuncResolver, + error::Result, import::Imports, sig_registry::SigRegistry, structures::Map, @@ -47,7 +48,7 @@ impl Module { } /// Instantiate a webassembly module with the provided imports. - pub fn instantiate(&self, imports: Imports) -> Result { + pub fn instantiate(&self, imports: Imports) -> Result { Instance::new(Rc::clone(&self.0), Box::new(imports)) } } diff --git a/lib/runtime/src/recovery.rs b/lib/runtime/src/recovery.rs index 7a88e4ae3..ec7a7cbf2 100644 --- a/lib/runtime/src/recovery.rs +++ b/lib/runtime/src/recovery.rs @@ -4,7 +4,10 @@ //! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here //! unless you have memory unsafety elsewhere in your code. -use crate::sighandler::install_sighandler; +use crate::{ + error::{RuntimeError, RuntimeResult}, + sighandler::install_sighandler, +}; use nix::libc::siginfo_t; use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV}; use std::cell::{Cell, UnsafeCell}; @@ -23,7 +26,7 @@ thread_local! { pub static CAUGHT_ADDRESS: Cell = Cell::new(0); } -pub fn call_protected(f: impl FnOnce() -> T) -> Result { +pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -45,7 +48,11 @@ pub fn call_protected(f: impl FnOnce() -> T) -> Result { Err(_) => "error while getting the Signal", _ => "unkown trapped signal", }; - Err(format!("trap at {:#x} - {}", addr, signal)) + // When the trap-handler is fully implemented, this will return more information. + Err(RuntimeError::Unknown { + msg: format!("trap at {:#x} - {}", addr, signal), + } + .into()) } else { let ret = f(); // TODO: Switch stack? *jmp_buf = prev_jmp_buf;