mirror of
https://github.com/fluencelabs/parity-wasm
synced 2025-06-06 03:21:36 +00:00
Merge pull request #124 from paritytech/extract-validation
Extract validation
This commit is contained in:
commit
9895133048
@ -45,7 +45,10 @@ impl<F> MemoryBuilder<F> where F: Invoke<MemoryDefinition> {
|
|||||||
|
|
||||||
pub fn with_data(mut self, index: u32, values: Vec<u8>) -> Self {
|
pub fn with_data(mut self, index: u32, values: Vec<u8>) -> Self {
|
||||||
self.memory.data.push(MemoryDataDefinition {
|
self.memory.data.push(MemoryDataDefinition {
|
||||||
offset: elements::InitExpr::new(vec![elements::Opcode::I32Const(index as i32)]),
|
offset: elements::InitExpr::new(vec![
|
||||||
|
elements::Opcode::I32Const(index as i32),
|
||||||
|
elements::Opcode::End,
|
||||||
|
]),
|
||||||
values: values,
|
values: values,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
|
@ -45,7 +45,10 @@ impl<F> TableBuilder<F> where F: Invoke<TableDefinition> {
|
|||||||
|
|
||||||
pub fn with_element(mut self, index: u32, values: Vec<u32>) -> Self {
|
pub fn with_element(mut self, index: u32, values: Vec<u32>) -> Self {
|
||||||
self.table.elements.push(TableEntryDefinition {
|
self.table.elements.push(TableEntryDefinition {
|
||||||
offset: elements::InitExpr::new(vec![elements::Opcode::I32Const(index as i32)]),
|
offset: elements::InitExpr::new(vec![
|
||||||
|
elements::Opcode::I32Const(index as i32),
|
||||||
|
elements::Opcode::End,
|
||||||
|
]),
|
||||||
values: values,
|
values: values,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
|
40
src/common/mod.rs
Normal file
40
src/common/mod.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use elements::BlockType;
|
||||||
|
|
||||||
|
pub mod stack;
|
||||||
|
|
||||||
|
/// Index of default linear memory.
|
||||||
|
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
|
/// Index of default table.
|
||||||
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
|
||||||
|
/// Control stack frame.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BlockFrame {
|
||||||
|
/// Frame type.
|
||||||
|
pub frame_type: BlockFrameType,
|
||||||
|
/// A signature, which is a block signature type indicating the number and types of result values of the region.
|
||||||
|
pub block_type: BlockType,
|
||||||
|
/// A label for reference to block instruction.
|
||||||
|
pub begin_position: usize,
|
||||||
|
/// A label for reference from branch instructions.
|
||||||
|
pub branch_position: usize,
|
||||||
|
/// A label for reference from end instructions.
|
||||||
|
pub end_position: usize,
|
||||||
|
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
|
||||||
|
pub value_stack_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type of block frame.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum BlockFrameType {
|
||||||
|
/// Function frame.
|
||||||
|
Function,
|
||||||
|
/// Usual block frame.
|
||||||
|
Block,
|
||||||
|
/// Loop frame (branching to the beginning of block).
|
||||||
|
Loop,
|
||||||
|
/// True-subblock of if expression.
|
||||||
|
IfTrue,
|
||||||
|
/// False-subblock of if expression.
|
||||||
|
IfFalse,
|
||||||
|
}
|
106
src/common/stack.rs
Normal file
106
src/common/stack.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stack with limit.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StackWithLimit<T> where T: Clone {
|
||||||
|
/// Stack values.
|
||||||
|
values: VecDeque<T>,
|
||||||
|
/// Stack limit (maximal stack len).
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StackWithLimit<T> where T: Clone {
|
||||||
|
pub fn with_data(data: Vec<T>, limit: usize) -> Self {
|
||||||
|
StackWithLimit {
|
||||||
|
values: data.into_iter().collect(),
|
||||||
|
limit: limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_limit(limit: usize) -> Self {
|
||||||
|
StackWithLimit {
|
||||||
|
values: VecDeque::new(),
|
||||||
|
limit: limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.values.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.values.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limit(&self) -> usize {
|
||||||
|
self.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(&self) -> &VecDeque<T> {
|
||||||
|
&self.values
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top(&self) -> Result<&T, Error> {
|
||||||
|
self.values
|
||||||
|
.back()
|
||||||
|
.ok_or(Error("non-empty stack expected".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
||||||
|
self.values
|
||||||
|
.back_mut()
|
||||||
|
.ok_or(Error("non-empty stack expected".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
||||||
|
if index >= self.values.len() {
|
||||||
|
return Err(Error(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||||
|
if self.values.len() >= self.limit {
|
||||||
|
return Err(Error(format!("exceeded stack limit {}", self.limit)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values.push_back(value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_penultimate(&mut self, value: T) -> Result<(), Error> {
|
||||||
|
if self.values.is_empty() {
|
||||||
|
return Err(Error("trying to insert penultimate element into empty stack".into()));
|
||||||
|
}
|
||||||
|
self.push(value)?;
|
||||||
|
|
||||||
|
let last_index = self.values.len() - 1;
|
||||||
|
let penultimate_index = last_index - 1;
|
||||||
|
self.values.swap(last_index, penultimate_index);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Result<T, Error> {
|
||||||
|
self.values
|
||||||
|
.pop_back()
|
||||||
|
.ok_or(Error("non-empty stack expected".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
||||||
|
debug_assert!(new_size <= self.values.len());
|
||||||
|
self.values.resize(new_size, dummy);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
//! WebAssembly interpreter module.
|
//! WebAssembly interpreter module.
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
use validation;
|
||||||
|
use common;
|
||||||
|
|
||||||
/// Custom user error.
|
/// Custom user error.
|
||||||
pub trait UserError: 'static + ::std::fmt::Display + ::std::fmt::Debug {
|
pub trait UserError: 'static + ::std::fmt::Display + ::std::fmt::Debug {
|
||||||
@ -116,6 +118,19 @@ impl<U> From<U> for Error where U: UserError + Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<validation::Error> for Error {
|
||||||
|
fn from(e: validation::Error) -> Self {
|
||||||
|
Error::Validation(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<common::stack::Error> for Error {
|
||||||
|
fn from(e: common::stack::Error) -> Self {
|
||||||
|
Error::Stack(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod validator;
|
||||||
mod native;
|
mod native;
|
||||||
mod imports;
|
mod imports;
|
||||||
mod memory;
|
mod memory;
|
||||||
@ -124,7 +139,6 @@ mod program;
|
|||||||
mod runner;
|
mod runner;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod table;
|
mod table;
|
||||||
mod validator;
|
|
||||||
mod value;
|
mod value;
|
||||||
mod variable;
|
mod variable;
|
||||||
|
|
||||||
|
@ -2,18 +2,18 @@ use std::collections::HashMap;
|
|||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use elements::{Module, InitExpr, Opcode, Type, FunctionType, Internal, External, BlockType, ResizableLimits, Local, ValueType};
|
use elements::{Module, InitExpr, Opcode, Type, FunctionType, Internal, External, ResizableLimits, Local, ValueType, BlockType};
|
||||||
use interpreter::Error;
|
use interpreter::Error;
|
||||||
use interpreter::native::UserFunctionDescriptor;
|
use interpreter::native::UserFunctionDescriptor;
|
||||||
use interpreter::imports::ModuleImports;
|
use interpreter::imports::ModuleImports;
|
||||||
use interpreter::memory::MemoryInstance;
|
use interpreter::memory::MemoryInstance;
|
||||||
use interpreter::program::ProgramInstanceEssence;
|
use interpreter::program::ProgramInstanceEssence;
|
||||||
use interpreter::runner::{Interpreter, FunctionContext, prepare_function_args};
|
use interpreter::runner::{Interpreter, FunctionContext, prepare_function_args};
|
||||||
use interpreter::stack::StackWithLimit;
|
|
||||||
use interpreter::table::TableInstance;
|
use interpreter::table::TableInstance;
|
||||||
use interpreter::validator::{Validator, FunctionValidationContext};
|
use interpreter::validator::{Validator, FunctionValidationContext};
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
use interpreter::value::{RuntimeValue, TryInto};
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
use interpreter::variable::{VariableInstance, VariableType};
|
||||||
|
use common::stack::StackWithLimit;
|
||||||
|
|
||||||
/// Maximum number of entries in value stack.
|
/// Maximum number of entries in value stack.
|
||||||
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||||
@ -232,6 +232,8 @@ impl ModuleInstance {
|
|||||||
|
|
||||||
/// Run instantiation-time procedures (validation). Module is not completely validated until this call.
|
/// Run instantiation-time procedures (validation). Module is not completely validated until this call.
|
||||||
pub fn instantiate<'a>(&mut self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<(), Error> {
|
pub fn instantiate<'a>(&mut self, externals: Option<&'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>>) -> Result<(), Error> {
|
||||||
|
::validation::validate_module(&self.module)?;
|
||||||
|
|
||||||
// validate start section
|
// validate start section
|
||||||
if let Some(start_function) = self.module.start_section() {
|
if let Some(start_function) = self.module.start_section() {
|
||||||
let func_type_index = self.require_function(ItemIndex::IndexSpace(start_function))?;
|
let func_type_index = self.require_function(ItemIndex::IndexSpace(start_function))?;
|
||||||
|
@ -8,18 +8,13 @@ use std::collections::{HashMap, VecDeque};
|
|||||||
use elements::{Opcode, BlockType, Local};
|
use elements::{Opcode, BlockType, Local};
|
||||||
use interpreter::Error;
|
use interpreter::Error;
|
||||||
use interpreter::module::{ModuleInstanceInterface, CallerContext, ItemIndex, InternalFunctionReference, FunctionSignature};
|
use interpreter::module::{ModuleInstanceInterface, CallerContext, ItemIndex, InternalFunctionReference, FunctionSignature};
|
||||||
use interpreter::stack::StackWithLimit;
|
|
||||||
use interpreter::value::{
|
use interpreter::value::{
|
||||||
RuntimeValue, TryInto, WrapInto, TryTruncateInto, ExtendInto,
|
RuntimeValue, TryInto, WrapInto, TryTruncateInto, ExtendInto,
|
||||||
ArithmeticOps, Integer, Float, LittleEndianConvert, TransmuteInto,
|
ArithmeticOps, Integer, Float, LittleEndianConvert, TransmuteInto,
|
||||||
};
|
};
|
||||||
use interpreter::validator::{BlockFrame, BlockFrameType};
|
|
||||||
use interpreter::variable::VariableInstance;
|
use interpreter::variable::VariableInstance;
|
||||||
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType};
|
||||||
/// Index of default linear memory.
|
use common::stack::StackWithLimit;
|
||||||
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
|
||||||
/// Index of default table.
|
|
||||||
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
|
||||||
|
|
||||||
/// Function interpreter.
|
/// Function interpreter.
|
||||||
pub struct Interpreter;
|
pub struct Interpreter;
|
||||||
@ -439,6 +434,7 @@ impl Interpreter {
|
|||||||
context
|
context
|
||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop()
|
.pop()
|
||||||
|
.map_err(Into::into)
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +475,7 @@ impl Interpreter {
|
|||||||
fn run_get_global<'a>(context: &mut FunctionContext, index: u32) -> Result<InstructionOutcome<'a>, Error> {
|
fn run_get_global<'a>(context: &mut FunctionContext, index: u32) -> Result<InstructionOutcome<'a>, Error> {
|
||||||
context.module()
|
context.module()
|
||||||
.global(ItemIndex::IndexSpace(index), None, Some(context.externals))
|
.global(ItemIndex::IndexSpace(index), None, Some(context.externals))
|
||||||
.and_then(|g| context.value_stack_mut().push(g.get()))
|
.and_then(|g| context.value_stack_mut().push(g.get()).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,6 +483,7 @@ impl Interpreter {
|
|||||||
context
|
context
|
||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop()
|
.pop()
|
||||||
|
.map_err(Into::into)
|
||||||
.and_then(|v| context.module().global(ItemIndex::IndexSpace(index), None, Some(context.externals)).and_then(|g| g.set(v)))
|
.and_then(|v| context.module().global(ItemIndex::IndexSpace(index), None, Some(context.externals)).and_then(|g| g.set(v)))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
@ -498,7 +495,7 @@ impl Interpreter {
|
|||||||
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
||||||
.and_then(|m| m.get(address, mem::size_of::<T>()))
|
.and_then(|m| m.get(address, mem::size_of::<T>()))
|
||||||
.and_then(|b| T::from_little_endian(b))
|
.and_then(|b| T::from_little_endian(b))
|
||||||
.and_then(|n| context.value_stack_mut().push(n.into()))
|
.and_then(|n| context.value_stack_mut().push(n.into()).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,6 +510,7 @@ impl Interpreter {
|
|||||||
context
|
context
|
||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.push(stack_value.into())
|
.push(stack_value.into())
|
||||||
|
.map_err(Into::into)
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,9 +527,21 @@ impl Interpreter {
|
|||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_store_wrap<'a, T, U>(context: &mut FunctionContext, _align: u32, offset: u32) -> Result<InstructionOutcome<'a>, Error>
|
fn run_store_wrap<'a, T, U>(
|
||||||
where RuntimeValue: TryInto<T, Error>, T: WrapInto<U>, U: LittleEndianConvert {
|
context: &mut FunctionContext,
|
||||||
let stack_value: T = context.value_stack_mut().pop().and_then(|v| v.try_into())?;
|
_align: u32,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<InstructionOutcome<'a>, Error>
|
||||||
|
where
|
||||||
|
RuntimeValue: TryInto<T, Error>,
|
||||||
|
T: WrapInto<U>,
|
||||||
|
U: LittleEndianConvert,
|
||||||
|
{
|
||||||
|
let stack_value: T = context
|
||||||
|
.value_stack_mut()
|
||||||
|
.pop()
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(|v| v.try_into())?;
|
||||||
let stack_value = stack_value.wrap_into().into_little_endian();
|
let stack_value = stack_value.wrap_into().into_little_endian();
|
||||||
let address = effective_address(offset, context.value_stack_mut().pop_as::<u32>()?)?;
|
let address = effective_address(offset, context.value_stack_mut().pop_as::<u32>()?)?;
|
||||||
context.module()
|
context.module()
|
||||||
@ -541,19 +551,31 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_current_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
|
fn run_current_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
|
||||||
context.module()
|
context
|
||||||
|
.module()
|
||||||
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
||||||
.map(|m| m.size())
|
.map(|m| m.size())
|
||||||
.and_then(|s| context.value_stack_mut().push(RuntimeValue::I32(s as i32)))
|
.and_then(|s|
|
||||||
|
context
|
||||||
|
.value_stack_mut()
|
||||||
|
.push(RuntimeValue::I32(s as i32))
|
||||||
|
.map_err(Into::into)
|
||||||
|
)
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_grow_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
|
fn run_grow_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
|
||||||
let pages: u32 = context.value_stack_mut().pop_as()?;
|
let pages: u32 = context.value_stack_mut().pop_as()?;
|
||||||
context.module()
|
context
|
||||||
|
.module()
|
||||||
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
|
||||||
.and_then(|m| m.grow(pages))
|
.and_then(|m| m.grow(pages))
|
||||||
.and_then(|m| context.value_stack_mut().push(RuntimeValue::I32(m as i32)))
|
.and_then(|m|
|
||||||
|
context
|
||||||
|
.value_stack_mut()
|
||||||
|
.push(RuntimeValue::I32(m as i32))
|
||||||
|
.map_err(Into::into)
|
||||||
|
)
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,6 +583,7 @@ impl Interpreter {
|
|||||||
context
|
context
|
||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.push(val)
|
.push(val)
|
||||||
|
.map_err(Into::into)
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,7 +593,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_as::<T>()
|
.pop_as::<T>()
|
||||||
.map(|v| RuntimeValue::I32(if v == Default::default() { 1 } else { 0 }))
|
.map(|v| RuntimeValue::I32(if v == Default::default() { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,7 +603,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left == right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left == right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,7 +613,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left != right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left != right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +623,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left < right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left < right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,7 +633,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left > right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left > right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,7 +643,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left <= right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left <= right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,7 +653,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_pair_as::<T>()
|
.pop_pair_as::<T>()
|
||||||
.map(|(left, right)| RuntimeValue::I32(if left >= right { 1 } else { 0 }))
|
.map(|(left, right)| RuntimeValue::I32(if left >= right { 1 } else { 0 }))
|
||||||
.and_then(|v| context.value_stack_mut().push(v))
|
.and_then(|v| context.value_stack_mut().push(v).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -911,11 +934,18 @@ impl Interpreter {
|
|||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_extend<'a, T, U, V>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error>
|
fn run_extend<'a, T, U, V>(
|
||||||
where RuntimeValue: From<V> + TryInto<T, Error>, T: ExtendInto<U>, U: TransmuteInto<V> {
|
context: &mut FunctionContext,
|
||||||
|
) -> Result<InstructionOutcome<'a>, Error>
|
||||||
|
where
|
||||||
|
RuntimeValue: From<V> + TryInto<T, Error>,
|
||||||
|
T: ExtendInto<U>,
|
||||||
|
U: TransmuteInto<V>,
|
||||||
|
{
|
||||||
context
|
context
|
||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_as::<T>()
|
.pop_as::<T>()
|
||||||
|
.map_err(Error::into)
|
||||||
.map(|v| v.extend_into())
|
.map(|v| v.extend_into())
|
||||||
.map(|v| v.transmute_into())
|
.map(|v| v.transmute_into())
|
||||||
.map(|v| context.value_stack_mut().push(v.into()))
|
.map(|v| context.value_stack_mut().push(v.into()))
|
||||||
@ -928,7 +958,7 @@ impl Interpreter {
|
|||||||
.value_stack_mut()
|
.value_stack_mut()
|
||||||
.pop_as::<T>()
|
.pop_as::<T>()
|
||||||
.map(TransmuteInto::transmute_into)
|
.map(TransmuteInto::transmute_into)
|
||||||
.and_then(|val| context.value_stack_mut().push(val.into()))
|
.and_then(|val| context.value_stack_mut().push(val.into()).map_err(Into::into))
|
||||||
.map(|_| InstructionOutcome::RunNextInstruction)
|
.map(|_| InstructionOutcome::RunNextInstruction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1038,19 +1068,18 @@ impl<'a> FunctionContext<'a> {
|
|||||||
BlockFrameType::Function => usize::MAX,
|
BlockFrameType::Function => usize::MAX,
|
||||||
_ => labels[&begin_position] + 1,
|
_ => labels[&begin_position] + 1,
|
||||||
};
|
};
|
||||||
self.frame_stack.push(BlockFrame {
|
Ok(self.frame_stack.push(BlockFrame {
|
||||||
frame_type: frame_type,
|
frame_type: frame_type,
|
||||||
block_type: block_type,
|
block_type: block_type,
|
||||||
begin_position: begin_position,
|
begin_position: begin_position,
|
||||||
branch_position: branch_position,
|
branch_position: branch_position,
|
||||||
end_position: end_position,
|
end_position: end_position,
|
||||||
value_stack_len: self.value_stack.len(),
|
value_stack_len: self.value_stack.len(),
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discard_frame(&mut self) -> Result<(), Error> {
|
pub fn discard_frame(&mut self) -> Result<(), Error> {
|
||||||
self.frame_stack.pop()
|
Ok(self.frame_stack.pop().map(|_| ())?)
|
||||||
.map(|_| ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_frame(&mut self, is_branch: bool) -> Result<(), Error> {
|
pub fn pop_frame(&mut self, is_branch: bool) -> Result<(), Error> {
|
||||||
|
@ -1,121 +1,32 @@
|
|||||||
use std::collections::VecDeque;
|
use interpreter::{Error as InterpreterError};
|
||||||
use interpreter::Error;
|
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
use interpreter::value::{RuntimeValue, TryInto};
|
||||||
|
use common::stack::StackWithLimit;
|
||||||
/// Stack with limit.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StackWithLimit<T> where T: Clone {
|
|
||||||
/// Stack values.
|
|
||||||
values: VecDeque<T>,
|
|
||||||
/// Stack limit (maximal stack len).
|
|
||||||
limit: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> StackWithLimit<T> where T: Clone {
|
|
||||||
pub fn with_data(data: Vec<T>, limit: usize) -> Self {
|
|
||||||
StackWithLimit {
|
|
||||||
values: data.into_iter().collect(),
|
|
||||||
limit: limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_limit(limit: usize) -> Self {
|
|
||||||
StackWithLimit {
|
|
||||||
values: VecDeque::new(),
|
|
||||||
limit: limit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.values.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn limit(&self) -> usize {
|
|
||||||
self.limit
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values(&self) -> &VecDeque<T> {
|
|
||||||
&self.values
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top(&self) -> Result<&T, Error> {
|
|
||||||
self.values
|
|
||||||
.back()
|
|
||||||
.ok_or(Error::Stack("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
|
|
||||||
self.values
|
|
||||||
.back_mut()
|
|
||||||
.ok_or(Error::Stack("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, index: usize) -> Result<&T, Error> {
|
|
||||||
if index >= self.values.len() {
|
|
||||||
return Err(Error::Stack(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
|
||||||
if self.values.len() >= self.limit {
|
|
||||||
return Err(Error::Stack(format!("exceeded stack limit {}", self.limit)));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values.push_back(value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_penultimate(&mut self, value: T) -> Result<(), Error> {
|
|
||||||
if self.values.is_empty() {
|
|
||||||
return Err(Error::Stack("trying to insert penultimate element into empty stack".into()));
|
|
||||||
}
|
|
||||||
self.push(value)?;
|
|
||||||
|
|
||||||
let last_index = self.values.len() - 1;
|
|
||||||
let penultimate_index = last_index - 1;
|
|
||||||
self.values.swap(last_index, penultimate_index);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Result<T, Error> {
|
|
||||||
self.values
|
|
||||||
.pop_back()
|
|
||||||
.ok_or(Error::Stack("non-empty stack expected".into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, new_size: usize, dummy: T) {
|
|
||||||
debug_assert!(new_size <= self.values.len());
|
|
||||||
self.values.resize(new_size, dummy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackWithLimit<RuntimeValue> {
|
impl StackWithLimit<RuntimeValue> {
|
||||||
pub fn pop_as<T>(&mut self) -> Result<T, Error>
|
pub fn pop_as<T>(&mut self) -> Result<T, InterpreterError>
|
||||||
where RuntimeValue: TryInto<T, Error> {
|
where
|
||||||
self.pop().and_then(TryInto::try_into)
|
RuntimeValue: TryInto<T, InterpreterError>,
|
||||||
|
{
|
||||||
|
let value = self.pop()?;
|
||||||
|
TryInto::try_into(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_pair(&mut self) -> Result<(RuntimeValue, RuntimeValue), Error> {
|
pub fn pop_pair(&mut self) -> Result<(RuntimeValue, RuntimeValue), InterpreterError> {
|
||||||
let right = self.pop()?;
|
let right = self.pop()?;
|
||||||
let left = self.pop()?;
|
let left = self.pop()?;
|
||||||
Ok((left, right))
|
Ok((left, right))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_pair_as<T>(&mut self) -> Result<(T, T), Error>
|
pub fn pop_pair_as<T>(&mut self) -> Result<(T, T), InterpreterError>
|
||||||
where RuntimeValue: TryInto<T, Error> {
|
where
|
||||||
|
RuntimeValue: TryInto<T, InterpreterError>,
|
||||||
|
{
|
||||||
let right = self.pop_as()?;
|
let right = self.pop_as()?;
|
||||||
let left = self.pop_as()?;
|
let left = self.pop_as()?;
|
||||||
Ok((left, right))
|
Ok((left, right))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_triple(&mut self) -> Result<(RuntimeValue, RuntimeValue, RuntimeValue), Error> {
|
pub fn pop_triple(&mut self) -> Result<(RuntimeValue, RuntimeValue, RuntimeValue), InterpreterError> {
|
||||||
let right = self.pop()?;
|
let right = self.pop()?;
|
||||||
let mid = self.pop()?;
|
let mid = self.pop()?;
|
||||||
let left = self.pop()?;
|
let left = self.pop()?;
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
///! Basic tests for instructions/constructions, missing in wabt tests
|
///! Basic tests for instructions/constructions, missing in wabt tests
|
||||||
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::Arc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use builder::module;
|
use builder::module;
|
||||||
use elements::{ExportEntry, Internal, ImportEntry, External, GlobalEntry, GlobalType,
|
use elements::{ExportEntry, Internal, ImportEntry, External, GlobalEntry, GlobalType,
|
||||||
InitExpr, ValueType, BlockType, Opcodes, Opcode, FunctionType, TableType, MemoryType};
|
InitExpr, ValueType, Opcodes, Opcode, FunctionType, TableType, MemoryType};
|
||||||
use interpreter::{Error, UserError, ProgramInstance};
|
use interpreter::{Error, UserError, ProgramInstance};
|
||||||
use interpreter::native::{native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
|
use interpreter::native::{native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor};
|
||||||
use interpreter::memory::MemoryInstance;
|
use interpreter::memory::MemoryInstance;
|
||||||
use interpreter::module::{ModuleInstance, ModuleInstanceInterface, CallerContext, ItemIndex, ExecutionParams, ExportEntryType, FunctionSignature};
|
use interpreter::module::{ModuleInstanceInterface, CallerContext, ItemIndex, ExecutionParams, ExportEntryType, FunctionSignature};
|
||||||
use interpreter::validator::{FunctionValidationContext, Validator};
|
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
use interpreter::value::{RuntimeValue, TryInto};
|
||||||
use interpreter::variable::{VariableInstance, ExternalVariableValue, VariableType};
|
use interpreter::variable::{VariableInstance, ExternalVariableValue, VariableType};
|
||||||
use super::utils::program_with_default_env;
|
use super::utils::program_with_default_env;
|
||||||
@ -83,7 +82,7 @@ fn wrong_import() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn global_get_set() {
|
fn global_get_set() {
|
||||||
let module = module()
|
let module = module()
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const(42)])))
|
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])))
|
||||||
.function()
|
.function()
|
||||||
.signature().return_type().i32().build()
|
.signature().return_type().i32().build()
|
||||||
.body().with_opcodes(Opcodes::new(vec![
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
@ -472,26 +471,6 @@ fn env_native_export_entry_type_check() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn if_else_with_return_type_validation() {
|
|
||||||
let module_instance = ModuleInstance::new(Weak::default(), "test".into(), module().build()).unwrap();
|
|
||||||
let mut context = FunctionValidationContext::new(&module_instance, None, &[], 1024, 1024, FunctionSignature::Module(&FunctionType::default()));
|
|
||||||
|
|
||||||
Validator::validate_function(&mut context, BlockType::NoResult, &[
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::If(BlockType::NoResult),
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::If(BlockType::Value(ValueType::I32)),
|
|
||||||
Opcode::I32Const(1),
|
|
||||||
Opcode::Else,
|
|
||||||
Opcode::I32Const(2),
|
|
||||||
Opcode::End,
|
|
||||||
Opcode::Drop,
|
|
||||||
Opcode::End,
|
|
||||||
Opcode::End,
|
|
||||||
]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn memory_import_limits_initial() {
|
fn memory_import_limits_initial() {
|
||||||
let core_module = module()
|
let core_module = module()
|
||||||
|
@ -18,9 +18,9 @@ mod utils {
|
|||||||
.with_min(64)
|
.with_min(64)
|
||||||
.build()
|
.build()
|
||||||
.with_export(ExportEntry::new("table".into(), Internal::Table(0)))
|
.with_export(ExportEntry::new("table".into(), Internal::Table(0)))
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0)])))
|
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])))
|
||||||
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
|
.with_export(ExportEntry::new("tableBase".into(), Internal::Global(0)))
|
||||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0)])))
|
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])))
|
||||||
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
|
.with_export(ExportEntry::new("memoryBase".into(), Internal::Global(1)))
|
||||||
.build();
|
.build();
|
||||||
program.add_module("env", env_module, None).unwrap();
|
program.add_module("env", env_module, None).unwrap();
|
||||||
|
@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use elements::{Opcode, BlockType, ValueType};
|
use elements::{Opcode, BlockType, ValueType};
|
||||||
use interpreter::Error;
|
use interpreter::Error;
|
||||||
use interpreter::runner::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use interpreter::module::{ModuleInstance, ModuleInstanceInterface, ItemIndex, FunctionSignature};
|
use interpreter::module::{ModuleInstance, ModuleInstanceInterface, ItemIndex, FunctionSignature};
|
||||||
use interpreter::stack::StackWithLimit;
|
use common::stack::StackWithLimit;
|
||||||
use interpreter::variable::VariableType;
|
use interpreter::variable::VariableType;
|
||||||
|
|
||||||
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
||||||
@ -597,7 +597,7 @@ impl<'a> FunctionValidationContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
pub fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
self.value_stack.push(value_type.into())
|
Ok(self.value_stack.push(value_type.into())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
pub fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
@ -636,26 +636,26 @@ impl<'a> FunctionValidationContext<'a> {
|
|||||||
|
|
||||||
pub fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
pub fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
||||||
self.check_stack_access()?;
|
self.check_stack_access()?;
|
||||||
self.value_stack.top().map(Clone::clone)
|
Ok(self.value_stack.top().map(Clone::clone)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unreachable(&mut self) -> Result<(), Error> {
|
pub fn unreachable(&mut self) -> Result<(), Error> {
|
||||||
self.value_stack.push(StackValueType::AnyUnlimited)
|
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn top_label(&self) -> Result<&BlockFrame, Error> {
|
pub fn top_label(&self) -> Result<&BlockFrame, Error> {
|
||||||
self.frame_stack.top()
|
Ok(self.frame_stack.top()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
pub fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
||||||
self.frame_stack.push(BlockFrame {
|
Ok(self.frame_stack.push(BlockFrame {
|
||||||
frame_type: frame_type,
|
frame_type: frame_type,
|
||||||
block_type: block_type,
|
block_type: block_type,
|
||||||
begin_position: self.position,
|
begin_position: self.position,
|
||||||
branch_position: self.position,
|
branch_position: self.position,
|
||||||
end_position: self.position,
|
end_position: self.position,
|
||||||
value_stack_len: self.value_stack.len(),
|
value_stack_len: self.value_stack.len(),
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
pub fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
||||||
@ -683,7 +683,7 @@ impl<'a> FunctionValidationContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
pub fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
||||||
self.frame_stack.get(idx as usize)
|
Ok(self.frame_stack.get(idx as usize)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn return_type(&self) -> Result<BlockType, Error> {
|
pub fn return_type(&self) -> Result<BlockType, Error> {
|
||||||
|
@ -10,6 +10,8 @@ extern crate parking_lot;
|
|||||||
pub mod elements;
|
pub mod elements;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod interpreter;
|
pub mod interpreter;
|
||||||
|
mod validation;
|
||||||
|
mod common;
|
||||||
|
|
||||||
pub use elements::{
|
pub use elements::{
|
||||||
Error as SerializationError,
|
Error as SerializationError,
|
||||||
|
82
src/validation/context.rs
Normal file
82
src/validation/context.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use elements::{MemoryType, TableType, GlobalType, Type};
|
||||||
|
use elements::{BlockType, ValueType};
|
||||||
|
use validation::Error;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ModuleContext {
|
||||||
|
pub memories: Vec<MemoryType>,
|
||||||
|
pub tables: Vec<TableType>,
|
||||||
|
pub globals: Vec<GlobalType>,
|
||||||
|
pub types: Vec<Type>,
|
||||||
|
pub func_type_indexes: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleContext {
|
||||||
|
pub fn memories(&self) -> &[MemoryType] {
|
||||||
|
&self.memories
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tables(&self) -> &[TableType] {
|
||||||
|
&self.tables
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn globals(&self) -> &[GlobalType] {
|
||||||
|
&self.globals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn types(&self) -> &[Type] {
|
||||||
|
&self.types
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func_type_indexes(&self) -> &[u32] {
|
||||||
|
&self.func_type_indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
|
||||||
|
if self.memories().get(idx as usize).is_none() {
|
||||||
|
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
|
||||||
|
self.tables()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||||
|
let ty_idx = self.func_type_indexes()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
|
||||||
|
self.require_function_type(*ty_idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
|
||||||
|
let &Type::Function(ref ty) = self.types()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
|
||||||
|
|
||||||
|
let params = ty.params();
|
||||||
|
let return_ty = ty.return_type()
|
||||||
|
.map(BlockType::Value)
|
||||||
|
.unwrap_or(BlockType::NoResult);
|
||||||
|
Ok((params, return_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
|
||||||
|
let global = self.globals()
|
||||||
|
.get(idx as usize)
|
||||||
|
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
|
||||||
|
|
||||||
|
if let Some(expected_mutable) = mutability {
|
||||||
|
if expected_mutable && !global.is_mutable() {
|
||||||
|
return Err(Error(format!("Expected global {} to be mutable", idx)));
|
||||||
|
}
|
||||||
|
if !expected_mutable && global.is_mutable() {
|
||||||
|
return Err(Error(format!("Expected global {} to be immutable", idx)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(global)
|
||||||
|
}
|
||||||
|
}
|
774
src/validation/func.rs
Normal file
774
src/validation/func.rs
Normal file
@ -0,0 +1,774 @@
|
|||||||
|
use std::u32;
|
||||||
|
use std::iter::repeat;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
||||||
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
|
use validation::context::ModuleContext;
|
||||||
|
|
||||||
|
use validation::Error;
|
||||||
|
|
||||||
|
use common::stack::StackWithLimit;
|
||||||
|
use common::{BlockFrame, BlockFrameType};
|
||||||
|
|
||||||
|
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).
|
||||||
|
const NATURAL_ALIGNMENT: u32 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
/// Maximum number of entries in value stack.
|
||||||
|
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
|
||||||
|
/// Maximum number of entries in frame stack.
|
||||||
|
const DEFAULT_FRAME_STACK_LIMIT: usize = 1024;
|
||||||
|
|
||||||
|
/// Function validation context.
|
||||||
|
struct FunctionValidationContext<'a> {
|
||||||
|
/// Wasm module
|
||||||
|
module: &'a ModuleContext,
|
||||||
|
/// Current instruction position.
|
||||||
|
position: usize,
|
||||||
|
/// Local variables.
|
||||||
|
locals: &'a [ValueType],
|
||||||
|
/// Value stack.
|
||||||
|
value_stack: StackWithLimit<StackValueType>,
|
||||||
|
/// Frame stack.
|
||||||
|
frame_stack: StackWithLimit<BlockFrame>,
|
||||||
|
/// Function return type. None if validating expression.
|
||||||
|
return_type: Option<BlockType>,
|
||||||
|
/// Labels positions.
|
||||||
|
labels: HashMap<usize, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value type on the stack.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum StackValueType {
|
||||||
|
/// Any value type.
|
||||||
|
Any,
|
||||||
|
/// Any number of any values of any type.
|
||||||
|
AnyUnlimited,
|
||||||
|
/// Concrete value type.
|
||||||
|
Specific(ValueType),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function validator.
|
||||||
|
pub struct Validator;
|
||||||
|
|
||||||
|
/// Instruction outcome.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum InstructionOutcome {
|
||||||
|
/// Continue with next instruction.
|
||||||
|
ValidateNextInstruction,
|
||||||
|
/// Unreachable instruction reached.
|
||||||
|
Unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Validator {
|
||||||
|
pub fn validate_function(
|
||||||
|
module: &ModuleContext,
|
||||||
|
func: &Func,
|
||||||
|
body: &FuncBody,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
||||||
|
|
||||||
|
// locals = (params + vars)
|
||||||
|
let mut locals = params.to_vec();
|
||||||
|
locals.extend(
|
||||||
|
body.locals()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|l| repeat(l.value_type())
|
||||||
|
.take(l.count() as usize)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut context = FunctionValidationContext::new(
|
||||||
|
&module,
|
||||||
|
&locals,
|
||||||
|
DEFAULT_VALUE_STACK_LIMIT,
|
||||||
|
DEFAULT_FRAME_STACK_LIMIT,
|
||||||
|
result_ty,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.push_label(BlockFrameType::Function, result_ty)?;
|
||||||
|
Validator::validate_function_block(&mut context, body.code().elements())?;
|
||||||
|
while !context.frame_stack.is_empty() {
|
||||||
|
context.pop_label()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
|
||||||
|
let body_len = body.len();
|
||||||
|
if body_len == 0 {
|
||||||
|
return Err(Error("Non-empty function body expected".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let opcode = &body[context.position];
|
||||||
|
match Validator::validate_instruction(context, opcode)? {
|
||||||
|
InstructionOutcome::ValidateNextInstruction => (),
|
||||||
|
InstructionOutcome::Unreachable => context.unreachable()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
context.position += 1;
|
||||||
|
if context.position == body_len {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
|
||||||
|
use self::Opcode::*;
|
||||||
|
debug!(target: "validator", "validating {:?}", opcode);
|
||||||
|
match *opcode {
|
||||||
|
Unreachable => Ok(InstructionOutcome::Unreachable),
|
||||||
|
Nop => Ok(InstructionOutcome::ValidateNextInstruction),
|
||||||
|
Block(block_type) => Validator::validate_block(context, block_type),
|
||||||
|
Loop(block_type) => Validator::validate_loop(context, block_type),
|
||||||
|
If(block_type) => Validator::validate_if(context, block_type),
|
||||||
|
Else => Validator::validate_else(context),
|
||||||
|
End => Validator::validate_end(context),
|
||||||
|
Br(idx) => Validator::validate_br(context, idx),
|
||||||
|
BrIf(idx) => Validator::validate_br_if(context, idx),
|
||||||
|
BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
|
||||||
|
Return => Validator::validate_return(context),
|
||||||
|
|
||||||
|
Call(index) => Validator::validate_call(context, index),
|
||||||
|
CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
|
||||||
|
|
||||||
|
Drop => Validator::validate_drop(context),
|
||||||
|
Select => Validator::validate_select(context),
|
||||||
|
|
||||||
|
GetLocal(index) => Validator::validate_get_local(context, index),
|
||||||
|
SetLocal(index) => Validator::validate_set_local(context, index),
|
||||||
|
TeeLocal(index) => Validator::validate_tee_local(context, index),
|
||||||
|
GetGlobal(index) => Validator::validate_get_global(context, index),
|
||||||
|
SetGlobal(index) => Validator::validate_set_global(context, index),
|
||||||
|
|
||||||
|
I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32),
|
||||||
|
I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64),
|
||||||
|
F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32),
|
||||||
|
F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64),
|
||||||
|
I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||||
|
I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
|
||||||
|
I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||||
|
I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
|
||||||
|
I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||||
|
I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
|
||||||
|
I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||||
|
I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
|
||||||
|
I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||||
|
I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
|
||||||
|
|
||||||
|
I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32),
|
||||||
|
I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64),
|
||||||
|
F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32),
|
||||||
|
F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64),
|
||||||
|
I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32),
|
||||||
|
I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32),
|
||||||
|
I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64),
|
||||||
|
I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64),
|
||||||
|
I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64),
|
||||||
|
|
||||||
|
CurrentMemory(_) => Validator::validate_current_memory(context),
|
||||||
|
GrowMemory(_) => Validator::validate_grow_memory(context),
|
||||||
|
|
||||||
|
I32Const(_) => Validator::validate_const(context, ValueType::I32),
|
||||||
|
I64Const(_) => Validator::validate_const(context, ValueType::I64),
|
||||||
|
F32Const(_) => Validator::validate_const(context, ValueType::F32),
|
||||||
|
F64Const(_) => Validator::validate_const(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32Eqz => Validator::validate_testop(context, ValueType::I32),
|
||||||
|
I32Eq => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32Ne => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LtS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LtU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GtS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GtU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LeS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32LeU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GeS => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
I32GeU => Validator::validate_relop(context, ValueType::I32),
|
||||||
|
|
||||||
|
I64Eqz => Validator::validate_testop(context, ValueType::I64),
|
||||||
|
I64Eq => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64Ne => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LtS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LtU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GtS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GtU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LeS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64LeU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GeS => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
I64GeU => Validator::validate_relop(context, ValueType::I64),
|
||||||
|
|
||||||
|
F32Eq => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Ne => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Lt => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Gt => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Le => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
F32Ge => Validator::validate_relop(context, ValueType::F32),
|
||||||
|
|
||||||
|
F64Eq => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Ne => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Lt => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Gt => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Le => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
F64Ge => Validator::validate_relop(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32Clz => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Ctz => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Popcnt => Validator::validate_unop(context, ValueType::I32),
|
||||||
|
I32Add => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Sub => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Mul => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32DivS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32DivU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32RemS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32RemU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32And => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Or => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Xor => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Shl => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32ShrS => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32ShrU => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Rotl => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
I32Rotr => Validator::validate_binop(context, ValueType::I32),
|
||||||
|
|
||||||
|
I64Clz => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Ctz => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Popcnt => Validator::validate_unop(context, ValueType::I64),
|
||||||
|
I64Add => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Sub => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Mul => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64DivS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64DivU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64RemS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64RemU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64And => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Or => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Xor => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Shl => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64ShrS => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64ShrU => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Rotl => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
I64Rotr => Validator::validate_binop(context, ValueType::I64),
|
||||||
|
|
||||||
|
F32Abs => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Neg => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Ceil => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Floor => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Trunc => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Nearest => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Sqrt => Validator::validate_unop(context, ValueType::F32),
|
||||||
|
F32Add => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Sub => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Mul => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Div => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Min => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Max => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
F32Copysign => Validator::validate_binop(context, ValueType::F32),
|
||||||
|
|
||||||
|
F64Abs => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Neg => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Ceil => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Floor => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Trunc => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Nearest => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Sqrt => Validator::validate_unop(context, ValueType::F64),
|
||||||
|
F64Add => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Sub => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Mul => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Div => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Min => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Max => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
F64Copysign => Validator::validate_binop(context, ValueType::F64),
|
||||||
|
|
||||||
|
I32WarpI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32),
|
||||||
|
I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||||
|
I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
|
||||||
|
I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||||
|
I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
|
||||||
|
I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||||
|
I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
|
||||||
|
I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||||
|
F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
|
||||||
|
F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32),
|
||||||
|
F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||||
|
F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
|
||||||
|
F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64),
|
||||||
|
|
||||||
|
I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
|
||||||
|
I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
|
||||||
|
F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
|
||||||
|
F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type1.into())?;
|
||||||
|
context.push_value(value_type2.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_any_value().map(|_| ())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
let select_type = context.pop_any_value()?;
|
||||||
|
context.pop_value(select_type)?;
|
||||||
|
context.push_value(select_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
context.push_value(local_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
let value_type = context.pop_any_value()?;
|
||||||
|
if local_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let local_type = context.require_local(index)?;
|
||||||
|
let value_type = context.tee_any_value()?;
|
||||||
|
if local_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let global_type: StackValueType = {
|
||||||
|
let global = context.module.require_global(index, None)?;
|
||||||
|
global.content_type().into()
|
||||||
|
};
|
||||||
|
context.push_value(global_type)?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let global_type: StackValueType = {
|
||||||
|
let global = context.module.require_global(index, Some(true))?;
|
||||||
|
global.content_type().into()
|
||||||
|
};
|
||||||
|
let value_type = context.pop_any_value()?;
|
||||||
|
if global_type != value_type {
|
||||||
|
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
if align != NATURAL_ALIGNMENT {
|
||||||
|
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||||
|
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
if align != NATURAL_ALIGNMENT {
|
||||||
|
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
|
||||||
|
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
let block_type = {
|
||||||
|
let top_frame = context.top_label()?;
|
||||||
|
if top_frame.frame_type != BlockFrameType::IfTrue {
|
||||||
|
return Err(Error("Misplaced else instruction".into()));
|
||||||
|
}
|
||||||
|
top_frame.block_type
|
||||||
|
};
|
||||||
|
context.pop_label()?;
|
||||||
|
|
||||||
|
if let BlockType::Value(value_type) = block_type {
|
||||||
|
context.pop_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
{
|
||||||
|
let top_frame = context.top_label()?;
|
||||||
|
if top_frame.frame_type == BlockFrameType::IfTrue {
|
||||||
|
if top_frame.block_type != BlockType::NoResult {
|
||||||
|
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let (frame_type, frame_block_type) = {
|
||||||
|
let frame = context.require_label(idx)?;
|
||||||
|
(frame.frame_type, frame.block_type)
|
||||||
|
};
|
||||||
|
if frame_type != BlockFrameType::Loop {
|
||||||
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
|
||||||
|
let (frame_type, frame_block_type) = {
|
||||||
|
let frame = context.require_label(idx)?;
|
||||||
|
(frame.frame_type, frame.block_type)
|
||||||
|
};
|
||||||
|
if frame_type != BlockFrameType::Loop {
|
||||||
|
if let BlockType::Value(value_type) = frame_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_table(context: &mut FunctionValidationContext, table: &Vec<u32>, default: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let mut required_block_type = None;
|
||||||
|
|
||||||
|
{
|
||||||
|
let default_block = context.require_label(default)?;
|
||||||
|
if default_block.frame_type != BlockFrameType::Loop {
|
||||||
|
required_block_type = Some(default_block.block_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
for label in table {
|
||||||
|
let label_block = context.require_label(*label)?;
|
||||||
|
if label_block.frame_type != BlockFrameType::Loop {
|
||||||
|
if let Some(required_block_type) = required_block_type {
|
||||||
|
if required_block_type != label_block.block_type {
|
||||||
|
return Err(Error(format!("Labels in br_table points to block of different types: {:?} and {:?}", required_block_type, label_block.block_type)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
required_block_type = Some(label_block.block_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
if let Some(required_block_type) = required_block_type {
|
||||||
|
if let BlockType::Value(value_type) = required_block_type {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
if let BlockType::Value(value_type) = context.return_type()? {
|
||||||
|
context.tee_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
let (argument_types, return_type) = context.module.require_function(idx)?;
|
||||||
|
for argument_type in argument_types.iter().rev() {
|
||||||
|
context.pop_value((*argument_type).into())?;
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = return_type {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
{
|
||||||
|
let table = context.module.require_table(DEFAULT_TABLE_INDEX)?;
|
||||||
|
if table.elem_type() != TableElementType::AnyFunc {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"Table {} has element type {:?} while `anyfunc` expected",
|
||||||
|
idx,
|
||||||
|
table.elem_type()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
let (argument_types, return_type) = context.module.require_function_type(idx)?;
|
||||||
|
for argument_type in argument_types.iter().rev() {
|
||||||
|
context.pop_value((*argument_type).into())?;
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = return_type {
|
||||||
|
context.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.pop_value(ValueType::I32.into())?;
|
||||||
|
context.push_value(ValueType::I32.into())?;
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FunctionValidationContext<'a> {
|
||||||
|
fn new(
|
||||||
|
module: &'a ModuleContext,
|
||||||
|
locals: &'a [ValueType],
|
||||||
|
value_stack_limit: usize,
|
||||||
|
frame_stack_limit: usize,
|
||||||
|
return_type: BlockType,
|
||||||
|
) -> Self {
|
||||||
|
FunctionValidationContext {
|
||||||
|
module: module,
|
||||||
|
position: 0,
|
||||||
|
locals: locals,
|
||||||
|
value_stack: StackWithLimit::with_limit(value_stack_limit),
|
||||||
|
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
|
||||||
|
return_type: Some(return_type),
|
||||||
|
labels: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
Ok(self.value_stack.push(value_type.into())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match self.value_stack.pop()? {
|
||||||
|
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
||||||
|
StackValueType::Any => Ok(()),
|
||||||
|
StackValueType::AnyUnlimited => {
|
||||||
|
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tee_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match *self.value_stack.top()? {
|
||||||
|
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => Ok(()),
|
||||||
|
StackValueType::Any | StackValueType::AnyUnlimited => Ok(()),
|
||||||
|
stack_value_type @ _ => Err(Error(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_any_value(&mut self) -> Result<StackValueType, Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
match self.value_stack.pop()? {
|
||||||
|
StackValueType::Specific(stack_value_type) => Ok(StackValueType::Specific(stack_value_type)),
|
||||||
|
StackValueType::Any => Ok(StackValueType::Any),
|
||||||
|
StackValueType::AnyUnlimited => {
|
||||||
|
self.value_stack.push(StackValueType::AnyUnlimited)?;
|
||||||
|
Ok(StackValueType::Any)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
|
||||||
|
self.check_stack_access()?;
|
||||||
|
Ok(self.value_stack.top().map(Clone::clone)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unreachable(&mut self) -> Result<(), Error> {
|
||||||
|
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn top_label(&self) -> Result<&BlockFrame, Error> {
|
||||||
|
Ok(self.frame_stack.top()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
|
||||||
|
Ok(self.frame_stack.push(BlockFrame {
|
||||||
|
frame_type: frame_type,
|
||||||
|
block_type: block_type,
|
||||||
|
begin_position: self.position,
|
||||||
|
branch_position: self.position,
|
||||||
|
end_position: self.position,
|
||||||
|
value_stack_len: self.value_stack.len(),
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
|
||||||
|
let frame = self.frame_stack.pop()?;
|
||||||
|
let actual_value_type = if self.value_stack.len() > frame.value_stack_len {
|
||||||
|
Some(self.value_stack.pop()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
|
||||||
|
|
||||||
|
match frame.block_type {
|
||||||
|
BlockType::NoResult if actual_value_type.map(|vt| vt.is_any_unlimited()).unwrap_or(true) => (),
|
||||||
|
BlockType::Value(required_value_type) if actual_value_type.map(|vt| vt == required_value_type).unwrap_or(false) => (),
|
||||||
|
_ => return Err(Error(format!("Expected block to return {:?} while it has returned {:?}", frame.block_type, actual_value_type))),
|
||||||
|
}
|
||||||
|
if !self.frame_stack.is_empty() {
|
||||||
|
self.labels.insert(frame.begin_position, self.position);
|
||||||
|
}
|
||||||
|
if let BlockType::Value(value_type) = frame.block_type {
|
||||||
|
self.push_value(value_type.into())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InstructionOutcome::ValidateNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
|
||||||
|
Ok(self.frame_stack.get(idx as usize)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_type(&self) -> Result<BlockType, Error> {
|
||||||
|
self.return_type.ok_or(Error("Trying to return from expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
||||||
|
self.locals.get(idx as usize)
|
||||||
|
.cloned()
|
||||||
|
.map(Into::into)
|
||||||
|
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_stack_access(&self) -> Result<(), Error> {
|
||||||
|
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
|
||||||
|
if self.value_stack.len() > value_stack_min {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error("Trying to access parent frame stack values.".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackValueType {
|
||||||
|
fn is_any(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&StackValueType::Any => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_any_unlimited(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&StackValueType::AnyUnlimited => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_type(&self) -> ValueType {
|
||||||
|
match self {
|
||||||
|
&StackValueType::Any | &StackValueType::AnyUnlimited => unreachable!("must be checked by caller"),
|
||||||
|
&StackValueType::Specific(value_type) => value_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ValueType> for StackValueType {
|
||||||
|
fn from(value_type: ValueType) -> Self {
|
||||||
|
StackValueType::Specific(value_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<StackValueType> for StackValueType {
|
||||||
|
fn eq(&self, other: &StackValueType) -> bool {
|
||||||
|
if self.is_any() || other.is_any() || self.is_any_unlimited() || other.is_any_unlimited() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.value_type() == other.value_type()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<ValueType> for StackValueType {
|
||||||
|
fn eq(&self, other: &ValueType) -> bool {
|
||||||
|
if self.is_any() || self.is_any_unlimited() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.value_type() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<StackValueType> for ValueType {
|
||||||
|
fn eq(&self, other: &StackValueType) -> bool {
|
||||||
|
other == self
|
||||||
|
}
|
||||||
|
}
|
319
src/validation/mod.rs
Normal file
319
src/validation/mod.rs
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use elements::{
|
||||||
|
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType,
|
||||||
|
Module, Opcode, ResizableLimits, TableType, ValueType, InitExpr
|
||||||
|
};
|
||||||
|
use common::stack;
|
||||||
|
use self::context::ModuleContext;
|
||||||
|
use self::func::Validator;
|
||||||
|
|
||||||
|
pub use self::module::ValidatedModule;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod module;
|
||||||
|
mod func;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error(String);
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<stack::Error> for Error {
|
||||||
|
fn from(e: stack::Error) -> Error {
|
||||||
|
Error(format!("Stack: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_module(module: &Module) -> Result<ValidatedModule, Error> {
|
||||||
|
let context = prepare_context(module)?;
|
||||||
|
|
||||||
|
let function_section_len = module
|
||||||
|
.function_section()
|
||||||
|
.map(|s| s.entries().len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
|
||||||
|
if function_section_len != code_section_len {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"length of function section is {}, while len of code section is {}",
|
||||||
|
function_section_len,
|
||||||
|
code_section_len
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate every function body in user modules
|
||||||
|
if function_section_len != 0 {
|
||||||
|
// tests use invalid code
|
||||||
|
let function_section = module
|
||||||
|
.function_section()
|
||||||
|
.expect("function_section_len != 0; qed");
|
||||||
|
let code_section = module
|
||||||
|
.code_section()
|
||||||
|
.expect("function_section_len != 0; function_section_len == code_section_len; qed");
|
||||||
|
// check every function body
|
||||||
|
for (index, function) in function_section.entries().iter().enumerate() {
|
||||||
|
let function_body = code_section
|
||||||
|
.bodies()
|
||||||
|
.get(index as usize)
|
||||||
|
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
||||||
|
Validator::validate_function(&context, function, function_body).map_err(|e| {
|
||||||
|
let Error(ref msg) = e;
|
||||||
|
Error(format!("Function #{} validation error: {}", index, msg))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate start section
|
||||||
|
if let Some(start_function) = module.start_section() {
|
||||||
|
let (params, return_ty) = context.require_function(start_function)?;
|
||||||
|
if return_ty != BlockType::NoResult || params.len() != 0 {
|
||||||
|
return Err(Error(
|
||||||
|
"start function expected to have type [] -> []".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate export section
|
||||||
|
if let Some(export_section) = module.export_section() {
|
||||||
|
for export in export_section.entries() {
|
||||||
|
match *export.internal() {
|
||||||
|
Internal::Function(function_index) => {
|
||||||
|
context.require_function(function_index)?;
|
||||||
|
}
|
||||||
|
Internal::Global(global_index) => {
|
||||||
|
context.require_global(global_index, Some(false))?;
|
||||||
|
}
|
||||||
|
Internal::Memory(memory_index) => {
|
||||||
|
context.require_memory(memory_index)?;
|
||||||
|
}
|
||||||
|
Internal::Table(table_index) => {
|
||||||
|
context.require_table(table_index)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate import section
|
||||||
|
if let Some(import_section) = module.import_section() {
|
||||||
|
for import in import_section.entries() {
|
||||||
|
match *import.external() {
|
||||||
|
External::Function(function_type_index) => {
|
||||||
|
context.require_function(function_type_index)?;
|
||||||
|
},
|
||||||
|
External::Global(ref global_type) => {
|
||||||
|
if global_type.is_mutable() {
|
||||||
|
return Err(Error(format!("trying to import mutable global {}", import.field())));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
External::Memory(ref memory_type) => {
|
||||||
|
memory_type.validate()?;
|
||||||
|
},
|
||||||
|
External::Table(ref table_type) => {
|
||||||
|
table_type.validate()?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there must be no greater than 1 table in tables index space
|
||||||
|
if context.tables().len() > 1 {
|
||||||
|
return Err(Error(format!("too many tables in index space: {}", context.tables().len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// there must be no greater than 1 linear memory in memory index space
|
||||||
|
if context.memories().len() > 1 {
|
||||||
|
return Err(Error(format!("too many memory regions in index space: {}", context.memories().len())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// use data section to initialize linear memory regions
|
||||||
|
if let Some(data_section) = module.data_section() {
|
||||||
|
for data_segment in data_section.entries() {
|
||||||
|
context.require_memory(data_segment.index())?;
|
||||||
|
let init_ty = data_segment.offset().expr_const_type(context.globals())?;
|
||||||
|
if init_ty != ValueType::I32 {
|
||||||
|
return Err(Error("segment offset should return I32".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use element section to fill tables
|
||||||
|
if let Some(element_section) = module.elements_section() {
|
||||||
|
for element_segment in element_section.entries() {
|
||||||
|
context.require_table(element_segment.index())?;
|
||||||
|
|
||||||
|
let init_ty = element_segment.offset().expr_const_type(context.globals())?;
|
||||||
|
if init_ty != ValueType::I32 {
|
||||||
|
return Err(Error("segment offset should return I32".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for function_index in element_segment.members() {
|
||||||
|
context.require_function(*function_index)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ModuleContext {
|
||||||
|
types,
|
||||||
|
tables,
|
||||||
|
memories,
|
||||||
|
globals,
|
||||||
|
func_type_indexes,
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
Ok(ValidatedModule {
|
||||||
|
types,
|
||||||
|
tables,
|
||||||
|
memories,
|
||||||
|
globals,
|
||||||
|
func_type_indexes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_context(module: &Module) -> Result<ModuleContext, Error> {
|
||||||
|
// Copy types from module as is.
|
||||||
|
let types = module
|
||||||
|
.type_section()
|
||||||
|
.map(|ts| ts.types().into_iter().cloned().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Fill elements with imported values.
|
||||||
|
let mut func_type_indexes = Vec::new();
|
||||||
|
let mut tables = Vec::new();
|
||||||
|
let mut memories = Vec::new();
|
||||||
|
let mut globals = Vec::new();
|
||||||
|
|
||||||
|
for import_entry in module
|
||||||
|
.import_section()
|
||||||
|
.map(|i| i.entries())
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
match *import_entry.external() {
|
||||||
|
External::Function(idx) => func_type_indexes.push(idx),
|
||||||
|
External::Table(ref table) => tables.push(table.clone()),
|
||||||
|
External::Memory(ref memory) => memories.push(memory.clone()),
|
||||||
|
External::Global(ref global) => globals.push(global.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate elements with defined in the module.
|
||||||
|
if let Some(function_section) = module.function_section() {
|
||||||
|
for func_entry in function_section.entries() {
|
||||||
|
func_type_indexes.push(func_entry.type_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(table_section) = module.table_section() {
|
||||||
|
for table_entry in table_section.entries() {
|
||||||
|
table_entry.validate()?;
|
||||||
|
tables.push(table_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(mem_section) = module.memory_section() {
|
||||||
|
for mem_entry in mem_section.entries() {
|
||||||
|
mem_entry.validate()?;
|
||||||
|
memories.push(mem_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(global_section) = module.global_section() {
|
||||||
|
// Validation of globals is defined over modified context C', which
|
||||||
|
// contains only imported globals. So we do globals validation
|
||||||
|
// in two passes, in first we validate globals and after all globals are validated
|
||||||
|
// add them in globals list.
|
||||||
|
for global_entry in global_section.entries() {
|
||||||
|
global_entry.validate(&globals)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for global_entry in global_section.entries() {
|
||||||
|
globals.push(global_entry.global_type().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ModuleContext {
|
||||||
|
types,
|
||||||
|
tables,
|
||||||
|
memories,
|
||||||
|
globals,
|
||||||
|
func_type_indexes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResizableLimits {
|
||||||
|
fn validate(&self) -> Result<(), Error> {
|
||||||
|
if let Some(maximum) = self.maximum() {
|
||||||
|
if self.initial() > maximum {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"maximum limit {} is lesser than minimum {}",
|
||||||
|
maximum,
|
||||||
|
self.initial()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryType {
|
||||||
|
fn validate(&self) -> Result<(), Error> {
|
||||||
|
self.limits().validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableType {
|
||||||
|
fn validate(&self) -> Result<(), Error> {
|
||||||
|
self.limits().validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalEntry {
|
||||||
|
fn validate(&self, globals: &[GlobalType]) -> Result<(), Error> {
|
||||||
|
let init = self.init_expr();
|
||||||
|
let init_expr_ty = init.expr_const_type(globals)?;
|
||||||
|
if init_expr_ty != self.global_type().content_type() {
|
||||||
|
return Err(Error(format!(
|
||||||
|
"Trying to initialize variable of type {:?} with value of type {:?}",
|
||||||
|
self.global_type().content_type(),
|
||||||
|
init_expr_ty
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InitExpr {
|
||||||
|
/// Returns type of this constant expression.
|
||||||
|
fn expr_const_type(&self, globals: &[GlobalType]) -> Result<ValueType, Error> {
|
||||||
|
let code = self.code();
|
||||||
|
if code.len() != 2 {
|
||||||
|
return Err(Error("Init expression should always be with length 2".into()));
|
||||||
|
}
|
||||||
|
let expr_ty: ValueType = match code[0] {
|
||||||
|
Opcode::I32Const(_) => ValueType::I32,
|
||||||
|
Opcode::I64Const(_) => ValueType::I64,
|
||||||
|
Opcode::F32Const(_) => ValueType::F32,
|
||||||
|
Opcode::F64Const(_) => ValueType::F64,
|
||||||
|
Opcode::GetGlobal(idx) => match globals.get(idx as usize) {
|
||||||
|
Some(target_global) => {
|
||||||
|
if target_global.is_mutable() {
|
||||||
|
return Err(Error(format!("Global {} is mutable", idx)));
|
||||||
|
}
|
||||||
|
target_global.content_type()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(Error(
|
||||||
|
format!("Global {} doesn't exists or not yet defined", idx),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => return Err(Error("Non constant opcode in init expr".into())),
|
||||||
|
};
|
||||||
|
if code[1] != Opcode::End {
|
||||||
|
return Err(Error("Expression doesn't ends with `end` opcode".into()));
|
||||||
|
}
|
||||||
|
Ok(expr_ty)
|
||||||
|
}
|
||||||
|
}
|
10
src/validation/module.rs
Normal file
10
src/validation/module.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use elements::{MemoryType, TableType, GlobalType, Type};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ValidatedModule {
|
||||||
|
pub memories: Vec<MemoryType>,
|
||||||
|
pub tables: Vec<TableType>,
|
||||||
|
pub globals: Vec<GlobalType>,
|
||||||
|
pub types: Vec<Type>,
|
||||||
|
pub func_type_indexes: Vec<u32>,
|
||||||
|
}
|
301
src/validation/tests.rs
Normal file
301
src/validation/tests.rs
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
use super::validate_module;
|
||||||
|
use builder::module;
|
||||||
|
use elements::{
|
||||||
|
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
|
||||||
|
Opcode, Opcodes, TableType, ValueType, BlockType
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_is_valid() {
|
||||||
|
let module = module().build();
|
||||||
|
assert!(validate_module(&module).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn limits() {
|
||||||
|
let test_cases = vec![
|
||||||
|
// min > max
|
||||||
|
(10, Some(9), false),
|
||||||
|
// min = max
|
||||||
|
(10, Some(10), true),
|
||||||
|
// table/memory is always valid without max
|
||||||
|
(10, None, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (min, max, is_valid) in test_cases {
|
||||||
|
// defined table
|
||||||
|
let m = module()
|
||||||
|
.table()
|
||||||
|
.with_min(min)
|
||||||
|
.with_max(max)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(min, max))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// defined memory
|
||||||
|
let m = module()
|
||||||
|
.memory()
|
||||||
|
.with_min(min)
|
||||||
|
.with_max(max)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
|
// imported table
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(min, max))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_const() {
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(
|
||||||
|
vec![Opcode::I32Const(42), Opcode::End]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// init expr type differs from declared global type
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I64, true),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_global() {
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// get_global can reference only previously defined globals
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// get_global can reference only const globals
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// get_global in init_expr can only refer to imported globals.
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, false),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_init_misc() {
|
||||||
|
// without delimiting End opcode
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::I32Const(42)])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// empty init expr
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// not an constant opcode used
|
||||||
|
let m = module()
|
||||||
|
.with_global(
|
||||||
|
GlobalEntry::new(
|
||||||
|
GlobalType::new(ValueType::I32, true),
|
||||||
|
InitExpr::new(vec![Opcode::Unreachable, Opcode::End])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_limits_validity() {
|
||||||
|
// module cannot contain more than 1 memory atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"memory".into(),
|
||||||
|
External::Memory(MemoryType::new(10, None))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.memory()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
|
// module cannot contain more than 1 table atm.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"core".into(),
|
||||||
|
"table".into(),
|
||||||
|
External::Table(TableType::new(10, None))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.table()
|
||||||
|
.with_min(10)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn funcs() {
|
||||||
|
// recursive function calls is legal.
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature().return_type().i32().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::Call(1),
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.function()
|
||||||
|
.signature().return_type().i32().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::Call(0),
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn globals() {
|
||||||
|
// import immutable global is legal.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, false))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
|
// import mutable global is invalid.
|
||||||
|
let m = module()
|
||||||
|
.with_import(
|
||||||
|
ImportEntry::new(
|
||||||
|
"env".into(),
|
||||||
|
"ext_global".into(),
|
||||||
|
External::Global(GlobalType::new(ValueType::I32, true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
assert!(validate_module(&m).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_with_return_type_validation() {
|
||||||
|
let m = module()
|
||||||
|
.function()
|
||||||
|
.signature().build()
|
||||||
|
.body().with_opcodes(Opcodes::new(vec![
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::If(BlockType::NoResult),
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::If(BlockType::Value(ValueType::I32)),
|
||||||
|
Opcode::I32Const(1),
|
||||||
|
Opcode::Else,
|
||||||
|
Opcode::I32Const(2),
|
||||||
|
Opcode::End,
|
||||||
|
Opcode::Drop,
|
||||||
|
Opcode::End,
|
||||||
|
Opcode::End,
|
||||||
|
])).build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
validate_module(&m).unwrap();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user