Add Validation traits

This commit is contained in:
Sergey Pepyakin 2019-04-08 14:49:53 +02:00
parent eaa030afa7
commit 9723dfbfb6
2 changed files with 286 additions and 1 deletions

View File

@ -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<StackValueType> for ValueType {
}
}
pub fn drive<T: FunctionValidator>(
module: &ModuleContext,
func: &Func,
body: &FuncBody,
validator: T,
) -> Result<T::Output, Error> {
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,

View File

@ -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: <<Self as Validation>::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<V: Validation>(module: &mut Module) -> Result<V::Output, Error> {
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<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();