Add error types and convert most results to wasmer-runtime results

This commit is contained in:
Lachlan Sneff 2019-01-18 09:17:44 -08:00
parent f5ab605878
commit d23601a810
11 changed files with 303 additions and 80 deletions

View File

@ -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<ModuleInner, String> {
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner> {
validate(wasm)?;
let isa = get_isa();
@ -53,15 +57,15 @@ fn get_isa() -> Box<isa::TargetIsa> {
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(),
})?,
_ => {}
}
}

View File

@ -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<LocalFuncIndex, ir::Function>,
) -> Result<ModuleInner, String> {
) -> CompileResult<ModuleInner> {
// we have to deduplicate `module.func_assoc`
let func_assoc = &mut self.module.func_assoc;
let sig_registry = &self.module.sig_registry;

View File

@ -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<Map<LocalFuncIndex, ir::Function>, String> {
translate_module(wasm, &mut self).map_err(|e| e.to_string())?;
pub fn translate(mut self, wasm: &[u8]) -> CompileResult<Map<LocalFuncIndex, ir::Function>> {
translate_module(wasm, &mut self)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
Ok(self.func_bodies)
}
}

View File

@ -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<LocalFuncIndex, ir::Function>,
) -> Result<Self, String> {
) -> CompileResult<Self> {
let mut compiled_functions: Vec<Vec<u8>> = 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::<usize>());
@ -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<FuncResolver, String> {
pub fn finalize(mut self) -> CompileResult<FuncResolver> {
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)

View File

@ -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<ModuleInner, String>;
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner>;
}
pub trait FuncResolver {

View File

@ -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<Self, String> {
) -> LinkResult<Self> {
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<BoxedMap<ImportedFuncIndex, vm::ImportedFunc>, String> {
) -> LinkResult<BoxedMap<ImportedFuncIndex, vm::ImportedFunc>> {
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<BoxedMap<ImportedMemoryIndex, vm::ImportedMemory>, String> {
) -> LinkResult<BoxedMap<ImportedMemoryIndex, vm::ImportedMemory>> {
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<BoxedMap<ImportedTableIndex, vm::ImportedTable>, String> {
) -> LinkResult<BoxedMap<ImportedTableIndex, vm::ImportedTable>> {
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<BoxedMap<ImportedGlobalIndex, vm::ImportedGlobal>, String> {
) -> LinkResult<BoxedMap<ImportedGlobalIndex, vm::ImportedGlobal>> {
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())

116
lib/runtime/src/error.rs Normal file
View File

@ -0,0 +1,116 @@
use crate::types::{FuncSig, GlobalDesc, Memory, MemoryIndex, Table, TableIndex, Type};
pub type Result<T> = std::result::Result<T, Box<Error>>;
pub type CompileResult<T> = std::result::Result<T, Box<CompileError>>;
pub type LinkResult<T> = std::result::Result<T, Box<LinkError>>;
pub type RuntimeResult<T> = std::result::Result<T, Box<RuntimeError>>;
pub type CallResult<T> = std::result::Result<T, Box<CallError>>;
/// 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<Type> },
NoSuchExport { name: String },
ExportNotFunc { name: String },
Runtime(Box<RuntimeError>),
}
/// 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<CompileError>),
LinkError(Box<LinkError>),
RuntimeError(Box<RuntimeError>),
CallError(Box<CallError>),
}
impl From<Box<CompileError>> for Box<Error> {
fn from(compile_err: Box<CompileError>) -> Self {
Box::new(Error::CompileError(compile_err))
}
}
impl From<Box<LinkError>> for Box<Error> {
fn from(link_err: Box<LinkError>) -> Self {
Box::new(Error::LinkError(link_err))
}
}
impl From<Box<RuntimeError>> for Box<Error> {
fn from(runtime_err: Box<RuntimeError>) -> Self {
Box::new(Error::RuntimeError(runtime_err))
}
}
impl From<Box<CallError>> for Box<Error> {
fn from(call_err: Box<CallError>) -> Self {
Box::new(Error::CallError(call_err))
}
}

View File

@ -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<ModuleInner>,
mut imports: Box<Imports>,
) -> Result<Instance, String> {
pub(crate) fn new(module: Rc<ModuleInner>, mut imports: Box<Imports>) -> Result<Instance> {
// 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<Option<Vec<Value>>, String>` in
/// order to support multi-value returns.
pub fn call(&mut self, name: &str, args: &[Value]) -> Result<Option<Value>, 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<Option<Value>> {
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<Option<Value>, String> {
) -> CallResult<Option<Value>> {
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())
}
}

View File

@ -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<module::Module, String> {
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult<module::Module> {
compiler
.compile(wasm)
.map(|inner| module::Module::new(Rc::new(inner)))

View File

@ -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<Instance, String> {
pub fn instantiate(&self, imports: Imports) -> Result<Instance> {
Instance::new(Rc::clone(&self.0), Box::new(imports))
}
}

View File

@ -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<usize> = Cell::new(0);
}
pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, String> {
pub fn call_protected<T>(f: impl FnOnce() -> T) -> RuntimeResult<T> {
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
let prev_jmp_buf = *jmp_buf;
@ -45,7 +48,11 @@ pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, String> {
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;