mirror of
https://github.com/fluencelabs/parity-wasm
synced 2025-06-15 16:01:44 +00:00
instructions validator
This commit is contained in:
@ -16,3 +16,7 @@ run_test!("f64_bitwise", wasm_f64_bitwise);
|
|||||||
run_test!("forward", wasm_forward);
|
run_test!("forward", wasm_forward);
|
||||||
run_test!("i32", wasm_i32);
|
run_test!("i32", wasm_i32);
|
||||||
run_test!("i64", wasm_i64);
|
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);
|
||||||
|
@ -38,10 +38,13 @@ fn setup_program(base_dir: &str, test_module_path: &str) -> (ProgramInstance, Ar
|
|||||||
(program, module_instance)
|
(program, module_instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_load(base_dir: &str, module_path: &str) -> Result<parity_wasm::elements::Module, parity_wasm::elements::Error> {
|
fn try_load(base_dir: &str, module_path: &str) -> Result<(), parity_wasm::interpreter::Error> {
|
||||||
let mut wasm_path = PathBuf::from(base_dir.clone());
|
let mut wasm_path = PathBuf::from(base_dir.clone());
|
||||||
wasm_path.push(module_path);
|
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 {
|
fn runtime_value(test_val: &test::RuntimeValue) -> parity_wasm::RuntimeValue {
|
||||||
|
1
spec/testsuite
Submodule
1
spec/testsuite
Submodule
Submodule spec/testsuite added at 434fe2f1cb
@ -81,7 +81,7 @@ pub struct EnvModuleInstance {
|
|||||||
|
|
||||||
impl EnvModuleInstance {
|
impl EnvModuleInstance {
|
||||||
pub fn new(params: EnvParams, module: Module) -> Result<Self, Error> {
|
pub fn new(params: EnvParams, module: Module) -> Result<Self, Error> {
|
||||||
let instance = ModuleInstance::new(Weak::default(), module)?;
|
let instance = ModuleInstance::new_with_validation_flag(Weak::default(), module, false)?;
|
||||||
|
|
||||||
Ok(EnvModuleInstance {
|
Ok(EnvModuleInstance {
|
||||||
_params: params,
|
_params: params,
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Program-level error.
|
/// Program-level error.
|
||||||
Program(String),
|
Program(String),
|
||||||
|
/// Validation error.
|
||||||
|
Validation(String),
|
||||||
/// Initialization error.
|
/// Initialization error.
|
||||||
Initialization(String),
|
Initialization(String),
|
||||||
/// Function-level error.
|
/// Function-level error.
|
||||||
@ -37,6 +39,7 @@ impl Into<String> for Error {
|
|||||||
fn into(self) -> String {
|
fn into(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Error::Program(s) => s,
|
Error::Program(s) => s,
|
||||||
|
Error::Validation(s) => s,
|
||||||
Error::Initialization(s) => s,
|
Error::Initialization(s) => s,
|
||||||
Error::Function(s) => s,
|
Error::Function(s) => s,
|
||||||
Error::Table(s) => s,
|
Error::Table(s) => s,
|
||||||
@ -63,6 +66,7 @@ mod program;
|
|||||||
mod runner;
|
mod runner;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod table;
|
mod table;
|
||||||
|
mod validator;
|
||||||
mod value;
|
mod value;
|
||||||
mod variable;
|
mod variable;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
use std::sync::{Arc, Weak};
|
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::Error;
|
||||||
use interpreter::imports::ModuleImports;
|
use interpreter::imports::ModuleImports;
|
||||||
use interpreter::memory::MemoryInstance;
|
use interpreter::memory::MemoryInstance;
|
||||||
@ -9,9 +9,15 @@ use interpreter::program::ProgramInstanceEssence;
|
|||||||
use interpreter::runner::{Interpreter, FunctionContext};
|
use interpreter::runner::{Interpreter, FunctionContext};
|
||||||
use interpreter::stack::StackWithLimit;
|
use interpreter::stack::StackWithLimit;
|
||||||
use interpreter::table::TableInstance;
|
use interpreter::table::TableInstance;
|
||||||
|
use interpreter::validator::{Validator, FunctionValidationContext};
|
||||||
use interpreter::value::{RuntimeValue, TryInto};
|
use interpreter::value::{RuntimeValue, TryInto};
|
||||||
use interpreter::variable::{VariableInstance, VariableType};
|
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)]
|
#[derive(Default, Clone)]
|
||||||
/// Execution context.
|
/// Execution context.
|
||||||
pub struct ExecutionParams<'a> {
|
pub struct ExecutionParams<'a> {
|
||||||
@ -112,8 +118,11 @@ impl<'a> From<Vec<RuntimeValue>> for ExecutionParams<'a> {
|
|||||||
impl ModuleInstance {
|
impl ModuleInstance {
|
||||||
/// Instantiate given module within program context.
|
/// Instantiate given module within program context.
|
||||||
pub fn new(program: Weak<ProgramInstanceEssence>, module: Module) -> Result<Self, Error> {
|
pub fn new(program: Weak<ProgramInstanceEssence>, module: Module) -> Result<Self, Error> {
|
||||||
// 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<ProgramInstanceEssence>, module: Module, requires_validation: bool) -> Result<Self, Error> {
|
||||||
// load entries from import section
|
// load entries from import section
|
||||||
let imports = ModuleImports::new(program, module.import_section());
|
let imports = ModuleImports::new(program, module.import_section());
|
||||||
|
|
||||||
@ -155,12 +164,35 @@ impl ModuleInstance {
|
|||||||
tables: tables,
|
tables: tables,
|
||||||
globals: globals,
|
globals: globals,
|
||||||
};
|
};
|
||||||
module.complete_initialization()?;
|
module.complete_initialization(requires_validation)?;
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete module initialization.
|
/// 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
|
// use data section to initialize linear memory regions
|
||||||
if let Some(data_section) = self.module.data_section() {
|
if let Some(data_section) = self.module.data_section() {
|
||||||
for (data_segment_index, data_segment) in data_section.entries().iter().enumerate() {
|
for (data_segment_index, data_segment) in data_section.entries().iter().enumerate() {
|
||||||
@ -365,8 +397,8 @@ impl<'a> CallerContext<'a> {
|
|||||||
/// Top most args
|
/// Top most args
|
||||||
pub fn topmost(args: &'a mut StackWithLimit<RuntimeValue>, externals: &'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>) -> Self {
|
pub fn topmost(args: &'a mut StackWithLimit<RuntimeValue>, externals: &'a HashMap<String, Arc<ModuleInstanceInterface + 'a>>) -> Self {
|
||||||
CallerContext {
|
CallerContext {
|
||||||
value_stack_limit: 1024,
|
value_stack_limit: DEFAULT_VALUE_STACK_LIMIT,
|
||||||
frame_stack_limit: 1024,
|
frame_stack_limit: DEFAULT_FRAME_STACK_LIMIT,
|
||||||
value_stack: args,
|
value_stack: args,
|
||||||
externals: externals,
|
externals: externals,
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ impl ProgramInstance {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Instantiate module.
|
/// Instantiate module with validation.
|
||||||
pub fn add_module(&self, name: &str, module: Module) -> Result<Arc<ModuleInstance>, Error> {
|
pub fn add_module(&self, name: &str, module: Module) -> Result<Arc<ModuleInstance>, Error> {
|
||||||
let module_instance = Arc::new(ModuleInstance::new(Arc::downgrade(&self.essence), module)?);
|
let module_instance = Arc::new(ModuleInstance::new(Arc::downgrade(&self.essence), module)?);
|
||||||
// replace existing module with the same name with new one
|
// replace existing module with the same name with new one
|
||||||
@ -39,6 +39,14 @@ impl ProgramInstance {
|
|||||||
Ok(module_instance)
|
Ok(module_instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instantiate module without validation.
|
||||||
|
pub fn add_module_without_validation(&self, name: &str, module: Module) -> Result<Arc<ModuleInstance>, 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
|
/// Get one of the modules by name
|
||||||
pub fn module(&self, name: &str) -> Option<Arc<ModuleInstanceInterface>> {
|
pub fn module(&self, name: &str) -> Option<Arc<ModuleInstanceInterface>> {
|
||||||
self.essence.module(name)
|
self.essence.module(name)
|
||||||
|
@ -14,9 +14,12 @@ use interpreter::value::{
|
|||||||
};
|
};
|
||||||
use interpreter::variable::VariableInstance;
|
use interpreter::variable::VariableInstance;
|
||||||
|
|
||||||
const DEFAULT_MEMORY_INDEX: u32 = 0;
|
/// Index of default linear memory.
|
||||||
const DEFAULT_TABLE_INDEX: u32 = 0;
|
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
|
||||||
|
/// Index of default table.
|
||||||
|
pub const DEFAULT_TABLE_INDEX: u32 = 0;
|
||||||
|
|
||||||
|
/// Function interpreter.
|
||||||
pub struct Interpreter;
|
pub struct Interpreter;
|
||||||
|
|
||||||
/// Function execution context.
|
/// Function execution context.
|
||||||
@ -101,30 +104,30 @@ impl Interpreter {
|
|||||||
&Opcode::GetGlobal(index) => Interpreter::run_get_global(context, index),
|
&Opcode::GetGlobal(index) => Interpreter::run_get_global(context, index),
|
||||||
&Opcode::SetGlobal(index) => Interpreter::run_set_global(context, index),
|
&Opcode::SetGlobal(index) => Interpreter::run_set_global(context, index),
|
||||||
|
|
||||||
&Opcode::I32Load(offset, align) => Interpreter::run_load::<i32>(context, offset, align),
|
&Opcode::I32Load(align, offset) => Interpreter::run_load::<i32>(context, align, offset),
|
||||||
&Opcode::I64Load(offset, align) => Interpreter::run_load::<i64>(context, offset, align),
|
&Opcode::I64Load(align, offset) => Interpreter::run_load::<i64>(context, align, offset),
|
||||||
&Opcode::F32Load(offset, align) => Interpreter::run_load::<f32>(context, offset, align),
|
&Opcode::F32Load(align, offset) => Interpreter::run_load::<f32>(context, align, offset),
|
||||||
&Opcode::F64Load(offset, align) => Interpreter::run_load::<f64>(context, offset, align),
|
&Opcode::F64Load(align, offset) => Interpreter::run_load::<f64>(context, align, offset),
|
||||||
&Opcode::I32Load8S(offset, align) => Interpreter::run_load_extend::<i8, i32>(context, offset, align),
|
&Opcode::I32Load8S(align, offset) => Interpreter::run_load_extend::<i8, i32>(context, align, offset),
|
||||||
&Opcode::I32Load8U(offset, align) => Interpreter::run_load_extend::<u8, i32>(context, offset, align),
|
&Opcode::I32Load8U(align, offset) => Interpreter::run_load_extend::<u8, i32>(context, align, offset),
|
||||||
&Opcode::I32Load16S(offset, align) => Interpreter::run_load_extend::<i16, i32>(context, offset, align),
|
&Opcode::I32Load16S(align, offset) => Interpreter::run_load_extend::<i16, i32>(context, align, offset),
|
||||||
&Opcode::I32Load16U(offset, align) => Interpreter::run_load_extend::<u16, i32>(context, offset, align),
|
&Opcode::I32Load16U(align, offset) => Interpreter::run_load_extend::<u16, i32>(context, align, offset),
|
||||||
&Opcode::I64Load8S(offset, align) => Interpreter::run_load_extend::<i8, i64>(context, offset, align),
|
&Opcode::I64Load8S(align, offset) => Interpreter::run_load_extend::<i8, i64>(context, align, offset),
|
||||||
&Opcode::I64Load8U(offset, align) => Interpreter::run_load_extend::<u8, i64>(context, offset, align),
|
&Opcode::I64Load8U(align, offset) => Interpreter::run_load_extend::<u8, i64>(context, align, offset),
|
||||||
&Opcode::I64Load16S(offset, align) => Interpreter::run_load_extend::<i16, i64>(context, offset, align),
|
&Opcode::I64Load16S(align, offset) => Interpreter::run_load_extend::<i16, i64>(context, align, offset),
|
||||||
&Opcode::I64Load16U(offset, align) => Interpreter::run_load_extend::<u16, i64>(context, offset, align),
|
&Opcode::I64Load16U(align, offset) => Interpreter::run_load_extend::<u16, i64>(context, align, offset),
|
||||||
&Opcode::I64Load32S(offset, align) => Interpreter::run_load_extend::<i32, i64>(context, offset, align),
|
&Opcode::I64Load32S(align, offset) => Interpreter::run_load_extend::<i32, i64>(context, align, offset),
|
||||||
&Opcode::I64Load32U(offset, align) => Interpreter::run_load_extend::<u32, i64>(context, offset, align),
|
&Opcode::I64Load32U(align, offset) => Interpreter::run_load_extend::<u32, i64>(context, align, offset),
|
||||||
|
|
||||||
&Opcode::I32Store(offset, align) => Interpreter::run_store::<i32>(context, offset, align),
|
&Opcode::I32Store(align, offset) => Interpreter::run_store::<i32>(context, align, offset),
|
||||||
&Opcode::I64Store(offset, align) => Interpreter::run_store::<i64>(context, offset, align),
|
&Opcode::I64Store(align, offset) => Interpreter::run_store::<i64>(context, align, offset),
|
||||||
&Opcode::F32Store(offset, align) => Interpreter::run_store::<f32>(context, offset, align),
|
&Opcode::F32Store(align, offset) => Interpreter::run_store::<f32>(context, align, offset),
|
||||||
&Opcode::F64Store(offset, align) => Interpreter::run_store::<f64>(context, offset, align),
|
&Opcode::F64Store(align, offset) => Interpreter::run_store::<f64>(context, align, offset),
|
||||||
&Opcode::I32Store8(offset, align) => Interpreter::run_store_wrap::<i32, i8>(context, offset, align),
|
&Opcode::I32Store8(align, offset) => Interpreter::run_store_wrap::<i32, i8>(context, align, offset),
|
||||||
&Opcode::I32Store16(offset, align) => Interpreter::run_store_wrap::<i32, i16>(context, offset, align),
|
&Opcode::I32Store16(align, offset) => Interpreter::run_store_wrap::<i32, i16>(context, align, offset),
|
||||||
&Opcode::I64Store8(offset, align) => Interpreter::run_store_wrap::<i64, i8>(context, offset, align),
|
&Opcode::I64Store8(align, offset) => Interpreter::run_store_wrap::<i64, i8>(context, align, offset),
|
||||||
&Opcode::I64Store16(offset, align) => Interpreter::run_store_wrap::<i64, i16>(context, offset, align),
|
&Opcode::I64Store16(align, offset) => Interpreter::run_store_wrap::<i64, i16>(context, align, offset),
|
||||||
&Opcode::I64Store32(offset, align) => Interpreter::run_store_wrap::<i64, i32>(context, offset, align),
|
&Opcode::I64Store32(align, offset) => Interpreter::run_store_wrap::<i64, i32>(context, align, offset),
|
||||||
|
|
||||||
&Opcode::CurrentMemory(_) => Interpreter::run_current_memory(context),
|
&Opcode::CurrentMemory(_) => Interpreter::run_current_memory(context),
|
||||||
&Opcode::GrowMemory(_) => Interpreter::run_grow_memory(context),
|
&Opcode::GrowMemory(_) => Interpreter::run_grow_memory(context),
|
||||||
|
@ -44,6 +44,14 @@ impl<T> StackWithLimit<T> where T: Clone {
|
|||||||
.ok_or(Error::Stack("non-empty stack expected".into()))
|
.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> {
|
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||||
if self.values.len() >= self.limit {
|
if self.values.len() >= self.limit {
|
||||||
return Err(Error::Stack(format!("exceeded stack limit {}", self.limit)));
|
return Err(Error::Stack(format!("exceeded stack limit {}", self.limit)));
|
||||||
|
@ -117,7 +117,7 @@ fn global_get_set() {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
let program = ProgramInstance::new().unwrap();
|
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(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(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()));
|
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()));
|
||||||
|
607
src/interpreter/validator.rs
Normal file
607
src/interpreter/validator.rs
Normal file
@ -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<ValueType>,
|
||||||
|
/// Frame stack.
|
||||||
|
frame_stack: StackWithLimit<(BlockType, usize)>,
|
||||||
|
/// Function return type. None if validating expression.
|
||||||
|
return_type: Option<BlockType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
context.push_value(value_type)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type)?;
|
||||||
|
context.push_value(value_type)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type)?;
|
||||||
|
context.push_value(ValueType::I32)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(value_type1)?;
|
||||||
|
context.push_value(value_type2)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_any_value().map(|_| ())?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
Validator::validate_block(context, block_type, body, Opcode::End)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType, body: &[Opcode]) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
context.require_label(idx)?;
|
||||||
|
Ok(InstructionOutcome::Unreachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
context.pop_value(ValueType::I32)?;
|
||||||
|
context.require_label(idx)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_br_table(context: &mut FunctionValidationContext, table: &Vec<u32>, default: u32) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
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<InstructionOutcome, Error> {
|
||||||
|
context.require_memory(DEFAULT_MEMORY_INDEX)?;
|
||||||
|
context.push_value(ValueType::I32)?;
|
||||||
|
Ok(InstructionOutcome::RunNextInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
|
||||||
|
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<ValueType, Error> {
|
||||||
|
self.value_stack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tee_any_value(&mut self) -> Result<ValueType, Error> {
|
||||||
|
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<Option<ValueType>, 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<BlockType, Error> {
|
||||||
|
self.frame_stack.get(idx as usize).map(|&(block_type, _)| block_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn return_type(&self) -> Result<BlockType, Error> {
|
||||||
|
self.return_type.ok_or(Error::Validation("Trying to return from expression".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_local(&self, idx: u32) -> Result<ValueType, Error> {
|
||||||
|
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<bool>) -> Result<ValueType, Error> {
|
||||||
|
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<ValueType>, 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<ValueType>, 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))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user