diff --git a/spec/src/fixtures.rs b/spec/src/fixtures.rs index 74c527f..b6a3e70 100644 --- a/spec/src/fixtures.rs +++ b/spec/src/fixtures.rs @@ -24,6 +24,27 @@ run_test!("f64_bitwise", wasm_f64_bitwise); run_test!("forward", wasm_forward); run_test!("i32", wasm_i32); run_test!("i64", wasm_i64); +run_test!("memory", wasm_memory); +// TODO: fix comparison??? run_test!("names", wasm_names); +run_test!("nop", wasm_nop); +run_test!("of_string-overflow-hex-u32.fail", wasm_of_string_overflow_hex_u32_fail, fail); +run_test!("of_string-overflow-hex-u64.fail", wasm_of_string_overflow_hex_u64_fail, fail); +run_test!("of_string-overflow-s32.fail", wasm_of_string_overflow_s32_fail, fail); +run_test!("of_string-overflow-s64.fail", wasm_of_string_overflow_s64_fail, fail); +run_test!("of_string-overflow-u32.fail", wasm_of_string_overflow_u32_fail, fail); +run_test!("of_string-overflow-u64.fail", wasm_of_string_overflow_u64_fail, fail); +run_test!("resizing", wasm_resizing); +run_test!("return", wasm_return); +run_test!("select", wasm_select); +run_test!("set_local", wasm_set_local); +run_test!("skip-stack-guard-page", wasm_skip_stack_guard_page); +run_test!("stack", wasm_stack); +run_test!("start", wasm_start); +run_test!("store_retval", wasm_store_retval); +run_test!("store-align-0.fail", wasm_store_align_0_fail, fail); +run_test!("store-align-big.fail", wasm_store_align_big_fail, fail); +run_test!("store-align-odd.fail", wasm_store_align_odd_fail, fail); +run_test!("switch", wasm_switch); run_test!("tee_local", wasm_tee_local); run_test!("traps", wasm_traps); run_test!("typecheck", wasm_typecheck); diff --git a/spec/src/run.rs b/spec/src/run.rs index 975bf23..8693111 100644 --- a/spec/src/run.rs +++ b/spec/src/run.rs @@ -18,10 +18,12 @@ use parity_wasm::interpreter::{ fn spec_test_module() -> elements::Module { builder::module() .function() - .signature().with_param(elements::ValueType::I32).build() + .signature().build() .body().build() .build() + .global().value_type().i32().init_expr(elements::Opcode::I32Const(0)).build() .export().field("print").internal().func(0).build() + .export().field("global").internal().global(0).build() .build() } @@ -196,6 +198,13 @@ pub fn spec(name: &str) { } } }, + &test::Command::AssertExhaustion { line, ref action, .. } => { + let result = run_action(&*module.as_ref().unwrap(), action); + match result { + Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), + Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), + } + }, &test::Command::AssertTrap { line, ref action, .. } => { let result = run_action(&*module.as_ref().unwrap(), action); match result { @@ -209,6 +218,7 @@ pub fn spec(name: &str) { }, &test::Command::AssertInvalid { line, ref filename, .. } | &test::Command::AssertMalformed { line, ref filename, .. } + | &test::Command::AssertUnlinkable { line, ref filename, .. } => { let module_load = try_load(&outdir, filename); match module_load { @@ -220,6 +230,12 @@ pub fn spec(name: &str) { } } }, + &test::Command::AssertUninstantiable { line, ref filename, .. } => { + match try_load(&outdir, &filename) { + Ok(_) => panic!("Expected error running start function at line {}", line), + Err(e) => println!("assert_uninstantiable - success ({:?})", e), + } + }, &test::Command::Action { line, ref action } => { match run_action(&*module.as_ref().unwrap(), action) { Ok(_) => { }, diff --git a/spec/src/test.rs b/spec/src/test.rs index cc7b505..866ad41 100644 --- a/spec/src/test.rs +++ b/spec/src/test.rs @@ -53,11 +53,28 @@ pub enum Command { filename: String, text: String, }, + #[serde(rename = "assert_uninstantiable")] + AssertUninstantiable { + line: u64, + filename: String, + text: String, + }, + #[serde(rename = "assert_exhaustion")] + AssertExhaustion { + line: u64, + action: Action, + }, + #[serde(rename = "assert_unlinkable")] + AssertUnlinkable { + line: u64, + filename: String, + text: String, + }, #[serde(rename = "action")] Action { line: u64, action: Action, - } + }, } #[derive(Deserialize, Debug)] diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index 3a76572..1aa9670 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -91,10 +91,6 @@ impl EnvModuleInstance { } impl ModuleInstanceInterface for EnvModuleInstance { - fn execute_main(&self, params: ExecutionParams) -> Result, Error> { - self.instance.execute_main(params) - } - fn execute_index(&self, index: u32, params: ExecutionParams) -> Result, Error> { self.instance.execute_index(index, params) } diff --git a/src/interpreter/env_native.rs b/src/interpreter/env_native.rs index 09e5227..7e92304 100644 --- a/src/interpreter/env_native.rs +++ b/src/interpreter/env_native.rs @@ -65,10 +65,6 @@ impl<'a> NativeModuleInstance<'a> { } impl<'a> ModuleInstanceInterface for NativeModuleInstance<'a> { - fn execute_main(&self, params: ExecutionParams) -> Result, Error> { - self.env.execute_main(params) - } - fn execute_index(&self, index: u32, params: ExecutionParams) -> Result, Error> { self.env.execute_index(index, params) } diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index fa0b247..7aadcae 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -7,6 +7,8 @@ use interpreter::module::check_limits; /// Linear memory page size. pub const LINEAR_MEMORY_PAGE_SIZE: u32 = 65536; +/// Maximal number of pages. +const LINEAR_MEMORY_MAX_PAGES: u32 = 65536; /// Linear memory instance. pub struct MemoryInstance { @@ -21,6 +23,12 @@ impl MemoryInstance { pub fn new(memory_type: &MemoryType) -> Result, Error> { check_limits(memory_type.limits())?; + if let Some(maximum_pages) = memory_type.limits().maximum() { + if maximum_pages > LINEAR_MEMORY_MAX_PAGES { + return Err(Error::Memory(format!("memory size must be at most 65536 pages"))); + } + } + let memory = MemoryInstance { buffer: RwLock::new(Vec::new()), // TODO: with_capacity maximum_size: memory_type.limits().maximum() @@ -64,7 +72,7 @@ impl MemoryInstance { }; let mut buffer = self.buffer.write(); - if buffer.len() <= end { + if buffer.len() < end { return Err(Error::Memory(format!("trying to update region [{}..{}] in memory [0..{}]", begin, end, buffer.len()))); } diff --git a/src/interpreter/module.rs b/src/interpreter/module.rs index 5273dd3..dd1ecb9 100644 --- a/src/interpreter/module.rs +++ b/src/interpreter/module.rs @@ -16,7 +16,7 @@ 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; +const DEFAULT_FRAME_STACK_LIMIT: usize = 128; // TODO: fix runner to support bigger depth #[derive(Default, Clone)] /// Execution context. @@ -29,8 +29,6 @@ pub struct ExecutionParams<'a> { /// Module instance API. pub trait ModuleInstanceInterface { - /// Execute start function of the module. - fn execute_main(&self, params: ExecutionParams) -> Result, Error>; /// Execute function with the given index. fn execute_index(&self, index: u32, params: ExecutionParams) -> Result, Error>; /// Execute function with the given export name. @@ -172,7 +170,13 @@ impl ModuleInstance { fn complete_initialization(&mut self, is_user_module: bool) -> Result<(), Error> { // validate start section if let Some(start_function) = self.module.start_section() { - self.require_function(ItemIndex::IndexSpace(start_function))?; + let func_type_index = self.require_function(ItemIndex::IndexSpace(start_function))?; + if is_user_module { // tests use non-empty main functions + let func_type = self.require_function_type(func_type_index)?; + if func_type.return_type() != None || func_type.params().len() != 0 { + return Err(Error::Validation("start function expected to have type [] -> []".into())); + } + } } // validate export section @@ -188,7 +192,7 @@ impl ModuleInstance { // this allows reexporting match export.internal() { &Internal::Function(function_index) => - self.require_function(ItemIndex::IndexSpace(function_index))?, + self.require_function(ItemIndex::IndexSpace(function_index)).map(|_| ())?, &Internal::Global(global_index) => self.global(ItemIndex::IndexSpace(global_index)) .and_then(|g| if g.is_mutable() { @@ -238,7 +242,7 @@ impl ModuleInstance { } // validate every function body in user modules - if is_user_module && function_section_len != 0 { + if is_user_module && 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 @@ -279,22 +283,30 @@ impl ModuleInstance { } } + // execute start function (if any) + if let Some(start_function) = self.module.start_section() { + self.execute_index(start_function, ExecutionParams::default())?; + } + Ok(()) } - fn require_function(&self, index: ItemIndex) -> Result<(), Error> { + fn require_function(&self, index: ItemIndex) -> Result { match self.imports.parse_function_index(index) { ItemIndex::IndexSpace(_) => unreachable!("parse_function_index resolves IndexSpace option"), ItemIndex::Internal(index) => self.module.function_section() - .ok_or(Error::Function(format!("initializing table element to value {}, without corresponding internal function", index))) + .ok_or(Error::Function(format!("missing internal function {}", index))) .and_then(|s| s.entries().get(index as usize) - .ok_or(Error::Function(format!("initializing table element to value {}, without corresponding internal function", index)))) - .map(|_| ()), + .ok_or(Error::Function(format!("missing internal function {}", index)))) + .map(|f| f.type_ref()), ItemIndex::External(index) => self.module.import_section() - .ok_or(Error::Function(format!("initializing table element to value {}, without corresponding external function", index))) + .ok_or(Error::Function(format!("missing external function {}", index))) .and_then(|s| s.entries().get(index as usize) - .ok_or(Error::Function(format!("initializing table element to value {}, without corresponding external function", index)))) - .map(|_| ()), + .ok_or(Error::Function(format!("missing external function {}", index)))) + .and_then(|import| match import.external() { + &External::Function(type_idx) => Ok(type_idx), + _ => Err(Error::Function(format!("external function {} is pointing to non-function import", index))), + }), } } @@ -309,11 +321,6 @@ impl ModuleInstance { } impl ModuleInstanceInterface for ModuleInstance { - fn execute_main(&self, params: ExecutionParams) -> Result, Error> { - let index = self.module.start_section().ok_or(Error::Program("module has no start section".into()))?; - self.execute_index(index, params) - } - fn execute_index(&self, index: u32, params: ExecutionParams) -> Result, Error> { let args_len = params.args.len(); let mut args = StackWithLimit::with_data(params.args, args_len); @@ -536,7 +543,12 @@ fn prepare_function_locals(function_type: &FunctionType, function_body: &FuncBod } fn get_initializer(expr: &InitExpr, module: &Module, imports: &ModuleImports) -> Result { - let first_opcode = expr.code().get(0).ok_or(Error::Initialization(format!("empty instantiation-time initializer")))?; + let first_opcode = match expr.code().len() { + 1 => &expr.code()[0], + 2 if expr.code().len() == 2 && expr.code()[1] == Opcode::End => &expr.code()[0], + _ => return Err(Error::Initialization(format!("expected 1-instruction len initializer. Got {:?}", expr.code()))), + }; + match first_opcode { &Opcode::GetGlobal(index) => { let index = match imports.parse_global_index(ItemIndex::IndexSpace(index)) { diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index b49bab9..6e2b8da 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -460,7 +460,7 @@ impl Interpreter { context.module() .memory(ItemIndex::IndexSpace(DEFAULT_MEMORY_INDEX)) .map(|m| m.size()) - .and_then(|s| context.value_stack_mut().push(RuntimeValue::I64(s as i64))) + .and_then(|s| context.value_stack_mut().push(RuntimeValue::I32(s as i32))) .map(|_| InstructionOutcome::RunNextInstruction) } diff --git a/src/interpreter/tests/wabt.rs b/src/interpreter/tests/wabt.rs index 28fcb93..e2d511f 100644 --- a/src/interpreter/tests/wabt.rs +++ b/src/interpreter/tests/wabt.rs @@ -516,7 +516,7 @@ fn return_void() { let module = module() .memory().build() - .function().main() + .function() .signature().param().i32().build() .body().with_opcodes(body).build() .build() @@ -524,11 +524,11 @@ fn return_void() { let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - module.execute_main(vec![RuntimeValue::I32(0)].into()).unwrap(); + module.execute_index(0, vec![RuntimeValue::I32(0)].into()).unwrap(); let memory = module.memory(ItemIndex::IndexSpace(0)).unwrap(); assert_eq!(memory.get(0, 4).unwrap(), vec![0, 0, 0, 0]); - module.execute_main(vec![RuntimeValue::I32(1)].into()).unwrap(); + module.execute_index(0, vec![RuntimeValue::I32(1)].into()).unwrap(); let memory = module.memory(ItemIndex::IndexSpace(0)).unwrap(); assert_eq!(memory.get(0, 4).unwrap(), vec![1, 0, 0, 0]); } @@ -564,7 +564,7 @@ fn call_1() { let module = module() .memory().build() - .function().main() + .function() .signature().return_type().i32().build() .body().with_opcodes(body1).build() .build() @@ -582,7 +582,7 @@ fn call_1() { let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - assert_eq!(module.execute_main(vec![].into()).unwrap().unwrap(), RuntimeValue::I32(10)); + assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(10)); } /// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/call.txt#L23 @@ -616,7 +616,7 @@ fn call_2() { ]); let module = module() - .function().main() + .function() .signature().return_type().i32().build() .body().with_opcodes(body1).build() .build() @@ -631,7 +631,7 @@ fn call_2() { let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - assert_eq!(module.execute_main(vec![].into()).unwrap().unwrap(), RuntimeValue::I32(3628800)); + assert_eq!(module.execute_index(0, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(3628800)); } /// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/call-zero-args.txt @@ -669,14 +669,14 @@ fn call_zero_args() { .build() .body().with_opcodes(body2).build() .build() - .function().main() + .function() .body().with_opcodes(body3).build() .build() .build(); let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - assert_eq!(module.execute_main(vec![].into()).unwrap().unwrap(), RuntimeValue::I32(43)); + assert_eq!(module.execute_index(2, vec![].into()).unwrap().unwrap(), RuntimeValue::I32(43)); } /// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/callindirect.txt#L31 @@ -711,7 +711,7 @@ fn callindirect_1() { .signature().return_type().i32().build() .body().with_opcodes(body2).build() .build() - .function().main() + .function() .signature() .param().i32() .return_type().i32() @@ -722,8 +722,8 @@ fn callindirect_1() { let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(0)].into()).unwrap().unwrap(), RuntimeValue::I32(0)); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(1)].into()).unwrap().unwrap(), RuntimeValue::I32(1)); + assert_eq!(module.execute_index(2, vec![RuntimeValue::I32(0)].into()).unwrap().unwrap(), RuntimeValue::I32(0)); + assert_eq!(module.execute_index(2, vec![RuntimeValue::I32(1)].into()).unwrap().unwrap(), RuntimeValue::I32(1)); } /// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/callindirect.txt#L39 @@ -782,7 +782,7 @@ fn callindirect_2() { .return_type().i32().build() .body().with_opcodes(body3).build() .build() - .function().main() + .function() .signature() .param().i32() .param().i32() @@ -795,11 +795,11 @@ fn callindirect_2() { let program = ProgramInstance::new().unwrap(); let module = program.add_module("main", module).unwrap(); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(0)].into()).unwrap().unwrap(), RuntimeValue::I32(14)); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(1)].into()).unwrap().unwrap(), RuntimeValue::I32(6)); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(2)].into()).unwrap_err(), + assert_eq!(module.execute_index(3, vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(0)].into()).unwrap().unwrap(), RuntimeValue::I32(14)); + assert_eq!(module.execute_index(3, vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(1)].into()).unwrap().unwrap(), RuntimeValue::I32(6)); + assert_eq!(module.execute_index(3, vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(2)].into()).unwrap_err(), Error::Function("expected function with signature ([I32, I32]) -> Some(I32) when got with ([I32]) -> Some(I32)".into())); - assert_eq!(module.execute_main(vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(3)].into()).unwrap_err(), + assert_eq!(module.execute_index(3, vec![RuntimeValue::I32(10), RuntimeValue::I32(4), RuntimeValue::I32(3)].into()).unwrap_err(), Error::Table("trying to read table item with index 3 when there are only 3 items".into())); } diff --git a/src/interpreter/validator.rs b/src/interpreter/validator.rs index 19745fc..df6698a 100644 --- a/src/interpreter/validator.rs +++ b/src/interpreter/validator.rs @@ -1,3 +1,4 @@ +use std::u32; use elements::{Module, Opcode, BlockType, FunctionType, ValueType, External, Type}; use interpreter::Error; use interpreter::runner::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; @@ -6,6 +7,9 @@ use interpreter::module::ItemIndex; use interpreter::stack::StackWithLimit; use interpreter::variable::VariableType; +/// 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. @@ -354,8 +358,10 @@ impl Validator { } fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: StackValueType) -> Result { - if align > max_align { - return Err(Error::Validation(format!("Too large memory alignment {} (expected at most {})", align, max_align))); + if align != NATURAL_ALIGNMENT { + if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { + return Err(Error::Validation(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align))); + } } context.pop_value(ValueType::I32.into())?; @@ -365,8 +371,10 @@ impl Validator { } fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: StackValueType) -> Result { - if align > max_align { - return Err(Error::Validation(format!("Too large memory alignment {} (expected at most {})", align, max_align))); + if align != NATURAL_ALIGNMENT { + if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align { + return Err(Error::Validation(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align))); + } } context.require_memory(DEFAULT_MEMORY_INDEX)?;