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 98ce1dfa6..6fbb525da 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/emscripten/src/utils.rs b/lib/emscripten/src/utils.rs index c590801f8..573c07805 100644 --- a/lib/emscripten/src/utils.rs +++ b/lib/emscripten/src/utils.rs @@ -8,7 +8,7 @@ use std::os::raw::c_char; use std::slice; use std::sync::Arc; /// We check if a provided module is an Emscripten generated one -pub fn is_emscripten_module(module: &Arc) -> bool { +pub fn is_emscripten_module(module: &Module) -> bool { for (_, import_name) in &module.0.imported_functions { if import_name.name == "_emscripten_memcpy_big" && import_name.namespace == "env" { return true; diff --git a/lib/runtime/build/spectests.rs b/lib/runtime/build/spectests.rs index 71051a059..b4fa98ef7 100644 --- a/lib/runtime/build/spectests.rs +++ b/lib/runtime/build/spectests.rs @@ -81,6 +81,7 @@ use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime::import::Imports; use wasmer_runtime::types::Value; use wasmer_runtime::{{Instance, module::Module}}; +use wasmer_runtime::error::Result; static IMPORT_MODULE: &str = r#" (module @@ -610,11 +611,12 @@ fn {}_assert_malformed() {{ let func_name = format!("{}_action_invoke", self.command_name()); self.buffer.push_str( format!( - "fn {func_name}(instance: &mut Instance) -> Result<(), String> {{ + "fn {func_name}(instance: &mut Instance) -> Result<()> {{ println!(\"Executing function {{}}\", \"{func_name}\"); let result = instance.call(\"{field}\", &[{args_values}]); {assertion} - result.map(|_| ()) + result?; + Ok(()) }}\n", func_name = func_name, field = field, diff --git a/lib/runtime/examples/simple/main.rs b/lib/runtime/examples/simple/main.rs index d0b1a2fe0..981ca7ac9 100644 --- a/lib/runtime/examples/simple/main.rs +++ b/lib/runtime/examples/simple/main.rs @@ -2,6 +2,7 @@ use wabt::wat2wasm; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime::{ self as runtime, + error::Result, export::{Context, Export, FuncPointer}, import::{Imports, NamespaceMap}, types::{FuncSig, Type, Value}, @@ -10,7 +11,7 @@ use wasmer_runtime::{ static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm"); -fn main() -> Result<(), String> { +fn main() -> Result<()> { let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); let inner_module = runtime::compile(&wasm_binary, &CraneliftCompiler::new())?; 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 72c5c908e..662b00858 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]; @@ -306,7 +306,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)?, @@ -324,7 +324,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)]; @@ -347,18 +347,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()) @@ -368,7 +383,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 @@ -391,18 +406,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()) @@ -412,7 +442,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 @@ -433,18 +463,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()) @@ -453,7 +498,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 @@ -466,18 +511,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..3a350748b --- /dev/null +++ b/lib/runtime/src/error.rs @@ -0,0 +1,162 @@ +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`. +/// +/// Comparing two `CompileError`s always evaluates to false. +#[derive(Debug, Clone)] +pub enum CompileError { + ValidationError { msg: String }, + InternalError { msg: String }, +} + +impl PartialEq for CompileError { + fn eq(&self, _other: &CompileError) -> bool { + false + } +} + +/// This is returned when the runtime is unable to +/// correctly link the module with the provided imports. +/// +/// Comparing two `LinkError`s always evaluates to false. +#[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, + }, +} + +impl PartialEq for LinkError { + fn eq(&self, _other: &LinkError) -> bool { + false + } +} + +/// This is the error type returned when calling +/// a webassembly function. +/// +/// The main way to do this is `Instance.call`. +/// +/// Comparing two `RuntimeError`s always evaluates to false. +#[derive(Debug, Clone)] +pub enum RuntimeError { + OutOfBoundsAccess { memory: MemoryIndex, addr: u32 }, + IndirectCallSignature { table: TableIndex }, + IndirectCallToNull { table: TableIndex }, + Unknown { msg: String }, +} + +impl PartialEq for RuntimeError { + fn eq(&self, _other: &RuntimeError) -> bool { + false + } +} + +/// 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. +/// +/// Comparing two `CallError`s always evaluates to false. +#[derive(Debug, Clone)] +pub enum CallError { + Signature { expected: FuncSig, found: Vec }, + NoSuchExport { name: String }, + ExportNotFunc { name: String }, + Runtime(RuntimeError), +} + +impl PartialEq for CallError { + fn eq(&self, _other: &CallError) -> bool { + false + } +} + +/// The amalgamation of all errors that can occur +/// during the compilation, instantiation, or execution +/// of a webassembly module. +/// +/// Comparing two `Error`s always evaluates to false. +#[derive(Debug, Clone)] +pub enum Error { + CompileError(CompileError), + LinkError(LinkError), + RuntimeError(RuntimeError), + CallError(CallError), +} + +impl PartialEq for Error { + fn eq(&self, _other: &Error) -> bool { + false + } +} + +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)) + } +} + +impl From> for Box { + fn from(runtime_err: Box) -> Self { + Box::new(CallError::Runtime(*runtime_err)) + } +} diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index f4d64712f..075d4709b 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 @@ -132,7 +138,7 @@ impl Instance { .chain(iter::once(libffi_arg(&vmctx_ptr))) .collect(); - call_protected(|| { + Ok(call_protected(|| { signature .returns .first() @@ -149,7 +155,7 @@ impl Instance { } None }) - }) + })?) } } diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index b09e2f1dd..bda698fee 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -7,6 +7,7 @@ pub mod macros; #[doc(hidden)] pub mod backend; mod backing; +pub mod error; pub mod export; pub mod import; pub mod instance; @@ -23,14 +24,15 @@ pub mod vm; #[doc(hidden)] pub mod vmcalls; -pub use self::import::Imports; +use self::error::CompileResult; pub use self::instance::Instance; #[doc(inline)] pub use self::module::Module; +pub use self::error::Result; 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 e575689b9..7ca3e30bf 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; diff --git a/lib/runtime/tests/semantics.rs b/lib/runtime/tests/semantics.rs index aea478854..bd5735108 100644 --- a/lib/runtime/tests/semantics.rs +++ b/lib/runtime/tests/semantics.rs @@ -2,7 +2,10 @@ mod tests { use wabt::wat2wasm; use wasmer_clif_backend::CraneliftCompiler; - use wasmer_runtime::import::Imports; + use wasmer_runtime::{ + error::{CallError, RuntimeError}, + import::Imports, + }; // The semantics of stack overflow are documented at: // https://webassembly.org/docs/semantics/#stack-overflow @@ -25,15 +28,16 @@ mod tests { .instantiate(Imports::new()) .expect("WASM can't be instantiated"); let result = instance.call("stack-overflow", &[]); - assert!( - result.is_err(), - "should fail with error due to stack overflow" - ); - // TODO The kind of error and message needs to be defined, not spec defined, maybe RuntimeError or RangeError - if let Err(message) = result { - assert!(!message.contains("segmentation violation")); - assert!(!message.contains("bus error")); + + match result { + Err(err) => match *err { + CallError::Runtime(RuntimeError::Unknown { msg }) => { + assert!(!msg.contains("segmentation violation")); + assert!(!msg.contains("bus error")); + } + _ => unimplemented!(), + }, + Ok(_) => panic!("should fail with error due to stack overflow"), } } - } diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 90605b1bd..79d75f312 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -12,7 +12,7 @@ use structopt::StructOpt; use wasmer::*; use wasmer_emscripten; -use wasmer_runtime; +use wasmer_runtime as runtime; #[derive(Debug, StructOpt)] #[structopt(name = "wasmer", about = "WASM execution runtime.")] @@ -65,14 +65,14 @@ fn execute_wasm(options: &Run) -> Result<(), String> { if !webassembly::utils::is_wasm_binary(&wasm_binary) { wasm_binary = wabt::wat2wasm(wasm_binary) - .map_err(|err| format!("Can't convert from wast to wasm: {:?}", err))?; + .map_err(|e| format!("Can't convert from wast to wasm: {:?}", e))?; } let isa = webassembly::get_isa(); debug!("webassembly - creating module"); let module = webassembly::compile(&wasm_binary[..]) - .map_err(|err| format!("Can't create the WebAssembly module: {}", err))?; + .map_err(|e| format!("{:?}", e))?; let abi = if wasmer_emscripten::is_emscripten_module(&module) { webassembly::InstanceABI::Emscripten @@ -100,14 +100,14 @@ fn execute_wasm(options: &Run) -> Result<(), String> { let mut instance = module .instantiate(import_object) - .map_err(|err| format!("Can't instantiate the WebAssembly module: {}", err))?; + .map_err(|e| format!("{:?}", e))?; - webassembly::start_instance( - Arc::clone(&module), + Ok(webassembly::start_instance( + &module, &mut instance, options.path.to_str().unwrap(), options.args.iter().map(|arg| arg.as_str()).collect(), - ) + ).map_err(|e| format!("{:?}", e))?) } fn run(options: Run) { @@ -115,7 +115,7 @@ fn run(options: Run) { Ok(()) => {} Err(message) => { // let name = options.path.as_os_str().to_string_lossy(); - println!("{}", message); + println!("{:?}", message); exit(1); } } diff --git a/src/webassembly/errors.rs b/src/webassembly/errors.rs deleted file mode 100644 index f763b0bd2..000000000 --- a/src/webassembly/errors.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! The webassembly::CompileError() constructor creates a new WebAssembly -//! CompileError object, which indicates an error during WebAssembly -//! decoding or validation - -//! The webassembly::LinkError() constructor creates a new WebAssembly -//! LinkError object, which indicates an error during module instantiation -//! (besides traps from the start function). - -//! The webassembly::RuntimeError() constructor creates a new WebAssembly -//! RuntimeError object — the type that is thrown whenever WebAssembly -//! specifies a trap. - -error_chain! { - errors { - CompileError(reason: String) { - description("WebAssembly compilation error") - display("Compilation error: {}", reason) - } - - LinkError(reason: String) { - description("WebAssembly link error") - display("Link error: {}", reason) - } - - RuntimeError(reason: String) { - description("WebAssembly runtime error") - display("Runtime error: {}", reason) - } - } -} diff --git a/src/webassembly/mod.rs b/src/webassembly/mod.rs index e17b439de..58fdad709 100644 --- a/src/webassembly/mod.rs +++ b/src/webassembly/mod.rs @@ -1,14 +1,14 @@ -pub mod errors; pub mod libcalls; pub mod relocation; pub mod utils; use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime::{ - backend::Compiler, + self as runtime, import::Imports, instance::Instance, module::{Module, ModuleInner}, + error::{Result, CallResult}, }; use cranelift_codegen::{ @@ -23,14 +23,12 @@ use target_lexicon; use wasmparser; use wasmparser::WasmDecoder; -pub use self::errors::{Error, ErrorKind}; - use wasmer_emscripten::{allocate_cstr_on_stack, allocate_on_stack, is_emscripten_module}; pub struct ResultObject { /// A webassembly::Module object representing the compiled WebAssembly module. /// This Module can be instantiated again - pub module: Arc, + pub module: Module, /// A webassembly::Instance object that contains all the Exported WebAssembly /// functions. pub instance: Box, @@ -70,7 +68,7 @@ pub fn instantiate( buffer_source: &[u8], import_object: &Imports, options: Option, -) -> Result { +) -> Result { debug!("webassembly - creating instance"); //let instance = Instance::new(&module, import_object, options)?; @@ -107,7 +105,7 @@ pub fn instantiate( pub fn instantiate_streaming( _buffer_source: Vec, _import_object: Imports, -) -> Result { +) -> Result { unimplemented!(); } @@ -121,40 +119,11 @@ pub fn instantiate_streaming( /// Errors: /// If the operation fails, the Result rejects with a /// webassembly::CompileError. -pub fn compile(buffer_source: &[u8]) -> Result, ErrorKind> { - let compiler = &CraneliftCompiler {}; - let module_inner = compiler - .compile(buffer_source) - .map_err(|e| ErrorKind::CompileError(e))?; +pub fn compile(buffer_source: &[u8]) -> Result { + let compiler = CraneliftCompiler::new(); + let module = runtime::compile(buffer_source, &compiler)?; - Ok(Arc::new(Module(Rc::new(module_inner)))) -} - -/// The webassembly::validate() function validates a given typed -/// array of WebAssembly binary code, returning whether the bytes -/// form a valid wasm module (true) or not (false). -/// Params: -/// * `buffer_source`: A `&[u8]` containing the -/// binary code of the .wasm module you want to compile. -pub fn validate(buffer_source: &[u8]) -> bool { - validate_or_error(buffer_source).is_ok() -} - -pub fn validate_or_error(bytes: &[u8]) -> Result<(), ErrorKind> { - let mut parser = wasmparser::ValidatingParser::new(bytes, None); - loop { - let state = parser.read(); - match *state { - wasmparser::ParserState::EndWasm => return Ok(()), - wasmparser::ParserState::Error(err) => { - return Err(ErrorKind::CompileError(format!( - "Validation error: {}", - err.message - ))); - } - _ => (), - } - } + Ok(module) } pub fn get_isa() -> Box { @@ -226,19 +195,19 @@ pub fn get_isa() -> Box { // } pub fn start_instance( - module: Arc, + module: &Module, instance: &mut Instance, path: &str, args: Vec<&str>, -) -> Result<(), String> { - let main_name = if is_emscripten_module(&module) { +) -> CallResult<()> { + let main_name = if is_emscripten_module(module) { "_main" } else { "main" }; // TODO handle args - instance.call(main_name, &[]).map(|o| ()) + instance.call(main_name, &[])?; // TODO atinit and atexit for emscripten // if let Some(ref emscripten_data) = &instance.emscripten_data { @@ -284,4 +253,5 @@ pub fn start_instance( // let main: extern "C" fn(&Instance) = get_instance_function!(instance, func_index); // call_protected!(main(&instance)).map_err(|err| format!("{}", err)) // } + Ok(()) }