Validator extracted.

This commit is contained in:
Sergey Pepyakin 2017-12-01 15:35:01 +03:00
parent 12a1bcfe04
commit 040cbb5056
12 changed files with 1161 additions and 195 deletions

40
src/common/mod.rs Normal file
View 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
View 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);
}
}

View File

@ -1,6 +1,7 @@
//! WebAssembly interpreter module.
use std::any::TypeId;
use validation;
/// Custom user error.
pub trait UserError: 'static + ::std::fmt::Display + ::std::fmt::Debug {
@ -116,6 +117,18 @@ 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 native;
mod imports;
mod memory;
@ -124,7 +137,6 @@ mod program;
mod runner;
mod stack;
mod table;
mod validator;
mod value;
mod variable;

View File

@ -1,19 +1,17 @@
use std::collections::HashMap;
use std::iter::repeat;
use std::sync::{Arc, Weak};
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};
use interpreter::Error;
use interpreter::native::UserFunctionDescriptor;
use interpreter::imports::ModuleImports;
use interpreter::memory::MemoryInstance;
use interpreter::program::ProgramInstanceEssence;
use interpreter::runner::{Interpreter, FunctionContext, prepare_function_args};
use interpreter::stack::StackWithLimit;
use interpreter::table::TableInstance;
use interpreter::validator::{Validator, FunctionValidationContext};
use interpreter::value::{RuntimeValue, TryInto};
use interpreter::variable::{VariableInstance, VariableType};
use common::stack::StackWithLimit;
/// Maximum number of entries in value stack.
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
@ -368,40 +366,41 @@ impl ModuleInstance {
return Err(Error::Validation(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 = self.module.function_section().expect("function_section_len != 0; qed");
let code_section = self.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_labels = {
let function_type = self.function_type_by_index(function.type_ref())?;
let function_body = code_section.bodies().get(index as usize).ok_or(Error::Validation(format!("Missing body for function {}", index)))?;
let mut locals = function_type.params().to_vec();
locals.extend(function_body.locals().iter().flat_map(|l| repeat(l.value_type()).take(l.count() as usize)));
// TODO: pepyakin reimplement
// // validate every function body in user modules
// if function_section_len != 0 { // tests use invalid code
// let function_section = self.module.function_section().expect("function_section_len != 0; qed");
// let code_section = self.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_labels = {
// let function_type = self.function_type_by_index(function.type_ref())?;
// let function_body = code_section.bodies().get(index as usize).ok_or(Error::Validation(format!("Missing body for function {}", index)))?;
// let mut locals = function_type.params().to_vec();
// locals.extend(function_body.locals().iter().flat_map(|l| repeat(l.value_type()).take(l.count() as usize)));
let mut context = FunctionValidationContext::new(
self,
externals,
&locals,
DEFAULT_VALUE_STACK_LIMIT,
DEFAULT_FRAME_STACK_LIMIT,
function_type.clone());
// let mut context = FunctionValidationContext::new(
// self,
// externals,
// &locals,
// DEFAULT_VALUE_STACK_LIMIT,
// DEFAULT_FRAME_STACK_LIMIT,
// function_type.clone());
let block_type = function_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult);
Validator::validate_function(&mut context, block_type, function_body.code().elements())
.map_err(|e| {
if let Error::Validation(msg) = e {
Error::Validation(format!("Function #{} validation error: {}", index, msg))
} else {
e
}
})?;
context.function_labels()
};
self.functions_labels.insert(index as u32, function_labels);
}
}
// let block_type = function_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult);
// Validator::validate_function(&mut context, block_type, function_body.code().elements())
// .map_err(|e| {
// if let Error::Validation(msg) = e {
// Error::Validation(format!("Function #{} validation error: {}", index, msg))
// } else {
// e
// }
// })?;
// context.function_labels()
// };
// self.functions_labels.insert(index as u32, function_labels);
// }
// }
// use data section to initialize linear memory regions
if let Some(data_section) = self.module.data_section() {
@ -670,7 +669,8 @@ impl<'a> CallerContext<'a> {
pub fn check_limits(limits: &ResizableLimits) -> Result<(), Error> {
if let Some(maximum) = limits.maximum() {
if maximum < limits.initial() {
return Err(Error::Validation(format!("maximum limit {} is lesser than minimum {}", maximum, limits.initial())));
panic!()
// return Err(Error::Validation(format!("maximum limit {} is lesser than minimum {}", maximum, limits.initial())));
}
}

View File

@ -8,18 +8,13 @@ use std::collections::{HashMap, VecDeque};
use elements::{Opcode, BlockType, Local};
use interpreter::Error;
use interpreter::module::{ModuleInstanceInterface, CallerContext, ItemIndex, InternalFunctionReference, FunctionSignature};
use interpreter::stack::StackWithLimit;
use interpreter::value::{
RuntimeValue, TryInto, WrapInto, TryTruncateInto, ExtendInto,
ArithmeticOps, Integer, Float, LittleEndianConvert, TransmuteInto,
};
use interpreter::validator::{BlockFrame, BlockFrameType};
use interpreter::variable::VariableInstance;
/// Index of default linear memory.
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX, BlockFrame, BlockFrameType};
use common::stack::{StackWithLimit};
/// Function interpreter.
pub struct Interpreter;
@ -439,6 +434,7 @@ impl Interpreter {
context
.value_stack_mut()
.pop()
.map_err(Into::into)
.map(|_| InstructionOutcome::RunNextInstruction)
}
@ -479,7 +475,7 @@ impl Interpreter {
fn run_get_global<'a>(context: &mut FunctionContext, index: u32) -> Result<InstructionOutcome<'a>, Error> {
context.module()
.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)
}
@ -487,6 +483,7 @@ impl Interpreter {
context
.value_stack_mut()
.pop()
.map_err(Into::into)
.and_then(|v| context.module().global(ItemIndex::IndexSpace(index), None, Some(context.externals)).and_then(|g| g.set(v)))
.map(|_| InstructionOutcome::RunNextInstruction)
}
@ -498,7 +495,7 @@ impl Interpreter {
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
.and_then(|m| m.get(address, mem::size_of::<T>()))
.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)
}
@ -513,6 +510,7 @@ impl Interpreter {
context
.value_stack_mut()
.push(stack_value.into())
.map_err(Into::into)
.map(|_| InstructionOutcome::RunNextInstruction)
}
@ -529,9 +527,21 @@ impl Interpreter {
.map(|_| InstructionOutcome::RunNextInstruction)
}
fn run_store_wrap<'a, T, U>(context: &mut FunctionContext, _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().and_then(|v| v.try_into())?;
fn run_store_wrap<'a, T, U>(
context: &mut FunctionContext,
_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 address = effective_address(offset, context.value_stack_mut().pop_as::<u32>()?)?;
context.module()
@ -541,19 +551,31 @@ impl Interpreter {
}
fn run_current_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
context.module()
context
.module()
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
.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)
}
fn run_grow_memory<'a>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error> {
let pages: u32 = context.value_stack_mut().pop_as()?;
context.module()
context
.module()
.memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX))
.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)
}
@ -561,6 +583,7 @@ impl Interpreter {
context
.value_stack_mut()
.push(val)
.map_err(Into::into)
.map(|_| InstructionOutcome::RunNextInstruction)
}
@ -570,7 +593,7 @@ impl Interpreter {
.value_stack_mut()
.pop_as::<T>()
.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)
}
@ -580,7 +603,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -590,7 +613,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -600,7 +623,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -610,7 +633,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -620,7 +643,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -630,7 +653,7 @@ impl Interpreter {
.value_stack_mut()
.pop_pair_as::<T>()
.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)
}
@ -911,11 +934,18 @@ impl Interpreter {
.map(|_| InstructionOutcome::RunNextInstruction)
}
fn run_extend<'a, T, U, V>(context: &mut FunctionContext) -> Result<InstructionOutcome<'a>, Error>
where RuntimeValue: From<V> + TryInto<T, Error>, T: ExtendInto<U>, U: TransmuteInto<V> {
fn run_extend<'a, T, U, V>(
context: &mut FunctionContext,
) -> Result<InstructionOutcome<'a>, Error>
where
RuntimeValue: From<V> + TryInto<T, Error>,
T: ExtendInto<U>,
U: TransmuteInto<V>,
{
context
.value_stack_mut()
.pop_as::<T>()
.map_err(Error::into)
.map(|v| v.extend_into())
.map(|v| v.transmute_into())
.map(|v| context.value_stack_mut().push(v.into()))
@ -928,7 +958,7 @@ impl Interpreter {
.value_stack_mut()
.pop_as::<T>()
.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)
}
}
@ -1038,19 +1068,18 @@ impl<'a> FunctionContext<'a> {
BlockFrameType::Function => usize::MAX,
_ => labels[&begin_position] + 1,
};
self.frame_stack.push(BlockFrame {
Ok(self.frame_stack.push(BlockFrame {
frame_type: frame_type,
block_type: block_type,
begin_position: begin_position,
branch_position: branch_position,
end_position: end_position,
value_stack_len: self.value_stack.len(),
})
})?)
}
pub fn discard_frame(&mut self) -> Result<(), Error> {
self.frame_stack.pop()
.map(|_| ())
Ok(self.frame_stack.pop().map(|_| ())?)
}
pub fn pop_frame(&mut self, is_branch: bool) -> Result<(), Error> {

View File

@ -1,121 +1,32 @@
use std::collections::VecDeque;
use interpreter::Error;
use interpreter::{Error as InterpreterError};
use interpreter::value::{RuntimeValue, TryInto};
/// 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);
}
}
use common::stack::StackWithLimit;
impl StackWithLimit<RuntimeValue> {
pub fn pop_as<T>(&mut self) -> Result<T, Error>
where RuntimeValue: TryInto<T, Error> {
self.pop().and_then(TryInto::try_into)
pub fn pop_as<T>(&mut self) -> Result<T, InterpreterError>
where
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 left = self.pop()?;
Ok((left, right))
}
pub fn pop_pair_as<T>(&mut self) -> Result<(T, T), Error>
where RuntimeValue: TryInto<T, Error> {
pub fn pop_pair_as<T>(&mut self) -> Result<(T, T), InterpreterError>
where
RuntimeValue: TryInto<T, InterpreterError>,
{
let right = self.pop_as()?;
let left = self.pop_as()?;
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 mid = self.pop()?;
let left = self.pop()?;

View File

@ -472,26 +472,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]
fn memory_import_limits_initial() {
let core_module = module()

View File

@ -3,9 +3,9 @@ use std::sync::Arc;
use std::collections::HashMap;
use elements::{Opcode, BlockType, ValueType};
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::stack::StackWithLimit;
use common::stack::StackWithLimit;
use interpreter::variable::VariableType;
/// Constant from wabt' validator.cc to skip alignment validation (not a part of spec).

View File

@ -11,6 +11,7 @@ pub mod elements;
pub mod builder;
pub mod interpreter;
pub mod validation;
mod common;
pub use elements::{
Error as SerializationError,

824
src/validation/func.rs Normal file
View File

@ -0,0 +1,824 @@
use std::u32;
use std::sync::Arc;
use std::collections::HashMap;
use elements::{Opcode, BlockType, ValueType, TableElementType};
use elements::{FunctionType, Type};
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use validation::module::ValidatedModule;
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;
/// Function validation context.
pub struct FunctionValidationContext<'a> {
/// Wasm module
module: &'a ValidatedModule,
/// 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)]
pub 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)]
pub enum InstructionOutcome {
/// Continue with next instruction.
ValidateNextInstruction,
/// Unreachable instruction reached.
Unreachable,
}
impl Validator {
pub fn validate_function(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode]) -> Result<(), Error> {
context.push_label(BlockFrameType::Function, block_type)?;
Validator::validate_function_block(context, body)?;
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> {
debug!(target: "validator", "validating {:?}", opcode);
match opcode {
&Opcode::Unreachable => Ok(InstructionOutcome::Unreachable),
&Opcode::Nop => Ok(InstructionOutcome::ValidateNextInstruction),
&Opcode::Block(block_type) => Validator::validate_block(context, block_type),
&Opcode::Loop(block_type) => Validator::validate_loop(context, block_type),
&Opcode::If(block_type) => Validator::validate_if(context, block_type),
&Opcode::Else => Validator::validate_else(context),
&Opcode::End => Validator::validate_end(context),
&Opcode::Br(idx) => Validator::validate_br(context, idx),
&Opcode::BrIf(idx) => Validator::validate_br_if(context, idx),
&Opcode::BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
&Opcode::Return => Validator::validate_return(context),
&Opcode::Call(index) => Validator::validate_call(context, index),
&Opcode::CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
&Opcode::Drop => Validator::validate_drop(context),
&Opcode::Select => Validator::validate_select(context),
&Opcode::GetLocal(index) => Validator::validate_get_local(context, index),
&Opcode::SetLocal(index) => Validator::validate_set_local(context, index),
&Opcode::TeeLocal(index) => Validator::validate_tee_local(context, index),
&Opcode::GetGlobal(index) => Validator::validate_get_global(context, index),
&Opcode::SetGlobal(index) => Validator::validate_set_global(context, index),
&Opcode::I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32.into()),
&Opcode::I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64.into()),
&Opcode::F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32.into()),
&Opcode::F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64.into()),
&Opcode::I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32.into()),
&Opcode::I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32.into()),
&Opcode::I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32.into()),
&Opcode::I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32.into()),
&Opcode::I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64.into()),
&Opcode::I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64.into()),
&Opcode::I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64.into()),
&Opcode::I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64.into()),
&Opcode::I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64.into()),
&Opcode::I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64.into()),
&Opcode::I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32.into()),
&Opcode::I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64.into()),
&Opcode::F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32.into()),
&Opcode::F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64.into()),
&Opcode::I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32.into()),
&Opcode::I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32.into()),
&Opcode::I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64.into()),
&Opcode::I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64.into()),
&Opcode::I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64.into()),
&Opcode::CurrentMemory(_) => Validator::validate_current_memory(context),
&Opcode::GrowMemory(_) => Validator::validate_grow_memory(context),
&Opcode::I32Const(_) => Validator::validate_const(context, ValueType::I32.into()),
&Opcode::I64Const(_) => Validator::validate_const(context, ValueType::I64.into()),
&Opcode::F32Const(_) => Validator::validate_const(context, ValueType::F32.into()),
&Opcode::F64Const(_) => Validator::validate_const(context, ValueType::F64.into()),
&Opcode::I32Eqz => Validator::validate_testop(context, ValueType::I32.into()),
&Opcode::I32Eq => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32Ne => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32LtS => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32LtU => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32GtS => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32GtU => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32LeS => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32LeU => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32GeS => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I32GeU => Validator::validate_relop(context, ValueType::I32.into()),
&Opcode::I64Eqz => Validator::validate_testop(context, ValueType::I64.into()),
&Opcode::I64Eq => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64Ne => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64LtS => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64LtU => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64GtS => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64GtU => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64LeS => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64LeU => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64GeS => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::I64GeU => Validator::validate_relop(context, ValueType::I64.into()),
&Opcode::F32Eq => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F32Ne => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F32Lt => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F32Gt => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F32Le => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F32Ge => Validator::validate_relop(context, ValueType::F32.into()),
&Opcode::F64Eq => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::F64Ne => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::F64Lt => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::F64Gt => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::F64Le => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::F64Ge => Validator::validate_relop(context, ValueType::F64.into()),
&Opcode::I32Clz => Validator::validate_unop(context, ValueType::I32.into()),
&Opcode::I32Ctz => Validator::validate_unop(context, ValueType::I32.into()),
&Opcode::I32Popcnt => Validator::validate_unop(context, ValueType::I32.into()),
&Opcode::I32Add => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Sub => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Mul => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32DivS => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32DivU => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32RemS => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32RemU => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32And => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Or => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Xor => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Shl => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32ShrS => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32ShrU => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Rotl => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I32Rotr => Validator::validate_binop(context, ValueType::I32.into()),
&Opcode::I64Clz => Validator::validate_unop(context, ValueType::I64.into()),
&Opcode::I64Ctz => Validator::validate_unop(context, ValueType::I64.into()),
&Opcode::I64Popcnt => Validator::validate_unop(context, ValueType::I64.into()),
&Opcode::I64Add => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Sub => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Mul => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64DivS => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64DivU => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64RemS => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64RemU => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64And => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Or => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Xor => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Shl => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64ShrS => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64ShrU => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Rotl => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::I64Rotr => Validator::validate_binop(context, ValueType::I64.into()),
&Opcode::F32Abs => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Neg => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Ceil => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Floor => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Trunc => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Nearest => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Sqrt => Validator::validate_unop(context, ValueType::F32.into()),
&Opcode::F32Add => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Sub => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Mul => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Div => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Min => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Max => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F32Copysign => Validator::validate_binop(context, ValueType::F32.into()),
&Opcode::F64Abs => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Neg => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Ceil => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Floor => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Trunc => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Nearest => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Sqrt => Validator::validate_unop(context, ValueType::F64.into()),
&Opcode::F64Add => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Sub => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Mul => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Div => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Min => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Max => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::F64Copysign => Validator::validate_binop(context, ValueType::F64.into()),
&Opcode::I32WarpI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::I32.into()),
&Opcode::I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
&Opcode::I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
&Opcode::I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I32.into()),
&Opcode::I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I32.into()),
&Opcode::I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::I64.into()),
&Opcode::I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::I64.into()),
&Opcode::I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I64.into()),
&Opcode::I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I64.into()),
&Opcode::I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
&Opcode::I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
&Opcode::F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
&Opcode::F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
&Opcode::F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F32.into()),
&Opcode::F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F32.into()),
&Opcode::F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::F32.into()),
&Opcode::F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F64.into()),
&Opcode::F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F64.into()),
&Opcode::F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
&Opcode::F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
&Opcode::F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::F64.into()),
&Opcode::I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32.into(), ValueType::I32.into()),
&Opcode::I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64.into(), ValueType::I64.into()),
&Opcode::F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32.into(), ValueType::F32.into()),
&Opcode::F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64.into(), ValueType::F64.into()),
}
}
fn validate_const(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
context.push_value(value_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_unop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type)?;
context.push_value(value_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_binop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type)?;
context.pop_value(value_type)?;
context.push_value(value_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_testop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type)?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_relop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type)?;
context.pop_value(value_type)?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: StackValueType, value_type2: StackValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type1)?;
context.push_value(value_type2)?;
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 = context.require_global(index, None)?;
context.push_value(global_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let global_type = context.require_global(index, Some(true))?;
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: StackValueType) -> 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.require_memory(DEFAULT_MEMORY_INDEX)?;
context.push_value(value_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: StackValueType) -> 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.require_memory(DEFAULT_MEMORY_INDEX)?;
context.pop_value(value_type)?;
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.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> {
context.require_table(DEFAULT_TABLE_INDEX, TableElementType::AnyFunc)?;
context.pop_value(ValueType::I32.into())?;
let (argument_types, return_type) = context.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.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.require_memory(DEFAULT_MEMORY_INDEX)?;
context.pop_value(ValueType::I32.into())?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
}
impl<'a> FunctionValidationContext<'a> {
pub fn new(
module: &'a ValidatedModule,
locals: &'a [ValueType],
value_stack_limit: usize,
frame_stack_limit: usize,
func_type: &'a FunctionType,
) -> 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(func_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult)),
labels: HashMap::new(),
}
}
pub fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
Ok(self.value_stack.push(value_type.into())?)
}
pub 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))),
}
}
pub 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))),
}
}
pub 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)
},
}
}
pub fn tee_any_value(&mut self) -> Result<StackValueType, Error> {
self.check_stack_access()?;
Ok(self.value_stack.top().map(Clone::clone)?)
}
pub fn unreachable(&mut self) -> Result<(), Error> {
Ok(self.value_stack.push(StackValueType::AnyUnlimited)?)
}
pub fn top_label(&self) -> Result<&BlockFrame, Error> {
Ok(self.frame_stack.top()?)
}
pub 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(),
})?)
}
pub 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)
}
pub fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
Ok(self.frame_stack.get(idx as usize)?)
}
pub fn return_type(&self) -> Result<BlockType, Error> {
self.return_type.ok_or(Error("Trying to return from expression".into()))
}
pub 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())))
}
pub fn require_global(
&self,
idx: u32,
mutability: Option<bool>,
) -> Result<StackValueType, Error> {
let global = match self.module.globals().get(idx as usize) {
Some(global) => global,
None => {
return Err(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(match global.content_type() {
ValueType::I32 => StackValueType::Specific(ValueType::I32),
ValueType::I64 => StackValueType::Specific(ValueType::I64),
ValueType::F32 => StackValueType::Specific(ValueType::F32),
ValueType::F64 => StackValueType::Specific(ValueType::F64),
})
}
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
if self.module.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, expected_type: TableElementType) -> Result<(), Error> {
let table = match self.module.tables().get(idx as usize) {
Some(table) => table,
None => {
return Err(Error(format!("Table at index {} doesn't exists", idx)));
}
};
if table.elem_type() != expected_type {
return Err(Error(format!(
"Table {} has element type {:?} while {:?} expected",
idx,
table.elem_type(),
expected_type
)));
}
Ok(())
}
pub fn require_function(&self, idx: u32) -> Result<(Vec<ValueType>, BlockType), Error> {
let ty = match self.module.function_types().get(idx as usize) {
Some(&Type::Function(ref func_ty)) => func_ty,
None => {
return Err(Error(
format!("Function at index {} doesn't exists", idx),
));
}
};
let params = ty.params().to_vec();
let return_ty = ty.return_type()
.map(BlockType::Value)
.unwrap_or(BlockType::NoResult);
Ok((params, return_ty))
}
pub fn require_function_type(&self, idx: u32) -> Result<(Vec<ValueType>, BlockType), Error> {
let ty = match self.module.types().get(idx as usize) {
Some(&Type::Function(ref func_ty)) => func_ty,
None => {
return Err(Error(
format!("Type at index {} doesn't exists", idx),
));
}
};
let params = ty.params().to_vec();
let return_ty = ty.return_type()
.map(BlockType::Value)
.unwrap_or(BlockType::NoResult);
Ok((params, return_ty))
}
pub fn function_labels(self) -> HashMap<usize, usize> {
self.labels
}
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 {
pub fn is_any(&self) -> bool {
match self {
&StackValueType::Any => true,
_ => false,
}
}
pub fn is_any_unlimited(&self) -> bool {
match self {
&StackValueType::AnyUnlimited => true,
_ => false,
}
}
pub 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
}
}

View File

@ -1,8 +1,25 @@
#![allow(unused, missing_docs)]
use elements::{Module, ResizableLimits, MemoryType, TableType};
mod module;
mod func;
pub struct Error(pub String);
use std::fmt;
use elements::{Module, ResizableLimits, MemoryType, TableType};
use common::stack;
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<(), Error> {
// TODO: Functions
@ -124,4 +141,24 @@ mod tests {
.build();
assert!(validate_module(&m).is_ok());
}
// #[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();
// }
}

26
src/validation/module.rs Normal file
View File

@ -0,0 +1,26 @@
use elements::{MemoryType, TableType, GlobalType, Type};
pub struct ValidatedModule {
}
impl ValidatedModule {
pub fn memories(&self) -> &[MemoryType] {
unimplemented!();
}
pub fn tables(&self) -> &[TableType] {
unimplemented!();
}
pub fn globals(&self) -> &[GlobalType] {
unimplemented!();
}
pub fn types(&self) -> &[Type] {
unimplemented!();
}
pub fn function_types(&self) -> &[Type] {
unimplemented!();
}
}