mirror of
https://github.com/fluencelabs/interface-types
synced 2024-12-04 15:20:20 +00:00
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.
This commit is contained in:
parent
8bd3345a79
commit
864ac79123
@ -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,
|
||||
|
219
src/errors.rs
Normal file
219
src/errors.rs
Normal file
@ -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<T> = Result<T, InstructionError>;
|
||||
|
||||
/// A type alias for the interpreter result.
|
||||
pub type InterpreterResult<T> = Result<T, InstructionError>;
|
||||
|
||||
/// 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<InterfaceType>, Vec<InterfaceType>),
|
||||
|
||||
/// The received signature.
|
||||
received: (Vec<InterfaceType>, Vec<InterfaceType>),
|
||||
},
|
||||
|
||||
/// 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
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
@ -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::<Vec<InterfaceType>>();
|
||||
|
||||
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!(
|
||||
|
@ -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!(
|
||||
|
@ -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::<i32>(&inputs[0], instruction)? as usize;
|
||||
let pointer = to_native::<i32>(&inputs[1], instruction)? as usize;
|
||||
let memory_view = memory.view();
|
||||
|
||||
let data: Vec<u8> = (&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<u8> = (&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::<Vec<Cell<u8>>>()),
|
||||
..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"#,
|
||||
);
|
||||
}
|
||||
|
@ -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<T>
|
||||
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::{
|
||||
|
@ -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] -> []`"#,
|
||||
);
|
||||
}
|
||||
|
@ -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<Instance, Export, LocalImport, Memory, MemoryView> = Box<
|
||||
dyn Fn(&mut Runtime<Instance, Export, LocalImport, Memory, MemoryView>) -> Result<(), String>,
|
||||
dyn Fn(
|
||||
&mut Runtime<Instance, Export, LocalImport, Memory, MemoryView>,
|
||||
) -> 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<Stack<InterfaceValue>, String> {
|
||||
) -> InterpreterResult<Stack<InterfaceValue>> {
|
||||
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<Export, LocalImport, Memory, MemoryView>,
|
||||
{
|
||||
type Error = String;
|
||||
type Error = ();
|
||||
|
||||
fn try_from(instructions: &Vec<Instruction>) -> Result<Self, Self::Error> {
|
||||
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!(),
|
||||
}
|
||||
})
|
||||
|
@ -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<Self, Self::Error> {
|
||||
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);
|
||||
|
@ -55,4 +55,5 @@ pub mod ast;
|
||||
mod macros;
|
||||
pub mod decoders;
|
||||
pub mod encoders;
|
||||
pub mod errors;
|
||||
pub mod interpreter;
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user