From 864ac79123d4145e1433bb8481b16b73ef16d4d7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 10 Mar 2020 15:41:49 +0100 Subject: [PATCH] feat(interface-types) Use better errors. The new `errors` module contains structure to represent errors, instead of using basic strings. The first usage is in the interpreter itself. --- src/ast.rs | 2 +- src/errors.rs | 219 ++++++++++++++++++ src/interpreter/instruction.rs | 2 +- src/interpreter/instructions/argument_get.rs | 15 +- src/interpreter/instructions/call_core.rs | 70 +++--- .../instructions/lowering_lifting.rs | 54 +++-- .../instructions/memory_to_string.rs | 101 ++++---- src/interpreter/instructions/mod.rs | 21 ++ .../instructions/string_to_memory.rs | 82 ++++--- src/interpreter/mod.rs | 89 +++---- src/interpreter/wasm/values.rs | 49 ++-- src/lib.rs | 1 + src/macros.rs | 2 +- 13 files changed, 500 insertions(+), 207 deletions(-) create mode 100644 src/errors.rs diff --git a/src/ast.rs b/src/ast.rs index 21b7602..9828043 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -5,7 +5,7 @@ use crate::interpreter::Instruction; use std::str; /// Represents the types supported by WIT. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub enum InterfaceType { /// A 8-bits signed integer. S8, diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..24ca556 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,219 @@ +//! The error module contains all the data structures that represent +//! an error. + +use crate::{ast::InterfaceType, interpreter::Instruction}; +use std::{ + fmt::{self, Display, Formatter}, + result::Result, + string::{self, ToString}, +}; + +/// A type alias for instruction's results. +pub type InstructionResult = Result; + +/// A type alias for the interpreter result. +pub type InterpreterResult = Result; + +/// Structure to represent errors when casting from an `InterfaceType` +/// to a native value. +#[derive(Debug)] +pub struct WasmValueNativeCastError { + /// The initial type. + pub from: InterfaceType, + + /// The targeted type. + /// + /// `InterfaceType` is used to represent the native type by + /// associativity. + pub to: InterfaceType, +} + +/// Structure to represent the errors for instructions. +#[derive(Debug)] +pub struct InstructionError { + /// The instruction that raises the error. + pub instruction: Instruction, + + /// The error kind. + pub error_kind: InstructionErrorKind, +} + +impl InstructionError { + pub(crate) fn new(instruction: Instruction, error_kind: InstructionErrorKind) -> Self { + Self { + instruction, + error_kind, + } + } +} + +impl Display for InstructionError { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "`{}` {}", + (&self.instruction).to_string(), + self.error_kind + ) + } +} + +/// The kind of instruction errors. +#[derive(Debug)] +pub enum InstructionErrorKind { + /// The instruction needs to read an invocation input at index `index`, but it's missing. + InvocationInputIsMissing { + /// The invocation input index. + index: u32, + }, + + /// Failed to cast from a WIT value to a native value. + ToNative(WasmValueNativeCastError), + + /// Failed to cast from `from` to `to`. + LoweringLifting { + /// The initial type. + from: InterfaceType, + + /// The targeted type. + to: InterfaceType, + }, + + /// Read a value from the stack, but it doesn't have the expected + /// type. + InvalidValueOnTheStack { + /// The expected type. + expected_type: InterfaceType, + + /// The received type. + received_type: InterfaceType, + }, + + /// Need to read some values from the stack, but it doesn't + /// contain enough data. + StackIsTooSmall { + /// The number of values that were needed. + needed: usize, + }, + + /// The local or import function doesn't exist. + LocalOrImportIsMissing { + /// The local or import function index. + function_index: u32, + }, + + /// Values given to a local or import function doesn't match the + /// function signature. + LocalOrImportSignatureMismatch { + /// The local or import function index. + function_index: u32, + + /// The expected signature. + expected: (Vec, Vec), + + /// The received signature. + received: (Vec, Vec), + }, + + /// Failed to call a local or import function. + LocalOrImportCall { + /// The local or import function index that has been called. + function_index: u32, + }, + + /// The memory doesn't exist. + MemoryIsMissing { + /// The memory indeX. + memory_index: u32, + }, + + /// Tried to read out of bounds of the memory. + MemoryOutOfBoundsAccess { + /// The access index. + index: usize, + + /// The memory length. + length: usize, + }, + + /// The string contains invalid UTF-8 encoding. + String(string::FromUtf8Error), +} + +impl Display for InstructionErrorKind { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + Self::InvocationInputIsMissing { index } => write!( + formatter, + "cannot access invocation inputs #{} because it doesn't exist", + index + ), + + Self::ToNative(WasmValueNativeCastError { from, .. }) => write!( + formatter, + "failed to cast the WIT value `{:?}` to its native type", + from, + ), + + Self::LoweringLifting { from, to } => { + write!(formatter, "failed to cast `{:?}` to `{:?}`", from, to) + } + + Self::InvalidValueOnTheStack { + expected_type, + received_type, + } => write!( + formatter, + "read a value of type `{:?}` from the stack, but the type `{:?}` was expected", + received_type, expected_type, + ), + + Self::StackIsTooSmall { needed } => write!( + formatter, + "needed to read `{}` value(s) from the stack, but it doesn't contain enough data", + needed + ), + + Self::LocalOrImportIsMissing { function_index } => write!( + formatter, + "the local or import function `{}` doesn't exist", + function_index + ), + + Self::LocalOrImportSignatureMismatch { function_index, expected, received } => write!( + formatter, + "the local or import function `{}` has the signature `{:?} -> {:?}` but it received values of kind `{:?} -> {:?}`", + function_index, + expected.0, + expected.1, + received.0, + received.1, + ), + + Self::LocalOrImportCall { function_index } => write!( + formatter, + "failed while calling the local or import function `{}`", + function_index + ), + + Self::MemoryIsMissing { memory_index } => write!( + formatter, + "memory `{}` does not exist", + memory_index, + ), + + Self::MemoryOutOfBoundsAccess { index, length } => write!( + formatter, + "read out of the memory bounds (index {} > memory length {})", + index, + length, + ), + + Self::String(error) => write!( + formatter, + "{}", + error + ), + } + } +} diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 109f269..425cdad 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -1,7 +1,7 @@ //use crate::ast::InterfaceType; /// Represents all the possible WIT instructions. -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone, Copy)] pub enum Instruction { /// The `arg.get` instruction. ArgumentGet { diff --git a/src/interpreter/instructions/argument_get.rs b/src/interpreter/instructions/argument_get.rs index e530d88..dd161e1 100644 --- a/src/interpreter/instructions/argument_get.rs +++ b/src/interpreter/instructions/argument_get.rs @@ -1,12 +1,17 @@ +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::Instruction, +}; + executable_instruction!( - argument_get(index: u32, instruction_name: String) -> _ { + argument_get(index: u32, instruction: Instruction) -> _ { move |runtime| -> _ { let invocation_inputs = runtime.invocation_inputs; if index >= (invocation_inputs.len() as u32) { - return Err(format!( - "`{}` cannot access argument #{} because it doesn't exist.", - instruction_name, index + return Err(InstructionError::new( + instruction, + InstructionErrorKind::InvocationInputIsMissing { index }, )); } @@ -49,6 +54,6 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 1 }], invocation_inputs: [InterfaceValue::I32(42)], instance: Instance::new(), - error: "`arg.get 1` cannot access argument #1 because it doesn't exist." + error: "`arg.get 1` cannot access invocation inputs #1 because it doesn't exist" ); } diff --git a/src/interpreter/instructions/call_core.rs b/src/interpreter/instructions/call_core.rs index 3179c90..dfaf520 100644 --- a/src/interpreter/instructions/call_core.rs +++ b/src/interpreter/instructions/call_core.rs @@ -1,10 +1,14 @@ -use crate::interpreter::wasm::{ - structures::{FunctionIndex, TypedIndex}, - values::InterfaceType, +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::InterfaceType, + }, + interpreter::Instruction, }; executable_instruction!( - call_core(function_index: usize, instruction_name: String) -> _ { + call_core(function_index: usize, instruction: Instruction) -> _ { move |runtime| -> _ { let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(function_index); @@ -21,12 +25,16 @@ executable_instruction!( .collect::>(); if input_types != local_or_import.inputs() { - return Err(format!( - "`{}` cannot call the local or imported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).", - instruction_name, - function_index, - local_or_import.inputs(), - )) + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: function_index as u32, + expected: (local_or_import.inputs().to_vec(), vec![]), + received: (input_types, vec![]), + } + ) + ) } match local_or_import.call(&inputs) { @@ -37,26 +45,28 @@ executable_instruction!( Ok(()) } - Err(_) => Err(format!( - "`{}` failed when calling the local or imported function `{}`.", - instruction_name, - function_index - )) + Err(_) => Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { function_index: function_index as u32, }, + ) + ) } } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because there is not enough data on the stack for the arguments (needs {}).", - instruction_name, - function_index, - inputs_cardinality, - )) + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: inputs_cardinality }, + ) + ) } } - None => Err(format!( - "`{}` cannot call the local or imported function `{}` because it doesn't exist.", - instruction_name, - function_index, - )) + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { function_index: function_index as u32, }, + ) + ) } } } @@ -89,7 +99,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Default::default(), - error: r#"`call-core 42` cannot call the local or imported function `42` because it doesn't exist."#, + error: r#"`call-core 42` the local or import function `42` doesn't exist"#, ); test_executable_instruction!( @@ -104,7 +114,7 @@ mod tests { InterfaceValue::I32(4), ], instance: Instance::new(), - error: r#"`call-core 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#, + error: r#"`call-core 42` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, ); test_executable_instruction!( @@ -120,7 +130,7 @@ mod tests { // ^^^ mismatch with `42` signature ], instance: Instance::new(), - error: r#"`call-core 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#, + error: r#"`call-core 42` the local or import function `42` has the signature `[I32, I32] -> []` but it received values of kind `[I32, I64] -> []`"#, ); test_executable_instruction!( @@ -151,7 +161,7 @@ mod tests { }, ..Default::default() }, - error: r#"`call-core 42` failed when calling the local or imported function `42`."#, + error: r#"`call-core 42` failed while calling the local or import function `42`"#, ); test_executable_instruction!( diff --git a/src/interpreter/instructions/lowering_lifting.rs b/src/interpreter/instructions/lowering_lifting.rs index c24fd99..f18c243 100644 --- a/src/interpreter/instructions/lowering_lifting.rs +++ b/src/interpreter/instructions/lowering_lifting.rs @@ -1,10 +1,14 @@ -use crate::interpreter::wasm::values::InterfaceValue; +use crate::{ + ast::InterfaceType, + errors::{InstructionError, InstructionErrorKind}, + interpreter::{wasm::values::InterfaceValue, Instruction}, +}; use std::convert::TryInto; macro_rules! lowering_lifting { ($instruction_function_name:ident, $instruction_name:expr, $from_variant:ident, $to_variant:ident) => { executable_instruction!( - $instruction_function_name() -> _ { + $instruction_function_name(instruction: Instruction) -> _ { move |runtime| -> _ { match runtime.stack.pop1() { Some(InterfaceValue::$from_variant(value)) => { @@ -12,39 +16,33 @@ macro_rules! lowering_lifting { .stack .push(InterfaceValue::$to_variant(value.try_into().map_err( |_| { - concat!( - "Failed to cast `", - stringify!($from_variant), - "` to `", - stringify!($to_variant), - "`." - ).to_string() + InstructionError::new( + instruction, + InstructionErrorKind::LoweringLifting { from: InterfaceType::$from_variant, to: InterfaceType::$to_variant }, + ) }, )?)) } Some(wrong_value) => { - return Err(format!( - concat!( - "Instruction `", - $instruction_name, - "` expects a `", - stringify!($from_variant), - "` value on the stack, got `{:?}`.", - ), - wrong_value - + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::InvalidValueOnTheStack { + expected_type: InterfaceType::$from_variant, + received_type: (&wrong_value).into(), + } + ) ) - .to_string()) }, None => { - return Err(concat!( - "Instruction `", - $instruction_name, - "` needs one value on the stack." + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 }, + ) ) - .to_string()) } } @@ -103,7 +101,7 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 0}, Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I32(128)], instance: Instance::new(), - error: "Failed to cast `I32` to `S8`." + error: "`i32-to-s8` failed to cast `I32` to `S8`" ); test_executable_instruction!( @@ -111,7 +109,7 @@ mod tests { instructions: [Instruction::ArgumentGet { index: 0}, Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I64(42)], instance: Instance::new(), - error: "Instruction `i32-to-s8` expects a `I32` value on the stack, got `I64(42)`." + error: "`i32-to-s8` read a value of type `I64` from the stack, but the type `I32` was expected" ); test_executable_instruction!( @@ -119,7 +117,7 @@ mod tests { instructions: [Instruction::I32ToS8], invocation_inputs: [InterfaceValue::I32(42)], instance: Instance::new(), - error: "Instruction `i32-to-s8` needs one value on the stack." + error: "`i32-to-s8` needed to read `1` value(s) from the stack, but it doesn't contain enough data" ); test_executable_instruction!( diff --git a/src/interpreter/instructions/memory_to_string.rs b/src/interpreter/instructions/memory_to_string.rs index 678d08d..b1b9230 100644 --- a/src/interpreter/instructions/memory_to_string.rs +++ b/src/interpreter/instructions/memory_to_string.rs @@ -1,52 +1,69 @@ -use crate::interpreter::wasm::values::InterfaceValue; -use std::{cell::Cell, convert::TryFrom}; +use super::to_native; +use crate::{ + errors::{InstructionError, InstructionErrorKind}, + interpreter::{wasm::values::InterfaceValue, Instruction}, +}; +use std::cell::Cell; executable_instruction!( - memory_to_string(instruction_name: String) -> _ { + memory_to_string(instruction: Instruction) -> _ { move |runtime| -> _ { match runtime.stack.pop(2) { - Some(inputs) => match runtime.wasm_instance.memory(0) { - Some(memory) => { - let length = i32::try_from(&inputs[0])? as usize; - let pointer = i32::try_from(&inputs[1])? as usize; - let memory_view = memory.view(); + Some(inputs) => { + let memory_index: u32 = 0; - if memory_view.len() < pointer + length { - return Err(format!( - "`{}` failed because it has to read out of the memory bounds (index {} > memory length {}).", - instruction_name, - pointer + length, - memory_view.len() - )); - } + match runtime.wasm_instance.memory(memory_index as usize) { + Some(memory) => { + let length = to_native::(&inputs[0], instruction)? as usize; + let pointer = to_native::(&inputs[1], instruction)? as usize; + let memory_view = memory.view(); - let data: Vec = (&memory_view[pointer..pointer + length]) - .iter() - .map(Cell::get) - .collect(); - - match String::from_utf8(data) { - Ok(string) => { - runtime.stack.push(InterfaceValue::String(string)); - - Ok(()) + if memory_view.len() < pointer + length { + return Err( + InstructionError::new( + instruction, + InstructionErrorKind::MemoryOutOfBoundsAccess { + index: pointer + length, + length: memory_view.len(), + } + ), + ) + } + + let data: Vec = (&memory_view[pointer..pointer + length]) + .iter() + .map(Cell::get) + .collect(); + + match String::from_utf8(data) { + Ok(string) => { + runtime.stack.push(InterfaceValue::String(string)); + + Ok(()) + } + Err(utf8_error) => Err( + InstructionError::new( + instruction, + InstructionErrorKind::String(utf8_error) + ), + ) } - Err(utf8_error) => Err(format!( - "`{}` failed because the read string isn't UTF-8 valid ({}).", - instruction_name, - utf8_error, - )) } + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index } + ), + ) } - None => Err(format!( - "`{}` failed because there is no memory to read.", - instruction_name - )) } - None => Err(format!( - "`{}` failed because there is not enough data on the stack (needs 2).", - instruction_name, - )) + + None => Err( + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 2 } + ), + ) } } } @@ -91,7 +108,7 @@ mod tests { memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()), ..Default::default() }, - error: r#"`memory-to-string` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#, + error: r#"`memory-to-string` read out of the memory bounds (index 13 > memory length 6)"#, ); test_executable_instruction!( @@ -111,7 +128,7 @@ mod tests { memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::>>()), ..Default::default() }, - error: r#"`memory-to-string` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#, + error: r#"`memory-to-string` invalid utf-8 sequence of 1 bytes from index 1"#, ); test_executable_instruction!( @@ -126,6 +143,6 @@ mod tests { InterfaceValue::I32(0), ], instance: Instance::new(), - error: r#"`memory-to-string` failed because there is not enough data on the stack (needs 2)."#, + error: r#"`memory-to-string` needed to read `2` value(s) from the stack, but it doesn't contain enough data"#, ); } diff --git a/src/interpreter/instructions/mod.rs b/src/interpreter/instructions/mod.rs index 8466b99..4400cf0 100644 --- a/src/interpreter/instructions/mod.rs +++ b/src/interpreter/instructions/mod.rs @@ -4,12 +4,33 @@ mod lowering_lifting; mod memory_to_string; mod string_to_memory; +use crate::{ + errors::{InstructionError, InstructionErrorKind, InstructionResult, WasmValueNativeCastError}, + interpreter::{ + wasm::values::{InterfaceValue, NativeType}, + Instruction, + }, +}; pub(crate) use argument_get::argument_get; pub(crate) use call_core::call_core; pub(crate) use lowering_lifting::*; pub(crate) use memory_to_string::memory_to_string; +use std::convert::TryFrom; pub(crate) use string_to_memory::string_to_memory; +/// Just a short helper to map the error of a cast from an +/// `InterfaceValue` to a native value. +pub(crate) fn to_native<'a, T>( + wit_value: &'a InterfaceValue, + instruction: Instruction, +) -> InstructionResult +where + T: NativeType + TryFrom<&'a InterfaceValue, Error = WasmValueNativeCastError>, +{ + T::try_from(wit_value) + .map_err(|error| InstructionError::new(instruction, InstructionErrorKind::ToNative(error))) +} + #[cfg(test)] pub(crate) mod tests { use crate::interpreter::wasm::{ diff --git a/src/interpreter/instructions/string_to_memory.rs b/src/interpreter/instructions/string_to_memory.rs index 57414d0..a4341a7 100644 --- a/src/interpreter/instructions/string_to_memory.rs +++ b/src/interpreter/instructions/string_to_memory.rs @@ -1,60 +1,66 @@ -use crate::interpreter::wasm::{ - structures::{FunctionIndex, TypedIndex}, - values::{InterfaceType, InterfaceValue}, +use super::to_native; +use crate::{ + ast::InterfaceType, + errors::{InstructionError, InstructionErrorKind}, + interpreter::{ + wasm::{ + structures::{FunctionIndex, TypedIndex}, + values::InterfaceValue, + }, + Instruction, + }, }; -use std::convert::TryInto; executable_instruction!( - string_to_memory(allocator_index: u32, instruction_name: String) -> _ { + string_to_memory(allocator_index: u32, instruction: Instruction) -> _ { move |runtime| -> _ { let instance = &mut runtime.wasm_instance; let index = FunctionIndex::new(allocator_index as usize); let allocator = instance.local_or_import(index).ok_or_else(|| { - format!( - "`{}` failed because the function `{}` (the allocator) doesn't exist.", - instruction_name, - allocator_index + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportIsMissing { function_index: allocator_index }, ) })?; if allocator.inputs() != [InterfaceType::I32] || allocator.outputs() != [InterfaceType::I32] { - return Err(format!( - "`{}` failed because the allocator `{}` has an invalid signature (expects [I32] -> [I32]).", - instruction_name, - allocator_index, - )); + return Err(InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportSignatureMismatch { + function_index: allocator_index, + expected: (vec![InterfaceType::I32], vec![InterfaceType::I32]), + received: (allocator.inputs().to_vec(), allocator.outputs().to_vec()) + } + )) } let string = runtime.stack.pop1().ok_or_else(|| { - format!( - "`{}` cannot call the allocator `{}` because there is not enough data on the stack for the arguments (needs {}).", - instruction_name, - allocator_index, - 1 + InstructionError::new( + instruction, + InstructionErrorKind::StackIsTooSmall { needed: 1 } ) })?; - let string: String = (&string).try_into()?; + let string: String = to_native(&string, instruction)?; let string_bytes = string.as_bytes(); - let string_length = (string_bytes.len() as i32) - .try_into() - .map_err(|error| format!("{}", error))?; + let string_length = string_bytes.len() as i32; - let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| format!( - "`{}` failed when calling the allocator `{}`.", - instruction_name, - allocator_index, - ))?; - - let string_pointer: i32 = (&outputs[0]).try_into()?; + let outputs = allocator.call(&[InterfaceValue::I32(string_length)]).map_err(|_| { + InstructionError::new( + instruction, + InstructionErrorKind::LocalOrImportCall { function_index: allocator_index }, + ) + })?; + let string_pointer: i32 = to_native(&outputs[0], instruction)?; + let memory_index: u32 = 0; let memory_view = instance - .memory(0) + .memory(memory_index as usize) .ok_or_else(|| { - format!( - "`{}` failed because there is no memory to write into.", - instruction_name + InstructionError::new( + instruction, + InstructionErrorKind::MemoryIsMissing { memory_index } ) })? .view(); @@ -106,7 +112,7 @@ mod tests { instructions: [Instruction::StringToMemory { allocator_index: 43 }], invocation_inputs: [], instance: Instance { ..Default::default() }, - error: r#"`string-to-memory 43` failed because the function `43` (the allocator) doesn't exist."#, + error: r#"`string-to-memory 43` the local or import function `43` doesn't exist"#, ); test_executable_instruction!( @@ -117,7 +123,7 @@ mod tests { ], invocation_inputs: [InterfaceValue::String("Hello, World!".into())], instance: Instance::new(), - error: r#"`string-to-memory 43` cannot call the allocator `43` because there is not enough data on the stack for the arguments (needs 1)."#, + error: r#"`string-to-memory 43` needed to read `1` value(s) from the stack, but it doesn't contain enough data"#, ); test_executable_instruction!( @@ -141,7 +147,7 @@ mod tests { instance }, - error: r#"`string-to-memory 153` failed when calling the allocator `153`."#, + error: r#"`string-to-memory 153` failed while calling the local or import function `153`"#, ); test_executable_instruction!( @@ -164,6 +170,6 @@ mod tests { instance }, - error: r#"`string-to-memory 153` failed because the allocator `153` has an invalid signature (expects [I32] -> [I32])."#, + error: r#"`string-to-memory 153` the local or import function `153` has the signature `[I32] -> [I32]` but it received values of kind `[I32, I32] -> []`"#, ); } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index bdc939f..122c482 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -5,6 +5,7 @@ mod instructions; pub mod stack; pub mod wasm; +use crate::errors::{InstructionResult, InterpreterResult}; pub use instruction::Instruction; use stack::Stack; use std::{convert::TryFrom, marker::PhantomData}; @@ -38,7 +39,9 @@ where /// Type alias for an executable instruction. It's an implementation /// details, but an instruction is a boxed closure instance. pub(crate) type ExecutableInstruction = Box< - dyn Fn(&mut Runtime) -> Result<(), String>, + dyn Fn( + &mut Runtime, + ) -> InstructionResult<()>, >; /// An interpreter is the central piece of this crate. It is a set of @@ -154,7 +157,7 @@ where &self, invocation_inputs: &[InterfaceValue], wasm_instance: &mut Instance, - ) -> Result, String> { + ) -> InterpreterResult> { let mut runtime = Runtime { invocation_inputs, stack: Stack::new(), @@ -165,7 +168,7 @@ where for executable_instruction in self.iter() { match executable_instruction(&mut runtime) { Ok(_) => continue, - Err(message) => return Err(message), + Err(error) => return Err(error), } } @@ -183,62 +186,64 @@ where MemoryView: wasm::structures::MemoryView, Instance: wasm::structures::Instance, { - type Error = String; + type Error = (); fn try_from(instructions: &Vec) -> Result { let executable_instructions = instructions .iter() .map(|instruction| { - let instruction_name = instruction.to_string(); - match instruction { Instruction::ArgumentGet { index } => { - instructions::argument_get(*index, instruction_name) + instructions::argument_get(*index, *instruction) } Instruction::CallCore { function_index } => { - instructions::call_core(*function_index, instruction_name) + instructions::call_core(*function_index, *instruction) } - Instruction::MemoryToString => instructions::memory_to_string(instruction_name), + Instruction::MemoryToString => instructions::memory_to_string(*instruction), Instruction::StringToMemory { allocator_index } => { - instructions::string_to_memory(*allocator_index, instruction_name) + instructions::string_to_memory(*allocator_index, *instruction) } - Instruction::I32ToS8 => instructions::i32_to_s8(), + Instruction::I32ToS8 => instructions::i32_to_s8(*instruction), //Instruction::I32ToS8X - Instruction::I32ToU8 => instructions::i32_to_u8(), - Instruction::I32ToS16 => instructions::i32_to_s16(), + Instruction::I32ToU8 => instructions::i32_to_u8(*instruction), + Instruction::I32ToS16 => instructions::i32_to_s16(*instruction), //Instruction::I32ToS16X - Instruction::I32ToU16 => instructions::i32_to_u16(), - Instruction::I32ToS32 => instructions::i32_to_s32(), - Instruction::I32ToU32 => instructions::i32_to_u32(), - Instruction::I32ToS64 => instructions::i32_to_s64(), - Instruction::I32ToU64 => instructions::i32_to_u64(), - Instruction::I64ToS8 => instructions::i64_to_s8(), + Instruction::I32ToU16 => instructions::i32_to_u16(*instruction), + Instruction::I32ToS32 => instructions::i32_to_s32(*instruction), + Instruction::I32ToU32 => instructions::i32_to_u32(*instruction), + Instruction::I32ToS64 => instructions::i32_to_s64(*instruction), + Instruction::I32ToU64 => instructions::i32_to_u64(*instruction), + Instruction::I64ToS8 => instructions::i64_to_s8(*instruction), //Instruction::I64ToS8X - Instruction::I64ToU8 => instructions::i64_to_u8(), - Instruction::I64ToS16 => instructions::i64_to_s16(), + Instruction::I64ToU8 => instructions::i64_to_u8(*instruction), + Instruction::I64ToS16 => instructions::i64_to_s16(*instruction), //Instruction::I64ToS16X - Instruction::I64ToU16 => instructions::i64_to_u16(), - Instruction::I64ToS32 => instructions::i64_to_s32(), - Instruction::I64ToU32 => instructions::i64_to_u32(), - Instruction::I64ToS64 => instructions::i64_to_s64(), - Instruction::I64ToU64 => instructions::i64_to_u64(), - Instruction::S8ToI32 => instructions::s8_to_i32(), - Instruction::U8ToI32 => instructions::u8_to_i32(), - Instruction::S16ToI32 => instructions::s16_to_i32(), - Instruction::U16ToI32 => instructions::u16_to_i32(), - Instruction::S32ToI32 => instructions::s32_to_i32(), - Instruction::U32ToI32 => instructions::u32_to_i32(), - Instruction::S64ToI32 | Instruction::S64ToI32X => instructions::s64_to_i32(), - Instruction::U64ToI32 | Instruction::U64ToI32X => instructions::u64_to_i32(), - Instruction::S8ToI64 => instructions::s8_to_i64(), - Instruction::U8ToI64 => instructions::u8_to_i64(), - Instruction::S16ToI64 => instructions::s16_to_i64(), - Instruction::U16ToI64 => instructions::u16_to_i64(), - Instruction::S32ToI64 => instructions::s32_to_i64(), - Instruction::U32ToI64 => instructions::u32_to_i64(), - Instruction::S64ToI64 => instructions::s64_to_i64(), - Instruction::U64ToI64 => instructions::u64_to_i64(), + Instruction::I64ToU16 => instructions::i64_to_u16(*instruction), + Instruction::I64ToS32 => instructions::i64_to_s32(*instruction), + Instruction::I64ToU32 => instructions::i64_to_u32(*instruction), + Instruction::I64ToS64 => instructions::i64_to_s64(*instruction), + Instruction::I64ToU64 => instructions::i64_to_u64(*instruction), + Instruction::S8ToI32 => instructions::s8_to_i32(*instruction), + Instruction::U8ToI32 => instructions::u8_to_i32(*instruction), + Instruction::S16ToI32 => instructions::s16_to_i32(*instruction), + Instruction::U16ToI32 => instructions::u16_to_i32(*instruction), + Instruction::S32ToI32 => instructions::s32_to_i32(*instruction), + Instruction::U32ToI32 => instructions::u32_to_i32(*instruction), + Instruction::S64ToI32 | Instruction::S64ToI32X => { + instructions::s64_to_i32(*instruction) + } + Instruction::U64ToI32 | Instruction::U64ToI32X => { + instructions::u64_to_i32(*instruction) + } + Instruction::S8ToI64 => instructions::s8_to_i64(*instruction), + Instruction::U8ToI64 => instructions::u8_to_i64(*instruction), + Instruction::S16ToI64 => instructions::s16_to_i64(*instruction), + Instruction::U16ToI64 => instructions::u16_to_i64(*instruction), + Instruction::S32ToI64 => instructions::s32_to_i64(*instruction), + Instruction::U32ToI64 => instructions::u32_to_i64(*instruction), + Instruction::S64ToI64 => instructions::s64_to_i64(*instruction), + Instruction::U64ToI64 => instructions::u64_to_i64(*instruction), _ => unimplemented!(), } }) diff --git a/src/interpreter/wasm/values.rs b/src/interpreter/wasm/values.rs index 1e0976c..484fa1d 100644 --- a/src/interpreter/wasm/values.rs +++ b/src/interpreter/wasm/values.rs @@ -1,8 +1,8 @@ #![allow(missing_docs)] -use std::convert::TryFrom; - pub use crate::ast::InterfaceType; +use crate::errors::WasmValueNativeCastError; +use std::convert::TryFrom; #[derive(Debug, Clone, PartialEq)] pub enum InterfaceValue { @@ -49,35 +49,46 @@ impl Default for InterfaceValue { } } -macro_rules! from_x_for_interface_value { - ($native_type:ty, $value_variant:ident) => { +pub trait NativeType { + const INTERFACE_TYPE: InterfaceType; +} + +macro_rules! native { + ($native_type:ty, $variant:ident) => { + impl NativeType for $native_type { + const INTERFACE_TYPE: InterfaceType = InterfaceType::$variant; + } + impl From<$native_type> for InterfaceValue { fn from(n: $native_type) -> Self { - Self::$value_variant(n) + Self::$variant(n) } } impl TryFrom<&InterfaceValue> for $native_type { - type Error = &'static str; + type Error = WasmValueNativeCastError; fn try_from(w: &InterfaceValue) -> Result { match w { - InterfaceValue::$value_variant(n) => Ok(n.clone()), - _ => Err("Invalid cast."), + InterfaceValue::$variant(n) => Ok(n.clone()), + _ => Err(WasmValueNativeCastError { + from: w.into(), + to: <$native_type>::INTERFACE_TYPE, + }), } } } }; } -from_x_for_interface_value!(i8, S8); -from_x_for_interface_value!(i16, S16); -from_x_for_interface_value!(u8, U8); -from_x_for_interface_value!(u16, U16); -from_x_for_interface_value!(u32, U32); -from_x_for_interface_value!(u64, U64); -from_x_for_interface_value!(f32, F32); -from_x_for_interface_value!(f64, F64); -from_x_for_interface_value!(String, String); -from_x_for_interface_value!(i32, I32); -from_x_for_interface_value!(i64, I64); +native!(i8, S8); +native!(i16, S16); +native!(u8, U8); +native!(u16, U16); +native!(u32, U32); +native!(u64, U64); +native!(f32, F32); +native!(f64, F64); +native!(String, String); +native!(i32, I32); +native!(i64, I64); diff --git a/src/lib.rs b/src/lib.rs index 9554594..9e88cac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,4 +55,5 @@ pub mod ast; mod macros; pub mod decoders; pub mod encoders; +pub mod errors; pub mod interpreter; diff --git a/src/macros.rs b/src/macros.rs index 5e2217e..b2e2cfa 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -122,7 +122,7 @@ macro_rules! test_executable_instruction { assert!(run.is_err()); - let error = run.unwrap_err(); + let error = run.unwrap_err().to_string(); assert_eq!(error, String::from($error)); }