From 040cbb50565b5736f9425dc3f189f7eb7d24152f Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin Date: Fri, 1 Dec 2017 15:35:01 +0300 Subject: [PATCH] Validator extracted. --- src/common/mod.rs | 40 ++ src/common/stack.rs | 106 ++++ src/interpreter/mod.rs | 14 +- src/interpreter/module.rs | 74 +-- src/interpreter/runner.rs | 89 ++-- src/interpreter/stack.rs | 117 +---- src/interpreter/tests/basics.rs | 20 - src/interpreter/validator.rs | 4 +- src/lib.rs | 1 + src/validation/func.rs | 824 ++++++++++++++++++++++++++++++++ src/validation/mod.rs | 41 +- src/validation/module.rs | 26 + 12 files changed, 1161 insertions(+), 195 deletions(-) create mode 100644 src/common/mod.rs create mode 100644 src/common/stack.rs create mode 100644 src/validation/func.rs create mode 100644 src/validation/module.rs diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..50f72ae --- /dev/null +++ b/src/common/mod.rs @@ -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, +} diff --git a/src/common/stack.rs b/src/common/stack.rs new file mode 100644 index 0000000..532dca7 --- /dev/null +++ b/src/common/stack.rs @@ -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 where T: Clone { + /// Stack values. + values: VecDeque, + /// Stack limit (maximal stack len). + limit: usize, +} + +impl StackWithLimit where T: Clone { + pub fn with_data(data: Vec, 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 { + &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 { + 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); + } +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 5749939..f53ed6e 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -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 From for Error where U: UserError + Sized { } } +impl From 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; diff --git a/src/interpreter/module.rs b/src/interpreter/module.rs index 876d566..6289392 100644 --- a/src/interpreter/module.rs +++ b/src/interpreter/module.rs @@ -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()))); } } diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index 23b2485..5e5f38b 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -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, 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::())) .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, Error> - where RuntimeValue: TryInto, T: WrapInto, 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, Error> + where + RuntimeValue: TryInto, + T: WrapInto, + 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::()?)?; context.module() @@ -541,19 +551,31 @@ impl Interpreter { } fn run_current_memory<'a>(context: &mut FunctionContext) -> Result, 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, 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::() .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::() .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::() .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::() .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::() .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::() .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::() .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, Error> - where RuntimeValue: From + TryInto, T: ExtendInto, U: TransmuteInto { + fn run_extend<'a, T, U, V>( + context: &mut FunctionContext, + ) -> Result, Error> + where + RuntimeValue: From + TryInto, + T: ExtendInto, + U: TransmuteInto, + { context .value_stack_mut() .pop_as::() + .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::() .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> { diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index 77f5fb7..ae81933 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -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 where T: Clone { - /// Stack values. - values: VecDeque, - /// Stack limit (maximal stack len). - limit: usize, -} - -impl StackWithLimit where T: Clone { - pub fn with_data(data: Vec, 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 { - &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 { - 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 { - pub fn pop_as(&mut self) -> Result - where RuntimeValue: TryInto { - self.pop().and_then(TryInto::try_into) + pub fn pop_as(&mut self) -> Result + where + RuntimeValue: TryInto, + { + 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(&mut self) -> Result<(T, T), Error> - where RuntimeValue: TryInto { + pub fn pop_pair_as(&mut self) -> Result<(T, T), InterpreterError> + where + RuntimeValue: TryInto, + { 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()?; diff --git a/src/interpreter/tests/basics.rs b/src/interpreter/tests/basics.rs index 3efc20f..6eeecef 100644 --- a/src/interpreter/tests/basics.rs +++ b/src/interpreter/tests/basics.rs @@ -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() diff --git a/src/interpreter/validator.rs b/src/interpreter/validator.rs index 85c0b3a..4662658 100644 --- a/src/interpreter/validator.rs +++ b/src/interpreter/validator.rs @@ -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). diff --git a/src/lib.rs b/src/lib.rs index 2f94d29..b68662e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod elements; pub mod builder; pub mod interpreter; pub mod validation; +mod common; pub use elements::{ Error as SerializationError, diff --git a/src/validation/func.rs b/src/validation/func.rs new file mode 100644 index 0000000..e459c0c --- /dev/null +++ b/src/validation/func.rs @@ -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, + /// Frame stack. + frame_stack: StackWithLimit, + /// Function return type. None if validating expression. + return_type: Option, + /// Labels positions. + labels: HashMap, +} + +/// 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 { + 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 { + context.push_value(value_type)?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_unop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result { + context.pop_value(value_type)?; + context.push_value(value_type)?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_binop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result { + 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 { + context.pop_value(value_type)?; + context.push_value(ValueType::I32.into())?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_relop(context: &mut FunctionValidationContext, value_type: StackValueType) -> Result { + 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 { + context.pop_value(value_type1)?; + context.push_value(value_type2)?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_drop(context: &mut FunctionValidationContext) -> Result { + context.pop_any_value().map(|_| ())?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_select(context: &mut FunctionValidationContext) -> Result { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) + } + + fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result { + context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) + } + + fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result { + context.pop_value(ValueType::I32.into())?; + context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction) + } + + fn validate_else(context: &mut FunctionValidationContext) -> Result { + 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 { + { + 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 { + 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 { + 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, default: u32) -> Result { + 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 { + 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 { + 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 { + 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 { + context.require_memory(DEFAULT_MEMORY_INDEX)?; + context.push_value(ValueType::I32.into())?; + Ok(InstructionOutcome::ValidateNextInstruction) + } + + fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result { + 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 { + 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 { + 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 { + 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 { + self.return_type.ok_or(Error("Trying to return from expression".into())) + } + + pub fn require_local(&self, idx: u32) -> Result { + 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, + ) -> Result { + 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, 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, 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 { + 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 for StackValueType { + fn from(value_type: ValueType) -> Self { + StackValueType::Specific(value_type) + } +} + +impl PartialEq 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 for StackValueType { + fn eq(&self, other: &ValueType) -> bool { + if self.is_any() || self.is_any_unlimited() { + true + } else { + self.value_type() == *other + } + } +} + +impl PartialEq for ValueType { + fn eq(&self, other: &StackValueType) -> bool { + other == self + } +} diff --git a/src/validation/mod.rs b/src/validation/mod.rs index a22d606..5cc680d 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -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 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(); + // } } diff --git a/src/validation/module.rs b/src/validation/module.rs new file mode 100644 index 0000000..abe65aa --- /dev/null +++ b/src/validation/module.rs @@ -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!(); + } +}