diff --git a/examples/interpret.rs b/examples/interpret.rs index b90e4d3..318f235 100644 --- a/examples/interpret.rs +++ b/examples/interpret.rs @@ -15,8 +15,7 @@ fn main() { let program = parity_wasm::DefaultProgramInstance::with_env_params( interpreter::EnvParams { total_stack: 128*1024, - total_memory: 2*1024*1024, - allow_memory_growth: false, + ..Default::default() } ).expect("Failed to load program"); let module = parity_wasm::deserialize_file(&args[1]).expect("Failed to load module"); diff --git a/examples/invoke.rs b/examples/invoke.rs index f96d42d..b7a2b02 100644 --- a/examples/invoke.rs +++ b/examples/invoke.rs @@ -22,8 +22,7 @@ fn main() { let program = parity_wasm::DefaultProgramInstance::with_env_params( interpreter::EnvParams { total_stack: 128*1024, - total_memory: 2*1024*1024, - allow_memory_growth: false, + ..Default::default() } ).expect("Program instance to load"); diff --git a/src/builder/memory.rs b/src/builder/memory.rs index 748a1e7..4c87c27 100644 --- a/src/builder/memory.rs +++ b/src/builder/memory.rs @@ -1,12 +1,14 @@ use elements; use super::invoke::{Invoke, Identity}; +#[derive(Debug)] pub struct MemoryDefinition { pub min: u32, pub max: Option, pub data: Vec, } +#[derive(Debug)] pub struct MemoryDataDefinition { pub offset: elements::InitExpr, pub values: Vec, diff --git a/src/builder/module.rs b/src/builder/module.rs index 742b05c..7deb726 100644 --- a/src/builder/module.rs +++ b/src/builder/module.rs @@ -217,7 +217,7 @@ impl ModuleBuilder where F: Invoke { /// Push table pub fn push_table(&mut self, mut table: table::TableDefinition) -> u32 { let entries = self.module.table.entries_mut(); - entries.push(elements::TableType::new(table.min, Some(table.min))); + entries.push(elements::TableType::new(table.min, table.max)); let table_index = (entries.len() - 1) as u32; for entry in table.elements.drain(..) { self.module.element.entries_mut() diff --git a/src/builder/table.rs b/src/builder/table.rs index a717494..95705af 100644 --- a/src/builder/table.rs +++ b/src/builder/table.rs @@ -1,11 +1,14 @@ use elements; use super::invoke::{Invoke, Identity}; +#[derive(Debug)] pub struct TableDefinition { pub min: u32, + pub max: Option, pub elements: Vec, } +#[derive(Debug)] pub struct TableEntryDefinition { pub offset: elements::InitExpr, pub values: Vec, @@ -35,6 +38,11 @@ impl TableBuilder where F: Invoke { self } + pub fn with_max(mut self, max: Option) -> Self { + self.table.max = max; + self + } + pub fn with_element(mut self, index: u32, values: Vec) -> Self { self.table.elements.push(TableEntryDefinition { offset: elements::InitExpr::new(vec![elements::Opcode::I32Const(index as i32)]), @@ -52,6 +60,7 @@ impl Default for TableDefinition { fn default() -> Self { TableDefinition { min: 0, + max: None, elements: Vec::new(), } } diff --git a/src/elements/import_entry.rs b/src/elements/import_entry.rs index edf8462..e2f33ac 100644 --- a/src/elements/import_entry.rs +++ b/src/elements/import_entry.rs @@ -96,7 +96,7 @@ impl Serialize for TableType { } /// Memory limits -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ResizableLimits { initial: u32, maximum: Option, diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index 9299db7..d9ca633 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -19,7 +19,7 @@ const DEFAULT_TOTAL_STACK: u32 = 5 * 1024 * 1024; /// Total memory, allocated by default. const DEFAULT_TOTAL_MEMORY: u32 = 16 * 1024 * 1024; /// Whether memory can be enlarged, or not. -const DEFAULT_ALLOW_MEMORY_GROWTH: bool = false; +const DEFAULT_ALLOW_MEMORY_GROWTH: bool = true; /// Default tableBase variable value. const DEFAULT_TABLE_BASE: u32 = 0; /// Default tableBase variable value. @@ -76,6 +76,8 @@ pub struct EnvParams { pub total_memory: u32, /// Allow memory growth. pub allow_memory_growth: bool, + /// Table size. + pub table_size: u32, } pub struct EnvModuleInstance { @@ -180,7 +182,7 @@ pub fn env_module(params: EnvParams) -> Result(params: EnvParams) -> Result { + /// Memofy limits. + limits: ResizableLimits, /// Linear memory buffer. buffer: RwLock>, /// Maximum buffer size. @@ -51,6 +53,7 @@ impl MemoryInstance where E: UserError { .ok_or(Error::Memory(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES)))?; let memory = MemoryInstance { + limits: memory_type.limits().clone(), buffer: RwLock::new(vec![0; initial_size as usize]), maximum_size: maximum_size, _dummy: Default::default(), @@ -59,6 +62,11 @@ impl MemoryInstance where E: UserError { Ok(Arc::new(memory)) } + /// Return linear memory limits. + pub fn limits(&self) -> &ResizableLimits { + &self.limits + } + /// Return linear memory size (in pages). pub fn size(&self) -> u32 { self.buffer.read().len() as u32 / LINEAR_MEMORY_PAGE_SIZE diff --git a/src/interpreter/module.rs b/src/interpreter/module.rs index a81a2ba..c601225 100644 --- a/src/interpreter/module.rs +++ b/src/interpreter/module.rs @@ -304,12 +304,48 @@ impl ModuleInstance where E: UserError { self.imports.global(externals, import, Some(global_type.content_type().into()))?; }, &External::Memory(ref memory_type) => { - check_limits(memory_type.limits())?; - self.imports.memory(externals, import)?; + let import_limits = memory_type.limits(); + check_limits(import_limits)?; + + let memory = self.imports.memory(externals, import)?; + let memory_limits = memory.limits(); + + // a linear-memory import's minimum length is required to be at most the imported linear memory's minimum length. + if import_limits.initial() > memory_limits.initial() { + return Err(Error::Validation(format!("trying to import memory with initial={} and import.initial={}", memory_limits.initial(), import_limits.initial()))); + } + + // a linear-memory import is required to have a maximum length if the imported linear memory has a maximum length. + // if present, a linear-memory import's maximum length is required to be at least the imported linear memory's maximum length. + match (memory_limits.maximum(), import_limits.maximum()) { + (Some(_), None) | (None, Some(_)) => + return Err(Error::Validation("trying to import memory with maximum absence mismatch".into())), + (Some(ml), Some(il)) if il < ml => + return Err(Error::Validation(format!("trying to import memory with maximum={} and import.maximum={}", ml, il))), + _ => (), + } }, &External::Table(ref table_type) => { - check_limits(table_type.limits())?; - self.imports.table(externals, import)?; + let import_limits = table_type.limits(); + check_limits(import_limits)?; + + let table = self.imports.table(externals, import)?; + let table_limits = table.limits(); + + // a table import's minimum length is required to be at most the imported table's minimum length. + if import_limits.initial() > table_limits.initial() { + return Err(Error::Validation(format!("trying to import table with initial={} and import.initial={}", table_limits.initial(), import_limits.initial()))); + } + + // a table import is required to have a maximum length if the imported table has a maximum length. + // if present, a table import's maximum length is required to be at least the imported table's maximum length. + match (table_limits.maximum(), import_limits.maximum()) { + (Some(_), None) | (None, Some(_)) => + return Err(Error::Validation("trying to import table with maximum absence mismatch".into())), + (Some(ml), Some(il)) if il < ml => + return Err(Error::Validation(format!("trying to import table with maximum={} and import.maximum={}", ml, il))), + _ => (), + } }, } } @@ -600,7 +636,7 @@ impl ModuleInstanceInterface for ModuleInstance where E: UserError { })) } - fn call_internal_function(&self, mut outer: CallerContext, index: u32) -> Result, Error> { + fn call_internal_function(&self, outer: CallerContext, index: u32) -> Result, Error> { let function_type = self.function_type(ItemIndex::Internal(index))?; let args = prepare_function_args(&function_type, outer.value_stack)?; let function_ref = InternalFunctionReference { module: self.self_ref(Some(outer.externals))?, internal_index: index }; diff --git a/src/interpreter/table.rs b/src/interpreter/table.rs index 681a74d..ffa9d99 100644 --- a/src/interpreter/table.rs +++ b/src/interpreter/table.rs @@ -1,7 +1,7 @@ use std::u32; use std::sync::Arc; use parking_lot::RwLock; -use elements::TableType; +use elements::{TableType, ResizableLimits}; use interpreter::{Error, UserError}; use interpreter::module::check_limits; use interpreter::variable::{VariableInstance, VariableType}; @@ -9,6 +9,8 @@ use interpreter::value::RuntimeValue; /// Table instance. pub struct TableInstance { + /// Table limits. + limits: ResizableLimits, /// Table variables type. variable_type: VariableType, /// Table memory buffer. @@ -27,6 +29,7 @@ impl TableInstance where E: UserError { let variable_type = table_type.elem_type().into(); Ok(Arc::new(TableInstance { + limits: table_type.limits().clone(), variable_type: variable_type, buffer: RwLock::new( vec![TableElement::new(VariableInstance::new(true, variable_type, RuntimeValue::Null)?); table_type.limits().initial() as usize] @@ -34,6 +37,11 @@ impl TableInstance where E: UserError { })) } + /// Return table limits. + pub fn limits(&self) -> &ResizableLimits { + &self.limits + } + /// Get variable type for this table. pub fn variable_type(&self) -> VariableType { self.variable_type diff --git a/src/interpreter/tests/basics.rs b/src/interpreter/tests/basics.rs index 5856e58..20ea4bd 100644 --- a/src/interpreter/tests/basics.rs +++ b/src/interpreter/tests/basics.rs @@ -5,7 +5,7 @@ use std::cell::RefCell; use std::collections::HashMap; use builder::module; use elements::{ExportEntry, Internal, ImportEntry, External, GlobalEntry, GlobalType, - InitExpr, ValueType, BlockType, Opcodes, Opcode, FunctionType}; + InitExpr, ValueType, BlockType, Opcodes, Opcode, FunctionType, TableType, MemoryType}; use interpreter::{Error, UserError, ProgramInstance, DefaultProgramInstance, DefaultModuleInstance}; use interpreter::env_native::{env_native_module, UserDefinedElements, UserFunctionExecutor, UserFunctionDescriptor}; use interpreter::memory::MemoryInstance; @@ -456,3 +456,143 @@ fn if_else_with_return_type_validation() { Opcode::End, ]).unwrap(); } + +#[test] +fn memory_import_limits_initial() { + let core_module = module() + .memory().with_min(10).build() + .with_export(ExportEntry::new("memory".into(), Internal::Memory(0))) + .build(); + + let program = DefaultProgramInstance::new().unwrap(); + program.add_module("core", core_module, None).unwrap(); + + let test_cases = vec![ + (9, false), + (10, false), + (11, true), + ]; + + for test_case in test_cases { + let (import_initial, is_error) = test_case; + let client_module = module() + .with_import(ImportEntry::new("core".into(), "memory".into(), External::Memory(MemoryType::new(import_initial, None)))) + .build(); + match program.add_module("client", client_module, None).map(|_| ()) { + Ok(_) if !is_error => (), + Err(Error::Validation(ref actual_error)) + if is_error && actual_error == &format!("trying to import memory with initial=10 and import.initial={}", import_initial) => (), + x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x), + } + } +} + +#[test] +fn memory_import_limits_maximum() { + #[derive(Debug, Clone, Copy, PartialEq)] + enum MaximumError { AbsenceMismatch, ValueMismatch, Ok }; + + let test_cases = vec![ + (None, Some(100), MaximumError::AbsenceMismatch), + (Some(100), None, MaximumError::AbsenceMismatch), + (Some(100), Some(98), MaximumError::ValueMismatch), + (Some(100), Some(100), MaximumError::Ok), + (Some(100), Some(101), MaximumError::Ok), + (None, None, MaximumError::Ok), + ]; + + let program = DefaultProgramInstance::new().unwrap(); + for test_case in test_cases { + let (core_maximum, client_maximum, expected_err) = test_case; + let core_module = module() + .memory().with_min(10).with_max(core_maximum).build() + .with_export(ExportEntry::new("memory".into(), Internal::Memory(0))) + .build(); + let client_module = module() + .with_import(ImportEntry::new("core".into(), "memory".into(), External::Memory(MemoryType::new(10, client_maximum)))) + .build(); + + program.add_module("core", core_module, None).unwrap(); + match program.add_module("client", client_module, None).map(|_| ()) { + Err(Error::Validation(actual_err)) => match expected_err { + MaximumError::AbsenceMismatch + if actual_err == "trying to import memory with maximum absence mismatch".to_owned() => (), + MaximumError::ValueMismatch + if actual_err == format!("trying to import memory with maximum={} and import.maximum={}", core_maximum.unwrap_or_default(), client_maximum.unwrap_or_default()) => (), + _ => panic!("unexpected validation error for test_case {:?}: {}", test_case, actual_err), + }, + Ok(_) if expected_err == MaximumError::Ok => (), + x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x), + } + } +} + +#[test] +fn table_import_limits_initial() { + let core_module = module() + .table().with_min(10).build() + .with_export(ExportEntry::new("table".into(), Internal::Table(0))) + .build(); + + let program = DefaultProgramInstance::new().unwrap(); + program.add_module("core", core_module, None).unwrap(); + + let test_cases = vec![ + (9, false), + (10, false), + (11, true), + ]; + + for test_case in test_cases { + let (import_initial, is_error) = test_case; + let client_module = module() + .with_import(ImportEntry::new("core".into(), "table".into(), External::Table(TableType::new(import_initial, None)))) + .build(); + match program.add_module("client", client_module, None).map(|_| ()) { + Ok(_) if !is_error => (), + Err(Error::Validation(ref actual_error)) + if is_error && actual_error == &format!("trying to import table with initial=10 and import.initial={}", import_initial) => (), + x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x), + } + } +} + +#[test] +fn table_import_limits_maximum() { + #[derive(Debug, Clone, Copy, PartialEq)] + enum MaximumError { AbsenceMismatch, ValueMismatch, Ok }; + + let test_cases = vec![ + (None, Some(100), MaximumError::AbsenceMismatch), + (Some(100), None, MaximumError::AbsenceMismatch), + (Some(100), Some(98), MaximumError::ValueMismatch), + (Some(100), Some(100), MaximumError::Ok), + (Some(100), Some(101), MaximumError::Ok), + (None, None, MaximumError::Ok), + ]; + + let program = DefaultProgramInstance::new().unwrap(); + for test_case in test_cases { + let (core_maximum, client_maximum, expected_err) = test_case; + let core_module = module() + .table().with_min(10).with_max(core_maximum).build() + .with_export(ExportEntry::new("table".into(), Internal::Table(0))) + .build(); + let client_module = module() + .with_import(ImportEntry::new("core".into(), "table".into(), External::Table(TableType::new(10, client_maximum)))) + .build(); + + program.add_module("core", core_module, None).unwrap(); + match program.add_module("client", client_module, None).map(|_| ()) { + Err(Error::Validation(actual_err)) => match expected_err { + MaximumError::AbsenceMismatch + if actual_err == "trying to import table with maximum absence mismatch".to_owned() => (), + MaximumError::ValueMismatch + if actual_err == format!("trying to import table with maximum={} and import.maximum={}", core_maximum.unwrap_or_default(), client_maximum.unwrap_or_default()) => (), + _ => panic!("unexpected validation error for test_case {:?}: {}", test_case, actual_err), + }, + Ok(_) if expected_err == MaximumError::Ok => (), + x @ _ => panic!("unexpected result for test_case {:?}: {:?}", test_case, x), + } + } +} diff --git a/src/interpreter/tests/wasm.rs b/src/interpreter/tests/wasm.rs index 71e4f08..7e0d936 100644 --- a/src/interpreter/tests/wasm.rs +++ b/src/interpreter/tests/wasm.rs @@ -1,6 +1,6 @@ use elements::deserialize_file; use elements::Module; -use interpreter::{EnvParams, ExecutionParams, DefaultProgramInstance}; +use interpreter::{ExecutionParams, DefaultProgramInstance}; use interpreter::value::RuntimeValue; use interpreter::module::{ModuleInstanceInterface, ItemIndex}; @@ -11,12 +11,7 @@ fn interpreter_inc_i32() { // The WASM file containing the module and function const WASM_FILE: &str = &"res/cases/v1/inc_i32.wasm"; - let program = DefaultProgramInstance::with_env_params( - EnvParams { - total_stack: 128 * 1024, - total_memory: 2 * 1024 * 1024, - allow_memory_growth: false, - }).expect("Failed to instanciate program"); + let program = DefaultProgramInstance::new().expect("Failed to instanciate program"); let module: Module = deserialize_file(WASM_FILE).expect("Failed to deserialize module from buffer"); @@ -48,11 +43,7 @@ fn interpreter_accumulate_u8() { const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1]; // Declare the memory limits of the runtime-environment - let program = DefaultProgramInstance::with_env_params(EnvParams { - total_stack: 128 * 1024, - total_memory: 2 * 1024 * 1024, - allow_memory_growth: false, - }).expect("Failed to instanciate program"); + let program = DefaultProgramInstance::new().expect("Failed to instanciate program"); // Load the module-structure from wasm-file and add to program let module: Module =