diff --git a/src/validation/func.rs b/src/validation/func.rs index aa045d9..b31471b 100644 --- a/src/validation/func.rs +++ b/src/validation/func.rs @@ -6,7 +6,7 @@ use parity_wasm::elements::{BlockType, Func, FuncBody, Instruction, TableElement use validation::context::ModuleContext; use validation::util::Locals; -use validation::Error; +use validation::{Error, FunctionValidator}; use common::stack::StackWithLimit; use isa; @@ -152,6 +152,61 @@ impl PartialEq for ValueType { } } +pub fn drive( + module: &ModuleContext, + func: &Func, + body: &FuncBody, + validator: T, +) -> Result { + let (params, result_ty) = module.require_function_type(func.type_ref())?; + + let ins_size_estimate = body.code().elements().len(); + let mut context = FunctionValidationContext::new( + &module, + Locals::new(params, body.locals())?, + DEFAULT_VALUE_STACK_LIMIT, + DEFAULT_FRAME_STACK_LIMIT, + result_ty, + ); + + let mut compiler = Compiler { + sink: Sink::with_instruction_capacity(ins_size_estimate), + label_stack: Vec::new(), + }; + let end_label = compiler.sink.new_label(); + compiler + .label_stack + .push(BlockFrameType::Block { end_label }); + + assert!(context.frame_stack.is_empty()); + + let body = body.code().elements(); + let body_len = body.len(); + if body_len == 0 { + return Err(Error("Non-empty function body expected".into())); + } + + loop { + let instruction = &body[context.position]; + + compiler + .compile_instruction(&mut context, instruction) + .map_err(|err| { + Error(format!( + "At instruction {:?}(@{}): {}", + instruction, context.position, err + )) + })?; + + context.position += 1; + if context.position == body_len { + break; + } + } + + Ok(validator.finish()) +} + pub struct Compiler { /// A sink used to emit optimized code. sink: Sink, diff --git a/src/validation/mod.rs b/src/validation/mod.rs index ffea71e..fc26fa7 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -175,6 +175,236 @@ pub fn deny_floating_point(module: &Module) -> Result<(), Error> { Ok(()) } +pub trait Validation { + type Output; + type FunctionValidator: FunctionValidator; + fn new(module: &Module) -> Self; + fn create_function_validator(&mut self) -> Self::FunctionValidator; + fn on_function_validated(&mut self, index: u32, + output: <::FunctionValidator as FunctionValidator>::Output + ); + fn finish(self) -> Self::Output; +} + +pub trait FunctionValidator { + type Output; + fn new() -> Self; + fn next_instruction(&mut self, instruction: &Instruction) -> Result<(), ()>; + fn finish(self) -> Self::Output; +} + +// TODO: Rename to validate_module +pub fn validate_module2(module: &mut Module) -> Result { + let mut context_builder = ModuleContextBuilder::new(); + let mut imported_globals = Vec::new(); + let mut validation = V::new(&module); + + // Copy types from module as is. + context_builder.set_types( + module + .type_section() + .map(|ts| { + ts.types() + .into_iter() + .map(|&Type::Function(ref ty)| ty) + .cloned() + .collect() + }) + .unwrap_or_default(), + ); + + // Fill elements with imported values. + for import_entry in module + .import_section() + .map(|i| i.entries()) + .unwrap_or_default() + { + match *import_entry.external() { + External::Function(idx) => context_builder.push_func_type_index(idx), + External::Table(ref table) => context_builder.push_table(table.clone()), + External::Memory(ref memory) => context_builder.push_memory(memory.clone()), + External::Global(ref global) => { + context_builder.push_global(global.clone()); + imported_globals.push(global.clone()); + } + } + } + + // Concatenate elements with defined in the module. + if let Some(function_section) = module.function_section() { + for func_entry in function_section.entries() { + context_builder.push_func_type_index(func_entry.type_ref()) + } + } + if let Some(table_section) = module.table_section() { + for table_entry in table_section.entries() { + validate_table_type(table_entry)?; + context_builder.push_table(table_entry.clone()); + } + } + if let Some(mem_section) = module.memory_section() { + for mem_entry in mem_section.entries() { + validate_memory_type(mem_entry)?; + context_builder.push_memory(mem_entry.clone()); + } + } + if let Some(global_section) = module.global_section() { + for global_entry in global_section.entries() { + validate_global_entry(global_entry, &imported_globals)?; + context_builder.push_global(global_entry.global_type().clone()); + } + } + + let context = context_builder.build(); + + let function_section_len = module + .function_section() + .map(|s| s.entries().len()) + .unwrap_or(0); + let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0); + if function_section_len != code_section_len { + return Err(Error(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 = module + .function_section() + .expect("function_section_len != 0; qed"); + let code_section = 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_body = code_section + .bodies() + .get(index as usize) + .ok_or(Error(format!("Missing body for function {}", index)))?; + + let func_validator = validation.create_function_validator(); + let output = func::drive(&context, function, function_body, func_validator).map_err( + |Error(ref msg)| { + Error(format!( + "Function #{} reading/validation error: {}", + index, msg + )) + }, + )?; + validation.on_function_validated(index as u32, output); + } + } + + // validate start section + if let Some(start_fn_idx) = module.start_section() { + let (params, return_ty) = context.require_function(start_fn_idx)?; + if return_ty != BlockType::NoResult || params.len() != 0 { + return Err(Error( + "start function expected to have type [] -> []".into(), + )); + } + } + + // validate export section + if let Some(export_section) = module.export_section() { + let mut export_names = HashSet::with_capacity(export_section.entries().len()); + for export in export_section.entries() { + // HashSet::insert returns false if item already in set. + let duplicate = export_names.insert(export.field()) == false; + if duplicate { + return Err(Error(format!("duplicate export {}", export.field()))); + } + match *export.internal() { + Internal::Function(function_index) => { + context.require_function(function_index)?; + } + Internal::Global(global_index) => { + context.require_global(global_index, Some(false))?; + } + Internal::Memory(memory_index) => { + context.require_memory(memory_index)?; + } + Internal::Table(table_index) => { + context.require_table(table_index)?; + } + } + } + } + + // validate import section + if let Some(import_section) = module.import_section() { + for import in import_section.entries() { + match *import.external() { + External::Function(function_type_index) => { + context.require_function_type(function_type_index)?; + } + External::Global(ref global_type) => { + if global_type.is_mutable() { + return Err(Error(format!( + "trying to import mutable global {}", + import.field() + ))); + } + } + External::Memory(ref memory_type) => { + validate_memory_type(memory_type)?; + } + External::Table(ref table_type) => { + validate_table_type(table_type)?; + } + } + } + } + + // there must be no greater than 1 table in tables index space + if context.tables().len() > 1 { + return Err(Error(format!( + "too many tables in index space: {}", + context.tables().len() + ))); + } + + // there must be no greater than 1 linear memory in memory index space + if context.memories().len() > 1 { + return Err(Error(format!( + "too many memory regions in index space: {}", + context.memories().len() + ))); + } + + // use data section to initialize linear memory regions + if let Some(data_section) = module.data_section() { + for data_segment in data_section.entries() { + context.require_memory(data_segment.index())?; + let init_ty = expr_const_type(data_segment.offset(), context.globals())?; + if init_ty != ValueType::I32 { + return Err(Error("segment offset should return I32".into())); + } + } + } + + // use element section to fill tables + if let Some(element_section) = module.elements_section() { + for element_segment in element_section.entries() { + context.require_table(element_segment.index())?; + + let init_ty = expr_const_type(element_segment.offset(), context.globals())?; + if init_ty != ValueType::I32 { + return Err(Error("segment offset should return I32".into())); + } + + for function_index in element_segment.members() { + context.require_function(*function_index)?; + } + } + } + + Ok(validation.finish()) +} + pub fn validate_module(module: Module) -> Result { let mut context_builder = ModuleContextBuilder::new(); let mut imported_globals = Vec::new();