Don't expand locals. (#86)
This commit is contained in:
parent
5cda9a05da
commit
730c918a80
|
@ -17,6 +17,7 @@ memory_units = "0.3.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wabt = "~0.2.2"
|
wabt = "~0.2.2"
|
||||||
|
assert_matches = "1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# 32-bit platforms are not supported and not tested. Use this flag if you really want to use
|
# 32-bit platforms are not supported and not tested. Use this flag if you really want to use
|
||||||
|
|
|
@ -98,6 +98,10 @@
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate wabt;
|
extern crate wabt;
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
extern crate parity_wasm;
|
extern crate parity_wasm;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate memory_units as memory_units_crate;
|
extern crate memory_units as memory_units_crate;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::u32;
|
use std::u32;
|
||||||
use std::iter::repeat;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use validation::context::ModuleContext;
|
use validation::context::ModuleContext;
|
||||||
|
|
||||||
use validation::Error;
|
use validation::Error;
|
||||||
|
use validation::util::Locals;
|
||||||
|
|
||||||
use common::stack::StackWithLimit;
|
use common::stack::StackWithLimit;
|
||||||
use common::{BlockFrame, BlockFrameType};
|
use common::{BlockFrame, BlockFrameType};
|
||||||
|
@ -22,7 +22,7 @@ struct FunctionValidationContext<'a> {
|
||||||
/// Current instruction position.
|
/// Current instruction position.
|
||||||
position: usize,
|
position: usize,
|
||||||
/// Local variables.
|
/// Local variables.
|
||||||
locals: &'a [ValueType],
|
locals: Locals<'a>,
|
||||||
/// Value stack.
|
/// Value stack.
|
||||||
value_stack: StackWithLimit<StackValueType>,
|
value_stack: StackWithLimit<StackValueType>,
|
||||||
/// Frame stack.
|
/// Frame stack.
|
||||||
|
@ -62,19 +62,9 @@ impl Validator {
|
||||||
) -> Result<HashMap<usize, usize>, Error> {
|
) -> Result<HashMap<usize, usize>, Error> {
|
||||||
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
let (params, result_ty) = module.require_function_type(func.type_ref())?;
|
||||||
|
|
||||||
// locals = (params + vars)
|
|
||||||
let mut locals = params.to_vec();
|
|
||||||
locals.extend(
|
|
||||||
body.locals()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|l| repeat(l.value_type())
|
|
||||||
.take(l.count() as usize)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut context = FunctionValidationContext::new(
|
let mut context = FunctionValidationContext::new(
|
||||||
&module,
|
&module,
|
||||||
&locals,
|
Locals::new(params, body.locals()),
|
||||||
DEFAULT_VALUE_STACK_LIMIT,
|
DEFAULT_VALUE_STACK_LIMIT,
|
||||||
DEFAULT_FRAME_STACK_LIMIT,
|
DEFAULT_FRAME_STACK_LIMIT,
|
||||||
result_ty,
|
result_ty,
|
||||||
|
@ -585,7 +575,7 @@ impl Validator {
|
||||||
impl<'a> FunctionValidationContext<'a> {
|
impl<'a> FunctionValidationContext<'a> {
|
||||||
fn new(
|
fn new(
|
||||||
module: &'a ModuleContext,
|
module: &'a ModuleContext,
|
||||||
locals: &'a [ValueType],
|
locals: Locals<'a>,
|
||||||
value_stack_limit: usize,
|
value_stack_limit: usize,
|
||||||
frame_stack_limit: usize,
|
frame_stack_limit: usize,
|
||||||
return_type: BlockType,
|
return_type: BlockType,
|
||||||
|
@ -707,10 +697,7 @@ impl<'a> FunctionValidationContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
|
||||||
self.locals.get(idx as usize)
|
Ok(self.locals.type_of_local(idx).map(StackValueType::from)?)
|
||||||
.cloned()
|
|
||||||
.map(Into::into)
|
|
||||||
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_labels(self) -> HashMap<usize, usize> {
|
fn into_labels(self) -> HashMap<usize, usize> {
|
||||||
|
|
|
@ -12,6 +12,7 @@ use memory_units::Pages;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod func;
|
mod func;
|
||||||
|
mod util;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
use parity_wasm::elements::{Local, ValueType};
|
||||||
|
use validation::Error;
|
||||||
|
|
||||||
|
/// Locals are the concatenation of a slice of function parameters
|
||||||
|
/// with function declared local variables.
|
||||||
|
///
|
||||||
|
/// Local variables are given in the form of groups represented by pairs
|
||||||
|
/// of a value_type and a count.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Locals<'a> {
|
||||||
|
params: &'a [ValueType],
|
||||||
|
local_groups: &'a [Local],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Locals<'a> {
|
||||||
|
pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Locals<'a> {
|
||||||
|
Locals {
|
||||||
|
params,
|
||||||
|
local_groups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type of a local variable (either a declared local or a param).
|
||||||
|
///
|
||||||
|
/// Returns `Err` in the case of overflow or when idx falls out of range.
|
||||||
|
pub fn type_of_local(&self, idx: u32) -> Result<ValueType, Error> {
|
||||||
|
if let Some(param) = self.params.get(idx as usize) {
|
||||||
|
return Ok(*param);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an index doesn't point to a param, then we have to look into local declarations.
|
||||||
|
let mut start_idx = self.params.len() as u32;
|
||||||
|
for locals_group in self.local_groups {
|
||||||
|
let end_idx = start_idx
|
||||||
|
.checked_add(locals_group.count())
|
||||||
|
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
|
||||||
|
|
||||||
|
if idx >= start_idx && idx < end_idx {
|
||||||
|
return Ok(locals_group.value_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
start_idx = end_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We didn't find anything, that's an error.
|
||||||
|
// At this moment `start_idx` should hold the count of all locals
|
||||||
|
// (since it's either set to the `end_idx` or equal to `params.len()`)
|
||||||
|
let total_count = start_idx;
|
||||||
|
|
||||||
|
Err(Error(format!(
|
||||||
|
"Trying to access local with index {} when there are only {} locals",
|
||||||
|
idx, total_count
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locals_it_works() {
|
||||||
|
let params = vec![ValueType::I32, ValueType::I64];
|
||||||
|
let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)];
|
||||||
|
let locals = Locals::new(¶ms, &local_groups);
|
||||||
|
|
||||||
|
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||||
|
assert_matches!(locals.type_of_local(1), Ok(ValueType::I64));
|
||||||
|
assert_matches!(locals.type_of_local(2), Ok(ValueType::F32));
|
||||||
|
assert_matches!(locals.type_of_local(3), Ok(ValueType::F32));
|
||||||
|
assert_matches!(locals.type_of_local(4), Ok(ValueType::F64));
|
||||||
|
assert_matches!(locals.type_of_local(5), Ok(ValueType::F64));
|
||||||
|
assert_matches!(locals.type_of_local(6), Err(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locals_no_declared_locals() {
|
||||||
|
let params = vec![ValueType::I32];
|
||||||
|
let locals = Locals::new(¶ms, &[]);
|
||||||
|
|
||||||
|
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||||
|
assert_matches!(locals.type_of_local(1), Err(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locals_no_params() {
|
||||||
|
let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)];
|
||||||
|
let locals = Locals::new(&[], &local_groups);
|
||||||
|
|
||||||
|
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
|
||||||
|
assert_matches!(locals.type_of_local(1), Ok(ValueType::I32));
|
||||||
|
assert_matches!(locals.type_of_local(2), Ok(ValueType::I64));
|
||||||
|
assert_matches!(locals.type_of_local(3), Ok(ValueType::I64));
|
||||||
|
assert_matches!(locals.type_of_local(4), Ok(ValueType::I64));
|
||||||
|
assert_matches!(locals.type_of_local(5), Err(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn locals_u32_overflow() {
|
||||||
|
let local_groups = vec![
|
||||||
|
Local::new(u32::max_value(), ValueType::I32),
|
||||||
|
Local::new(1, ValueType::I64),
|
||||||
|
];
|
||||||
|
let locals = Locals::new(&[], &local_groups);
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
locals.type_of_local(u32::max_value() - 1),
|
||||||
|
Ok(ValueType::I32)
|
||||||
|
);
|
||||||
|
assert_matches!(locals.type_of_local(u32::max_value()), Err(_));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue