diff --git a/spec/src/fixtures.rs b/spec/src/fixtures.rs index 8481ed0..f73166b 100644 --- a/spec/src/fixtures.rs +++ b/spec/src/fixtures.rs @@ -16,3 +16,7 @@ run_test!("f64_bitwise", wasm_f64_bitwise); run_test!("forward", wasm_forward); run_test!("i32", wasm_i32); run_test!("i64", wasm_i64); +run_test!("tee_local", wasm_tee_local); +run_test!("traps", wasm_traps); +run_test!("unreachable", wasm_unreachable); +run_test!("unwind", wasm_unwind); diff --git a/spec/src/run.rs b/spec/src/run.rs index ad03cc0..59d45c5 100644 --- a/spec/src/run.rs +++ b/spec/src/run.rs @@ -38,10 +38,13 @@ fn setup_program(base_dir: &str, test_module_path: &str) -> (ProgramInstance, Ar (program, module_instance) } -fn try_load(base_dir: &str, module_path: &str) -> Result { +fn try_load(base_dir: &str, module_path: &str) -> Result<(), parity_wasm::interpreter::Error> { let mut wasm_path = PathBuf::from(base_dir.clone()); wasm_path.push(module_path); - parity_wasm::deserialize_file(&wasm_path) + let module = parity_wasm::deserialize_file(&wasm_path).map_err(|e| parity_wasm::interpreter::Error::Program(format!("{:?}", e)))?; + + let program = ProgramInstance::new().expect("Failed creating program"); + program.add_module("try_load", module).map(|_| ()) } fn runtime_value(test_val: &test::RuntimeValue) -> parity_wasm::RuntimeValue { diff --git a/spec/testsuite b/spec/testsuite new file mode 160000 index 0000000..434fe2f --- /dev/null +++ b/spec/testsuite @@ -0,0 +1 @@ +Subproject commit 434fe2f1cb4c2a545e8b6165b695435e1adfeb2a diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index d46ed7d..3a76572 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -81,7 +81,7 @@ pub struct EnvModuleInstance { impl EnvModuleInstance { pub fn new(params: EnvParams, module: Module) -> Result { - let instance = ModuleInstance::new(Weak::default(), module)?; + let instance = ModuleInstance::new_with_validation_flag(Weak::default(), module, false)?; Ok(EnvModuleInstance { _params: params, diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index f0ffac0..049d7e7 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -5,6 +5,8 @@ pub enum Error { /// Program-level error. Program(String), + /// Validation error. + Validation(String), /// Initialization error. Initialization(String), /// Function-level error. @@ -37,6 +39,7 @@ impl Into for Error { fn into(self) -> String { match self { Error::Program(s) => s, + Error::Validation(s) => s, Error::Initialization(s) => s, Error::Function(s) => s, Error::Table(s) => s, @@ -63,6 +66,7 @@ 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 614a210..7df2be6 100644 --- a/src/interpreter/module.rs +++ b/src/interpreter/module.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::iter::repeat; use std::sync::{Arc, Weak}; -use elements::{Module, InitExpr, Opcode, Type, FunctionType, FuncBody, Internal}; +use elements::{Module, InitExpr, Opcode, Type, FunctionType, FuncBody, Internal, BlockType}; use interpreter::Error; use interpreter::imports::ModuleImports; use interpreter::memory::MemoryInstance; @@ -9,9 +9,15 @@ use interpreter::program::ProgramInstanceEssence; use interpreter::runner::{Interpreter, FunctionContext}; use interpreter::stack::StackWithLimit; use interpreter::table::TableInstance; +use interpreter::validator::{Validator, FunctionValidationContext}; use interpreter::value::{RuntimeValue, TryInto}; use interpreter::variable::{VariableInstance, VariableType}; +/// Maximum number of entries in value stack. +const DEFAULT_VALUE_STACK_LIMIT: usize = 16384; +/// Maximum number of entries in frame stack. +const DEFAULT_FRAME_STACK_LIMIT: usize = 1024; + #[derive(Default, Clone)] /// Execution context. pub struct ExecutionParams<'a> { @@ -112,8 +118,11 @@ impl<'a> From> for ExecutionParams<'a> { impl ModuleInstance { /// Instantiate given module within program context. pub fn new(program: Weak, module: Module) -> Result { - // TODO: missing validation step - + Self::new_with_validation_flag(program, module, true) + } + + /// Instantiate given module within program context. + pub fn new_with_validation_flag(program: Weak, module: Module, requires_validation: bool) -> Result { // load entries from import section let imports = ModuleImports::new(program, module.import_section()); @@ -155,12 +164,35 @@ impl ModuleInstance { tables: tables, globals: globals, }; - module.complete_initialization()?; + module.complete_initialization(requires_validation)?; Ok(module) } /// Complete module initialization. - fn complete_initialization(&mut self) -> Result<(), Error> { + fn complete_initialization(&mut self, requires_validation: bool) -> Result<(), Error> { + if requires_validation { + // TODO: missing module validation step + + // validate every function body + match (self.module.function_section(), self.module.code_section(), self.module.type_section()) { + (Some(function_section), Some(code_section), Some(type_section)) => { + for (index, function) in function_section.entries().iter().enumerate() { + let function_type = match type_section.types().get(function.type_ref() as usize) { + Some(&Type::Function(ref function_type)) => function_type.clone(), + _ => return Err(Error::Validation(format!("Missing type for function {}", index))), + }; + 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.module, &self.imports, &locals, DEFAULT_VALUE_STACK_LIMIT, DEFAULT_FRAME_STACK_LIMIT, &function_type); + let block_type = function_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult); + Validator::validate_block(&mut context, block_type, function_body.code().elements(), Opcode::End)?; + } + }, + _ => (), + } + } + // use data section to initialize linear memory regions if let Some(data_section) = self.module.data_section() { for (data_segment_index, data_segment) in data_section.entries().iter().enumerate() { @@ -365,8 +397,8 @@ impl<'a> CallerContext<'a> { /// Top most args pub fn topmost(args: &'a mut StackWithLimit, externals: &'a HashMap>) -> Self { CallerContext { - value_stack_limit: 1024, - frame_stack_limit: 1024, + value_stack_limit: DEFAULT_VALUE_STACK_LIMIT, + frame_stack_limit: DEFAULT_FRAME_STACK_LIMIT, value_stack: args, externals: externals, } diff --git a/src/interpreter/program.rs b/src/interpreter/program.rs index 33c179d..0bcbe8c 100644 --- a/src/interpreter/program.rs +++ b/src/interpreter/program.rs @@ -31,7 +31,7 @@ impl ProgramInstance { }) } - /// Instantiate module. + /// Instantiate module with validation. pub fn add_module(&self, name: &str, module: Module) -> Result, Error> { let module_instance = Arc::new(ModuleInstance::new(Arc::downgrade(&self.essence), module)?); // replace existing module with the same name with new one @@ -39,6 +39,14 @@ impl ProgramInstance { Ok(module_instance) } + /// Instantiate module without validation. + pub fn add_module_without_validation(&self, name: &str, module: Module) -> Result, Error> { + let module_instance = Arc::new(ModuleInstance::new_with_validation_flag(Arc::downgrade(&self.essence), module, false)?); + // replace existing module with the same name with new one + self.essence.modules.write().insert(name.into(), module_instance.clone()); + Ok(module_instance) + } + /// Get one of the modules by name pub fn module(&self, name: &str) -> Option> { self.essence.module(name) diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index d3c1150..2a2b1de 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -14,9 +14,12 @@ use interpreter::value::{ }; use interpreter::variable::VariableInstance; -const DEFAULT_MEMORY_INDEX: u32 = 0; -const DEFAULT_TABLE_INDEX: u32 = 0; +/// Index of default linear memory. +pub const DEFAULT_MEMORY_INDEX: u32 = 0; +/// Index of default table. +pub const DEFAULT_TABLE_INDEX: u32 = 0; +/// Function interpreter. pub struct Interpreter; /// Function execution context. @@ -101,30 +104,30 @@ impl Interpreter { &Opcode::GetGlobal(index) => Interpreter::run_get_global(context, index), &Opcode::SetGlobal(index) => Interpreter::run_set_global(context, index), - &Opcode::I32Load(offset, align) => Interpreter::run_load::(context, offset, align), - &Opcode::I64Load(offset, align) => Interpreter::run_load::(context, offset, align), - &Opcode::F32Load(offset, align) => Interpreter::run_load::(context, offset, align), - &Opcode::F64Load(offset, align) => Interpreter::run_load::(context, offset, align), - &Opcode::I32Load8S(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I32Load8U(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I32Load16S(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I32Load16U(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load8S(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load8U(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load16S(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load16U(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load32S(offset, align) => Interpreter::run_load_extend::(context, offset, align), - &Opcode::I64Load32U(offset, align) => Interpreter::run_load_extend::(context, offset, align), + &Opcode::I32Load(align, offset) => Interpreter::run_load::(context, align, offset), + &Opcode::I64Load(align, offset) => Interpreter::run_load::(context, align, offset), + &Opcode::F32Load(align, offset) => Interpreter::run_load::(context, align, offset), + &Opcode::F64Load(align, offset) => Interpreter::run_load::(context, align, offset), + &Opcode::I32Load8S(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I32Load8U(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I32Load16S(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I32Load16U(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load8S(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load8U(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load16S(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load16U(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load32S(align, offset) => Interpreter::run_load_extend::(context, align, offset), + &Opcode::I64Load32U(align, offset) => Interpreter::run_load_extend::(context, align, offset), - &Opcode::I32Store(offset, align) => Interpreter::run_store::(context, offset, align), - &Opcode::I64Store(offset, align) => Interpreter::run_store::(context, offset, align), - &Opcode::F32Store(offset, align) => Interpreter::run_store::(context, offset, align), - &Opcode::F64Store(offset, align) => Interpreter::run_store::(context, offset, align), - &Opcode::I32Store8(offset, align) => Interpreter::run_store_wrap::(context, offset, align), - &Opcode::I32Store16(offset, align) => Interpreter::run_store_wrap::(context, offset, align), - &Opcode::I64Store8(offset, align) => Interpreter::run_store_wrap::(context, offset, align), - &Opcode::I64Store16(offset, align) => Interpreter::run_store_wrap::(context, offset, align), - &Opcode::I64Store32(offset, align) => Interpreter::run_store_wrap::(context, offset, align), + &Opcode::I32Store(align, offset) => Interpreter::run_store::(context, align, offset), + &Opcode::I64Store(align, offset) => Interpreter::run_store::(context, align, offset), + &Opcode::F32Store(align, offset) => Interpreter::run_store::(context, align, offset), + &Opcode::F64Store(align, offset) => Interpreter::run_store::(context, align, offset), + &Opcode::I32Store8(align, offset) => Interpreter::run_store_wrap::(context, align, offset), + &Opcode::I32Store16(align, offset) => Interpreter::run_store_wrap::(context, align, offset), + &Opcode::I64Store8(align, offset) => Interpreter::run_store_wrap::(context, align, offset), + &Opcode::I64Store16(align, offset) => Interpreter::run_store_wrap::(context, align, offset), + &Opcode::I64Store32(align, offset) => Interpreter::run_store_wrap::(context, align, offset), &Opcode::CurrentMemory(_) => Interpreter::run_current_memory(context), &Opcode::GrowMemory(_) => Interpreter::run_grow_memory(context), diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index 9436cce..b075cae 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -44,6 +44,14 @@ impl StackWithLimit where T: Clone { .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))); diff --git a/src/interpreter/tests/basics.rs b/src/interpreter/tests/basics.rs index fee1201..a4e5294 100644 --- a/src/interpreter/tests/basics.rs +++ b/src/interpreter/tests/basics.rs @@ -117,7 +117,7 @@ fn global_get_set() { .build(); let program = ProgramInstance::new().unwrap(); - let module = program.add_module("main", module).unwrap(); + let module = program.add_module_without_validation("main", module).unwrap(); // validation is failing (accessing immutable global) assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(50)); assert_eq!(module.execute_index(1, vec![].into()).unwrap_err(), Error::Variable("trying to update immutable variable".into())); assert_eq!(module.execute_index(2, vec![].into()).unwrap_err(), Error::Variable("trying to update variable of type I32 with value of type Some(I64)".into())); diff --git a/src/interpreter/validator.rs b/src/interpreter/validator.rs new file mode 100644 index 0000000..4a0b96f --- /dev/null +++ b/src/interpreter/validator.rs @@ -0,0 +1,607 @@ +use elements::{Module, Opcode, BlockType, FunctionType, ValueType, External, Type}; +use interpreter::Error; +use interpreter::runner::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; +use interpreter::imports::ModuleImports; +use interpreter::module::ItemIndex; +use interpreter::stack::StackWithLimit; +use interpreter::variable::VariableType; + +/// Function validation context. +pub struct FunctionValidationContext<'a> { + /// Wasm module. + module: &'a Module, + /// Module imports. + imports: &'a ModuleImports, + /// Local variables. + locals: &'a [ValueType], + /// Value stack. + value_stack: StackWithLimit, + /// Frame stack. + frame_stack: StackWithLimit<(BlockType, usize)>, + /// Function return type. None if validating expression. + return_type: Option, +} + +/// Function validator. +pub struct Validator; + +/// Instruction outcome. +#[derive(Debug, Clone)] +pub enum InstructionOutcome { + /// Continue with next instruction. + RunNextInstruction, + /// Unreachable instruction reached. + Unreachable, +} + +impl Validator { + pub fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode], end_instr: Opcode) -> Result { + if body.is_empty() || body[body.len() - 1] != end_instr { + return Err(Error::Validation("Every block must end with end/else instruction".into())); + } + + context.push_label(block_type)?; + for opcode in body { + match Validator::validate_instruction(context, opcode)? { + InstructionOutcome::RunNextInstruction => (), + InstructionOutcome::Unreachable => return Ok(InstructionOutcome::Unreachable), + } + } + let return_type = context.pop_label()?; + + match block_type { + BlockType::NoResult => Ok(InstructionOutcome::RunNextInstruction), + BlockType::Value(value_type) if Some(value_type) != return_type => + Err(Error::Validation(format!("Expected block to return {:?} while it has returned {:?}", value_type, return_type))), + BlockType::Value(value_type) => context.push_value(value_type).map(|_| InstructionOutcome::RunNextInstruction), + } + } + + pub fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result { + match opcode { + &Opcode::Unreachable => Ok(InstructionOutcome::Unreachable), + &Opcode::Nop => Ok(InstructionOutcome::RunNextInstruction), + &Opcode::Block(block_type, ref ops) => Validator::validate_block(context, block_type, ops.elements(), Opcode::End), + &Opcode::Loop(block_type, ref ops) => Validator::validate_loop(context, block_type, ops.elements()), + &Opcode::If(block_type, ref ops) => Validator::validate_if(context, block_type, ops.elements()), + &Opcode::Else => Ok(InstructionOutcome::RunNextInstruction), + &Opcode::End => Ok(InstructionOutcome::RunNextInstruction), + &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), + &Opcode::I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64), + &Opcode::F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32), + &Opcode::F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64), + &Opcode::I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32), + &Opcode::I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32), + &Opcode::I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32), + &Opcode::I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32), + &Opcode::I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64), + &Opcode::I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64), + &Opcode::I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64), + &Opcode::I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64), + &Opcode::I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64), + &Opcode::I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64), + + &Opcode::I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32), + &Opcode::I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64), + &Opcode::F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32), + &Opcode::F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64), + &Opcode::I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32), + &Opcode::I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32), + &Opcode::I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64), + &Opcode::I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64), + &Opcode::I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64), + + &Opcode::CurrentMemory(_) => Validator::validate_current_memory(context), + &Opcode::GrowMemory(_) => Validator::validate_grow_memory(context), + + &Opcode::I32Const(_) => Validator::validate_const(context, ValueType::I32), + &Opcode::I64Const(_) => Validator::validate_const(context, ValueType::I64), + &Opcode::F32Const(_) => Validator::validate_const(context, ValueType::F32), + &Opcode::F64Const(_) => Validator::validate_const(context, ValueType::F64), + + &Opcode::I32Eqz => Validator::validate_testop(context, ValueType::I32), + &Opcode::I32Eq => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32Ne => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32LtS => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32LtU => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32GtS => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32GtU => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32LeS => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32LeU => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32GeS => Validator::validate_relop(context, ValueType::I32), + &Opcode::I32GeU => Validator::validate_relop(context, ValueType::I32), + + &Opcode::I64Eqz => Validator::validate_testop(context, ValueType::I64), + &Opcode::I64Eq => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64Ne => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64LtS => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64LtU => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64GtS => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64GtU => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64LeS => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64LeU => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64GeS => Validator::validate_relop(context, ValueType::I64), + &Opcode::I64GeU => Validator::validate_relop(context, ValueType::I64), + + &Opcode::F32Eq => Validator::validate_relop(context, ValueType::F32), + &Opcode::F32Ne => Validator::validate_relop(context, ValueType::F32), + &Opcode::F32Lt => Validator::validate_relop(context, ValueType::F32), + &Opcode::F32Gt => Validator::validate_relop(context, ValueType::F32), + &Opcode::F32Le => Validator::validate_relop(context, ValueType::F32), + &Opcode::F32Ge => Validator::validate_relop(context, ValueType::F32), + + &Opcode::F64Eq => Validator::validate_relop(context, ValueType::F64), + &Opcode::F64Ne => Validator::validate_relop(context, ValueType::F64), + &Opcode::F64Lt => Validator::validate_relop(context, ValueType::F64), + &Opcode::F64Gt => Validator::validate_relop(context, ValueType::F64), + &Opcode::F64Le => Validator::validate_relop(context, ValueType::F64), + &Opcode::F64Ge => Validator::validate_relop(context, ValueType::F64), + + &Opcode::I32Clz => Validator::validate_unop(context, ValueType::I32), + &Opcode::I32Ctz => Validator::validate_unop(context, ValueType::I32), + &Opcode::I32Popcnt => Validator::validate_unop(context, ValueType::I32), + &Opcode::I32Add => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Sub => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Mul => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32DivS => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32DivU => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32RemS => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32RemU => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32And => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Or => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Xor => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Shl => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32ShrS => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32ShrU => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Rotl => Validator::validate_binop(context, ValueType::I32), + &Opcode::I32Rotr => Validator::validate_binop(context, ValueType::I32), + + &Opcode::I64Clz => Validator::validate_unop(context, ValueType::I64), + &Opcode::I64Ctz => Validator::validate_unop(context, ValueType::I64), + &Opcode::I64Popcnt => Validator::validate_unop(context, ValueType::I64), + &Opcode::I64Add => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Sub => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Mul => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64DivS => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64DivU => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64RemS => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64RemU => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64And => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Or => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Xor => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Shl => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64ShrS => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64ShrU => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Rotl => Validator::validate_binop(context, ValueType::I64), + &Opcode::I64Rotr => Validator::validate_binop(context, ValueType::I64), + + &Opcode::F32Abs => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Neg => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Ceil => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Floor => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Trunc => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Nearest => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Sqrt => Validator::validate_unop(context, ValueType::F32), + &Opcode::F32Add => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Sub => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Mul => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Div => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Min => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Max => Validator::validate_binop(context, ValueType::F32), + &Opcode::F32Copysign => Validator::validate_binop(context, ValueType::F32), + + &Opcode::F64Abs => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Neg => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Ceil => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Floor => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Trunc => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Nearest => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Sqrt => Validator::validate_unop(context, ValueType::F64), + &Opcode::F64Add => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Sub => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Mul => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Div => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Min => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Max => Validator::validate_binop(context, ValueType::F64), + &Opcode::F64Copysign => Validator::validate_binop(context, ValueType::F64), + + &Opcode::I32WarpI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32), + &Opcode::I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), + &Opcode::I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), + &Opcode::I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32), + &Opcode::I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32), + &Opcode::I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64), + &Opcode::I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64), + &Opcode::I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64), + &Opcode::I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64), + &Opcode::I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), + &Opcode::I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), + &Opcode::F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), + &Opcode::F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), + &Opcode::F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32), + &Opcode::F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32), + &Opcode::F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32), + &Opcode::F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64), + &Opcode::F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64), + &Opcode::F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), + &Opcode::F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), + &Opcode::F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64), + + &Opcode::I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32), + &Opcode::I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64), + &Opcode::F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32), + &Opcode::F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64), + } + } + + fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { + context.push_value(value_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { + context.pop_value(value_type)?; + context.push_value(value_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { + context.pop_value(value_type)?; + context.pop_value(value_type)?; + context.push_value(value_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { + context.pop_value(value_type)?; + context.push_value(ValueType::I32)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result { + context.pop_value(value_type)?; + context.pop_value(value_type)?; + context.push_value(ValueType::I32)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result { + context.pop_value(value_type1)?; + context.push_value(value_type2)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_drop(context: &mut FunctionValidationContext) -> Result { + context.pop_any_value().map(|_| ())?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_select(context: &mut FunctionValidationContext) -> Result { + context.pop_value(ValueType::I32)?; + let select_type = context.pop_any_value()?; + context.pop_value(select_type)?; + context.push_value(select_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result { + let local_type = context.require_local(index)?; + context.push_value(local_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + 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::Validation(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type))); + } + Ok(InstructionOutcome::RunNextInstruction) + } + + 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::Validation(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type))); + } + Ok(InstructionOutcome::RunNextInstruction) + } + + 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::RunNextInstruction) + } + + 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::Validation(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type))); + } + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result { + if align > max_align { + return Err(Error::Validation(format!("Too large memory alignment {} (expected at most {})", align, max_align))); + } + + context.require_memory(DEFAULT_MEMORY_INDEX)?; + context.push_value(value_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result { + if align > max_align { + return Err(Error::Validation(format!("Too large memory alignment {} (expected at most {})", align, max_align))); + } + + context.require_memory(DEFAULT_MEMORY_INDEX)?; + context.pop_value(value_type)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode]) -> Result { + Validator::validate_block(context, block_type, body, Opcode::End) + } + + fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode]) -> Result { + let body_len = body.len(); + let separator_index = body.iter() + .position(|op| *op == Opcode::Else) + .unwrap_or(body_len - 1); + if separator_index != body_len - 1 { + Validator::validate_block(context, block_type, &body[..separator_index + 1], Opcode::Else)?; + Validator::validate_block(context, block_type, &body[separator_index+1..], Opcode::End) + } else { + Validator::validate_block(context, block_type, body, Opcode::End) + } + } + + fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result { + context.require_label(idx)?; + Ok(InstructionOutcome::Unreachable) + } + + fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result { + context.pop_value(ValueType::I32)?; + context.require_label(idx)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_br_table(context: &mut FunctionValidationContext, table: &Vec, default: u32) -> Result { + let default_block_type = context.require_label(default)?; + for label in table { + let label_block_type = context.require_label(*label)?; + if default_block_type != label_block_type { + return Err(Error::Validation(format!("Default label in br_table points to block of type {:?}, while other points to {:?}", default_block_type, label_block_type))); + } + } + + Ok(InstructionOutcome::Unreachable) + } + + fn validate_return(context: &mut FunctionValidationContext) -> Result { + if let BlockType::Value(value_type) = context.return_type()? { + context.tee_value(value_type)?; + } + 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)?; + } + if let BlockType::Value(value_type) = return_type { + context.push_value(value_type)?; + } + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result { + context.require_table(DEFAULT_TABLE_INDEX, VariableType::AnyFunc)?; + let (argument_types, return_type) = context.require_function_type(idx)?; + for argument_type in argument_types.iter().rev() { + context.pop_value(*argument_type)?; + } + if let BlockType::Value(value_type) = return_type { + context.push_value(value_type)?; + } + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_current_memory(context: &mut FunctionValidationContext) -> Result { + context.require_memory(DEFAULT_MEMORY_INDEX)?; + context.push_value(ValueType::I32)?; + Ok(InstructionOutcome::RunNextInstruction) + } + + fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result { + context.require_memory(DEFAULT_MEMORY_INDEX)?; + context.pop_value(ValueType::I32)?; + context.push_value(ValueType::I32)?; + Ok(InstructionOutcome::RunNextInstruction) + } +} + +impl<'a> FunctionValidationContext<'a> { + pub fn new(module: &'a Module, imports: &'a ModuleImports, locals: &'a [ValueType], value_stack_limit: usize, frame_stack_limit: usize, function: &FunctionType) -> Self { + FunctionValidationContext { + module: module, + imports: imports, + locals: locals, + value_stack: StackWithLimit::with_limit(value_stack_limit), + frame_stack: StackWithLimit::with_limit(frame_stack_limit), + return_type: Some(function.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult)), + } + } + + pub fn push_value(&mut self, value_type: ValueType) -> Result<(), Error> { + self.value_stack.push(value_type) + } + + pub fn pop_value(&mut self, value_type: ValueType) -> Result<(), Error> { + let stack_value_type = self.value_stack.pop()?; + if stack_value_type == value_type { + return Ok(()); + } + + Err(Error::Validation(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))) + } + + pub fn tee_value(&mut self, value_type: ValueType) -> Result<(), Error> { + let stack_value_type = self.value_stack.top()?; + if *stack_value_type == value_type { + return Ok(()); + } + + Err(Error::Validation(format!("Expected value of type {:?} on top of stack. Got {:?}", value_type, stack_value_type))) + } + + pub fn pop_any_value(&mut self) -> Result { + self.value_stack.pop() + } + + pub fn tee_any_value(&mut self) -> Result { + self.value_stack.top().map(Clone::clone) + } + + pub fn push_label(&mut self, block_type: BlockType) -> Result<(), Error> { + self.frame_stack.push((block_type, self.value_stack.len())) + } + + pub fn pop_label(&mut self) -> Result, Error> { + let (_, value_stack_len) = self.frame_stack.pop()?; + let return_type = if self.value_stack.len() > value_stack_len { + Some(self.value_stack.pop()?) + } else { + None + }; + + self.value_stack.resize(value_stack_len, ValueType::I32); + Ok(return_type) + } + + pub fn require_label(&self, idx: u32) -> Result { + self.frame_stack.get(idx as usize).map(|&(block_type, _)| block_type) + } + + pub fn return_type(&self) -> Result { + self.return_type.ok_or(Error::Validation("Trying to return from expression".into())) + } + + pub fn require_local(&self, idx: u32) -> Result { + self.locals.get(idx as usize) + .cloned() + .ok_or(Error::Validation(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 { + match self.imports.parse_global_index(ItemIndex::IndexSpace(idx)) { + ItemIndex::IndexSpace(_) => unreachable!("parse_global_index is intended to resolve this"), + ItemIndex::Internal(internal_idx) => self.module + .global_section().ok_or(Error::Validation(format!("Trying to access internal global {} in module without global section", internal_idx))) + .and_then(|s| s.entries().get(internal_idx as usize).ok_or(Error::Validation(format!("Trying to access internal global {} in module with {} globals", internal_idx, s.entries().len())))) + .and_then(|g| match mutability { + Some(true) if !g.global_type().is_mutable() => Err(Error::Validation(format!("Expected internal global {} to be mutable", internal_idx))), + Some(false) if g.global_type().is_mutable() => Err(Error::Validation(format!("Expected internal global {} to be immutable", internal_idx))), + _ => Ok(g), + }) + .map(|g| g.global_type().content_type()), + ItemIndex::External(external_idx) => self.module + .import_section().ok_or(Error::Validation(format!("Trying to access external global {} in module without import section", external_idx))) + .and_then(|s| s.entries().get(external_idx as usize).ok_or(Error::Validation(format!("Trying to access external global with index {} in module with {}-entries import section", external_idx, s.entries().len())))) + .and_then(|e| match e.external() { + &External::Global(ref g) => { + match mutability { + Some(true) if !g.is_mutable() => Err(Error::Validation(format!("Expected external global {} to be mutable", external_idx))), + Some(false) if g.is_mutable() => Err(Error::Validation(format!("Expected external global {} to be immutable", external_idx))), + _ => Ok(g.content_type()), + } + }, + _ => Err(Error::Validation(format!("Import entry {} expected to import global", external_idx))) + }), + } + } + + pub fn require_memory(&self, idx: u32) -> Result<(), Error> { + match self.imports.parse_memory_index(ItemIndex::IndexSpace(idx)) { + ItemIndex::IndexSpace(_) => unreachable!("parse_memory_index is intended to resolve this"), + ItemIndex::Internal(internal_idx) => self.module + .memory_section().ok_or(Error::Validation(format!("Trying to access internal memory {} in module without memory section", internal_idx))) + .and_then(|s| s.entries().get(internal_idx as usize).ok_or(Error::Validation(format!("Trying to access internal memory {} in module with {} memory regions", internal_idx, s.entries().len())))) + .map(|_| ()), + ItemIndex::External(external_idx) => self.module + .import_section().ok_or(Error::Validation(format!("Trying to access external memory {} in module without import section", external_idx))) + .and_then(|s| s.entries().get(external_idx as usize).ok_or(Error::Validation(format!("Trying to access external memory with index {} in module with {}-entries import section", external_idx, s.entries().len())))) + .and_then(|e| match e.external() { + &External::Memory(_) => Ok(()), + _ => Err(Error::Validation(format!("Import entry {} expected to import memory", external_idx))) + }), + } + } + + pub fn require_table(&self, idx: u32, _variable_type: VariableType) -> Result<(), Error> { + match self.imports.parse_table_index(ItemIndex::IndexSpace(idx)) { + ItemIndex::IndexSpace(_) => unreachable!("parse_table_index is intended to resolve this"), + ItemIndex::Internal(internal_idx) => self.module + .table_section().ok_or(Error::Validation(format!("Trying to access internal table {} in module without table section", internal_idx))) + .and_then(|s| s.entries().get(internal_idx as usize).ok_or(Error::Validation(format!("Trying to access internal table {} in module with {} tables", internal_idx, s.entries().len())))) + .and_then(|_| Ok(())), // TODO: check variable type + ItemIndex::External(external_idx) => self.module + .import_section().ok_or(Error::Validation(format!("Trying to access external table {} in module without import section", external_idx))) + .and_then(|s| s.entries().get(external_idx as usize).ok_or(Error::Validation(format!("Trying to access external table with index {} in module with {}-entries import section", external_idx, s.entries().len())))) + .and_then(|e| match e.external() { + &External::Table(_) => Ok(()), // TODO: check variable type + _ => Err(Error::Validation(format!("Import entry {} expected to import table", external_idx))) + }), + } + } + + pub fn require_function(&self, idx: u32) -> Result<(Vec, BlockType), Error> { + match self.imports.parse_function_index(ItemIndex::IndexSpace(idx)) { + ItemIndex::IndexSpace(_) => unreachable!("parse_function_index is intended to resolve this"), + ItemIndex::Internal(internal_idx) => self.module + .function_section().ok_or(Error::Validation(format!("Trying to access internal function {} in module without function section", internal_idx))) + .and_then(|s| s.entries().get(internal_idx as usize).map(|f| f.type_ref()).ok_or(Error::Validation(format!("Trying to access internal function {} in module with {} functions", internal_idx, s.entries().len())))) + .and_then(|tidx| self.require_function_type(tidx)), + ItemIndex::External(external_idx) => self.module + .import_section().ok_or(Error::Validation(format!("Trying to access external function {} in module without import section", external_idx))) + .and_then(|s| s.entries().get(external_idx as usize).ok_or(Error::Validation(format!("Trying to access external function with index {} in module with {}-entries import section", external_idx, s.entries().len())))) + .and_then(|e| match e.external() { + &External::Function(tidx) => Ok(tidx), + _ => Err(Error::Validation(format!("Import entry {} expected to import function", external_idx))) + }) + .and_then(|tidx| self.require_function_type(tidx)), + } + } + + pub fn require_function_type(&self, idx: u32) -> Result<(Vec, BlockType), Error> { + self.module + .type_section().ok_or(Error::Validation(format!("Trying to access internal function {} in module without type section", idx))) + .and_then(|ts| match ts.types().get(idx as usize) { + Some(&Type::Function(ref function_type)) => Ok((function_type.params().to_vec(), function_type.return_type().map(BlockType::Value).unwrap_or(BlockType::NoResult))), + _ => Err(Error::Validation(format!("Trying to access internal function {} with wrong type", idx))), + }) + } +}