Compare commits

...

55 Commits

Author SHA1 Message Date
NikVolf 29c771cf70 show match 2019-04-18 19:43:00 +03:00
Sergey Pepyakin e8cc132972 use checked sub instead of - 2019-04-18 18:01:33 +02:00
Sergey Pepyakin 843a40d888 Propagate error if frame stack overflown on create 2019-04-18 17:53:12 +02:00
Sergey Pepyakin 2a058136b9 Get rid of unreachable in StackValueType 2019-04-18 17:38:59 +02:00
Sergey Pepyakin fb6638c163 Add better proof 2019-04-18 17:29:42 +02:00
Sergey Pepyakin 28456e1163 Proofs 2019-04-18 17:26:26 +02:00
Sergey Pepyakin 0dc908c81b fmt 2019-04-18 15:11:23 +02:00
Sergey Pepyakin 7d1d6efe38 fmt. 2019-04-18 14:59:06 +02:00
Sergey Pepyakin 8a654554d1 s/with_instruction_capacity/with_capacity 2019-04-17 18:20:46 +02:00
Sergey Pepyakin cd34cc6afb Print value that can't be coerced to u32 2019-04-17 18:19:18 +02:00
Sergey Pepyakin ddbeba0154 Remove redundant PartialEq 2019-04-17 18:15:52 +02:00
Sergey Pepyakin 65e8400e56 Comment access to require_target 2019-04-16 16:47:45 +02:00
Sergey Pepyakin 36e671bc31 Remove another TODO 2019-04-16 16:36:16 +02:00
Sergey Pepyakin 88c48aa1ea Add comment about top_label safety 2019-04-16 16:35:16 +02:00
Sergey Pepyakin d5a383a442 Clean and detail End opcode. 2019-04-16 16:30:41 +02:00
Sergey Pepyakin 921dda469e fmt. 2019-04-16 16:14:19 +02:00
Sergey Pepyakin 98570fc1d7 Estimate capacity. 2019-04-16 16:14:10 +02:00
Sergey Pepyakin e7381bfdde Clean. 2019-04-16 16:14:00 +02:00
Sergey Pepyakin e4dcf553a2 Rename. 2019-04-16 14:39:34 +02:00
Sergey Pepyakin fc36931c06 Get rid of memory_units dependency in validation 2019-04-16 14:27:05 +02:00
Sergey Pepyakin 9935df3307 Renamings. 2019-04-15 20:17:44 +02:00
Sergey Pepyakin 0b11d665aa Make wasmi compilation tests work 2019-04-15 20:02:55 +02:00
Sergey Pepyakin 584b1fd2e9 Make validation tests work 2019-04-15 19:43:51 +02:00
Sergey Pepyakin e78f8ad37e Rename validate_module2 → validate_module 2019-04-15 19:00:32 +02:00
Sergey Pepyakin 44acdd6eb7 Move deny_floating_point to wasmi 2019-04-15 18:57:30 +02:00
Sergey Pepyakin 321f0b765c Make it work under no_std 2019-04-15 17:45:54 +02:00
Sergey Pepyakin 379e960c94 Clean. 2019-04-15 17:35:29 +02:00
Sergey Pepyakin cf0f8b4ad6 Fix warnings. 2019-04-15 17:34:56 +02:00
Sergey Pepyakin 3cc5dd6485 Format it. 2019-04-15 17:34:09 +02:00
Sergey Pepyakin 1dad287999 Make it compile. 2019-04-15 17:33:50 +02:00
Sergey Pepyakin e167cbcb96 Make validation compile 2019-04-15 17:25:34 +02:00
Sergey Pepyakin baf60ac977 The great move of validation 2019-04-15 17:16:20 +02:00
Sergey Pepyakin f02a356b6b WIP 2019-04-15 17:15:48 +02:00
Sergey Pepyakin cd4948e37e Comments. 2019-04-09 18:10:28 +02:00
Sergey Pepyakin b7a94855d8 Move code under prepare 2019-04-08 16:20:21 +02:00
Sergey Pepyakin fc3d21a17a Express the compiler using validation trait 2019-04-08 15:41:44 +02:00
Sergey Pepyakin 9723dfbfb6 Add Validation traits 2019-04-08 14:49:53 +02:00
Sergey Pepyakin eaa030afa7 Move push_label under validation context. 2019-04-08 10:59:34 +02:00
Sergey Pepyakin 628815ac0f fmt 2019-04-05 23:56:55 +02:00
Sergey Pepyakin 05527b05db Rename to compiler. 2019-04-05 23:41:29 +02:00
Sergey Pepyakin b13c730604 Move sink to FunctionReader 2019-04-05 23:36:21 +02:00
Sergey Pepyakin 33125dd00f Validation separated from compilation. 2019-04-05 23:21:43 +02:00
Sergey Pepyakin a4b7140c0e Refactoring cleaning 2019-04-05 23:19:39 +02:00
Sergey Pepyakin 382d68d4b4 Extract compilation 2019-04-05 18:42:58 +02:00
Sergey Pepyakin 30c738c571 Finally get rid from frame_type. 2019-04-05 14:30:31 +02:00
Sergey Pepyakin ff379cb950 Avoid using frame_type. 2019-04-05 14:29:43 +02:00
Sergey Pepyakin 57251eec79 Mirror label_stack. 2019-04-05 14:17:15 +02:00
Sergey Pepyakin 4e2347b4bf Actually use started_with. 2019-04-05 14:10:40 +02:00
Sergey Pepyakin 5c183c4da0 Introduce StartedWith 2019-04-05 13:38:12 +02:00
Sergey Pepyakin 0994150ba2 Get rid of outcome 2019-04-05 13:25:17 +02:00
Sergey Pepyakin d813c912a5 Rework.
Now we will a compiler which wraps and uses info from a evaluation simulator.
2019-04-05 13:22:33 +02:00
Sergey Pepyakin 58e4722a39 Attempt number 10 2019-04-01 20:49:49 +02:00
Sergey Pepyakin 7868e77f8e Add comment about safety of top_label 2019-04-01 17:46:58 +02:00
Sergey Pepyakin cf0dee5c18 return_type isn't failable 2019-04-01 17:20:46 +02:00
Sergey Pepyakin 716e8b5613 Add some docs. 2019-04-01 17:20:14 +02:00
24 changed files with 3237 additions and 2445 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
**/*.rs.bk
Cargo.lock
spec/target
.idea

View File

@ -10,15 +10,8 @@ description = "WebAssembly interpreter"
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
[features]
default = ["std"]
# Disable for no_std support
std = ["parity-wasm/std"]
# Enable for no_std support
# hashbrown only works on no_std
core = ["hashbrown/nightly", "libm"]
[dependencies]
wasmi-validation = { path = "validation", default-features = false }
parity-wasm = { version = "0.31", default-features = false }
hashbrown = { version = "0.1.8", optional = true }
memory_units = "0.3.0"
@ -28,3 +21,21 @@ libm = { version = "0.1.2", optional = true }
assert_matches = "1.1"
rand = "0.4.2"
wabt = "0.6"
[features]
default = ["std"]
# Disable for no_std support
std = [
"parity-wasm/std",
"wasmi-validation/std",
]
# Enable for no_std support
# hashbrown only works on no_std
core = [
"wasmi-validation/core",
"hashbrown/nightly",
"libm"
]
[workspace]
members = ["validation"]

View File

@ -1,8 +0,0 @@
pub mod stack;
/// Index of default linear memory.
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
// TODO: Move BlockFrame under validation.

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use alloc::rc::{Rc, Weak};
use core::fmt;
use host::Externals;

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;

View File

@ -68,7 +68,7 @@
//!
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
/// Should we keep a value before "discarding" a stack frame?
///

View File

@ -97,7 +97,7 @@
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
//// alloc is required in no_std
#![cfg_attr(not(feature = "std"), feature(alloc))]
#![cfg_attr(not(feature = "std"), feature(alloc, alloc_prelude))]
#[cfg(not(feature = "std"))]
#[macro_use]
@ -110,18 +110,19 @@ extern crate std as alloc;
extern crate core;
#[cfg(test)]
extern crate wabt;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
#[cfg(test)]
extern crate wabt;
#[cfg(not(feature = "std"))]
extern crate hashbrown;
extern crate memory_units as memory_units_crate;
extern crate parity_wasm;
extern crate wasmi_validation as validation;
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use core::fmt;
#[cfg(feature = "std")]
use std::error;
@ -380,7 +381,6 @@ impl From<validation::Error> for Error {
}
}
mod common;
mod func;
mod global;
mod host;
@ -389,10 +389,10 @@ mod isa;
mod memory;
mod module;
pub mod nan_preserving_float;
mod prepare;
mod runner;
mod table;
mod types;
mod validation;
mod value;
#[cfg(test)]
@ -454,8 +454,7 @@ impl Module {
/// }
/// ```
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule};
let ValidatedModule { code_map, module } = validate_module(module)?;
let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?;
Ok(Module { code_map, module })
}
@ -517,7 +516,7 @@ impl Module {
/// assert!(module.deny_floating_point().is_err());
/// ```
pub fn deny_floating_point(&self) -> Result<(), Error> {
validation::deny_floating_point(&self.module).map_err(Into::into)
prepare::deny_floating_point(&self.module).map_err(Into::into)
}
/// Create `Module` from a given buffer.

View File

@ -1,11 +1,12 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use alloc::rc::Rc;
use core::cell::{Cell, RefCell};
use core::cmp;
use core::fmt;
use core::ops::Range;
use core::u32;
use core::{
cell::{Cell, RefCell},
cmp, fmt,
ops::Range,
u32,
};
use memory_units::{Bytes, Pages, RoundUpTo};
use parity_wasm::elements::ResizableLimits;
use value::LittleEndianConvert;
@ -18,9 +19,6 @@ use Error;
/// [`MemoryInstance`]: struct.MemoryInstance.html
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
/// Maximal number of pages.
const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536);
/// Reference to a memory (See [`MemoryInstance`] for details).
///
/// This reference has a reference-counting semantics.
@ -111,7 +109,22 @@ impl MemoryInstance {
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
validate_memory(initial, maximum).map_err(Error::Memory)?;
{
use std::convert::TryInto;
let initial_u32: u32 = initial.0.try_into().map_err(|_| {
Error::Memory(format!("initial ({}) can't be coerced to u32", initial.0))
})?;
let maximum_u32: Option<u32> = match maximum {
Some(maximum_pages) => Some(maximum_pages.0.try_into().map_err(|_| {
Error::Memory(format!(
"maximum ({}) can't be coerced to u32",
maximum_pages.0
))
})?),
None => None,
};
validation::validate_memory(initial_u32, maximum_u32).map_err(Error::Memory)?;
}
let memory = MemoryInstance::new(initial, maximum);
Ok(MemoryRef(Rc::new(memory)))
@ -271,7 +284,9 @@ impl MemoryInstance {
}
let new_size: Pages = size_before_grow + additional;
let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES);
let maximum = self
.maximum
.unwrap_or(Pages(validation::LINEAR_MEMORY_MAX_PAGES as usize));
if new_size > maximum {
return Err(Error::Memory(format!(
"Trying to grow memory by {} pages when already have {}",
@ -549,31 +564,6 @@ impl MemoryInstance {
}
}
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
if initial > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"initial memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES.0
));
}
if let Some(maximum) = maximum {
if initial > maximum {
return Err(format!(
"maximum limit {} is less than minimum {}",
maximum.0, initial.0,
));
}
if maximum > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"maximum memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES.0
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::fmt;
@ -10,7 +10,6 @@ use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use core::cell::Ref;
use func::{FuncBody, FuncInstance, FuncRef};
use global::{GlobalInstance, GlobalRef};
@ -21,6 +20,7 @@ use memory_units::Pages;
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
use table::TableRef;
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
/// Reference to a [`ModuleInstance`].

View File

@ -153,9 +153,11 @@ mod tests {
use super::{F32, F64};
use core::fmt::Debug;
use core::iter;
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::{
fmt::Debug,
iter,
ops::{Add, Div, Mul, Neg, Sub},
};
fn test_ops<T, F, I>(iter: I)
where

1285
src/prepare/compile.rs Normal file

File diff suppressed because it is too large Load Diff

170
src/prepare/mod.rs Normal file
View File

@ -0,0 +1,170 @@
#[allow(unused_imports)]
use alloc::prelude::v1::*;
use crate::{
isa,
validation::{validate_module, Error, Validator},
};
use parity_wasm::elements::Module;
mod compile;
#[cfg(test)]
mod tests;
#[derive(Clone)]
pub struct CompiledModule {
pub code_map: Vec<isa::Instructions>,
pub module: Module,
}
pub struct WasmiValidation {
code_map: Vec<isa::Instructions>,
}
// This implementation of `Validation` is compiling wasm code at the
// validation time.
impl Validator for WasmiValidation {
type Output = Vec<isa::Instructions>;
type FuncValidator = compile::Compiler;
fn new(_module: &Module) -> Self {
WasmiValidation {
// TODO: with capacity?
code_map: Vec::new(),
}
}
fn on_function_validated(&mut self, _index: u32, output: isa::Instructions) {
self.code_map.push(output);
}
fn finish(self) -> Vec<isa::Instructions> {
self.code_map
}
}
/// Validate a module and compile it to the internal representation.
pub fn compile_module(module: Module) -> Result<CompiledModule, Error> {
let code_map = validate_module::<WasmiValidation>(&module)?;
Ok(CompiledModule { module, code_map })
}
/// Verify that the module doesn't use floating point instructions or types.
///
/// Returns `Err` if
///
/// - Any of function bodies uses a floating pointer instruction (an instruction that
/// consumes or produces a value of a floating point type)
/// - If a floating point type used in a definition of a function.
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
use parity_wasm::elements::{
Instruction::{self, *},
Type, ValueType,
};
if let Some(code) = module.code_section() {
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
macro_rules! match_eq {
($pattern:pat) => {
|val| if let $pattern = *val { true } else { false }
};
}
const DENIED: &[fn(&Instruction) -> bool] = &[
match_eq!(F32Load(_, _)),
match_eq!(F64Load(_, _)),
match_eq!(F32Store(_, _)),
match_eq!(F64Store(_, _)),
match_eq!(F32Const(_)),
match_eq!(F64Const(_)),
match_eq!(F32Eq),
match_eq!(F32Ne),
match_eq!(F32Lt),
match_eq!(F32Gt),
match_eq!(F32Le),
match_eq!(F32Ge),
match_eq!(F64Eq),
match_eq!(F64Ne),
match_eq!(F64Lt),
match_eq!(F64Gt),
match_eq!(F64Le),
match_eq!(F64Ge),
match_eq!(F32Abs),
match_eq!(F32Neg),
match_eq!(F32Ceil),
match_eq!(F32Floor),
match_eq!(F32Trunc),
match_eq!(F32Nearest),
match_eq!(F32Sqrt),
match_eq!(F32Add),
match_eq!(F32Sub),
match_eq!(F32Mul),
match_eq!(F32Div),
match_eq!(F32Min),
match_eq!(F32Max),
match_eq!(F32Copysign),
match_eq!(F64Abs),
match_eq!(F64Neg),
match_eq!(F64Ceil),
match_eq!(F64Floor),
match_eq!(F64Trunc),
match_eq!(F64Nearest),
match_eq!(F64Sqrt),
match_eq!(F64Add),
match_eq!(F64Sub),
match_eq!(F64Mul),
match_eq!(F64Div),
match_eq!(F64Min),
match_eq!(F64Max),
match_eq!(F64Copysign),
match_eq!(F32ConvertSI32),
match_eq!(F32ConvertUI32),
match_eq!(F32ConvertSI64),
match_eq!(F32ConvertUI64),
match_eq!(F32DemoteF64),
match_eq!(F64ConvertSI32),
match_eq!(F64ConvertUI32),
match_eq!(F64ConvertSI64),
match_eq!(F64ConvertUI64),
match_eq!(F64PromoteF32),
match_eq!(F32ReinterpretI32),
match_eq!(F64ReinterpretI64),
match_eq!(I32TruncSF32),
match_eq!(I32TruncUF32),
match_eq!(I32TruncSF64),
match_eq!(I32TruncUF64),
match_eq!(I64TruncSF32),
match_eq!(I64TruncUF32),
match_eq!(I64TruncSF64),
match_eq!(I64TruncUF64),
match_eq!(I32ReinterpretF32),
match_eq!(I64ReinterpretF64),
];
if DENIED.iter().any(|is_denied| is_denied(op)) {
return Err(Error(format!("Floating point operation denied: {:?}", op)));
}
}
}
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
let types = types.types();
for sig in sec.entries() {
if let Some(typ) = types.get(sig.type_ref() as usize) {
match *typ {
Type::Function(ref func) => {
if func
.params()
.iter()
.chain(func.return_type().as_ref())
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
{
return Err(Error(format!("Use of floating point types denied")));
}
}
}
}
}
}
Ok(())
}

View File

@ -1,285 +1,17 @@
use super::{validate_module, ValidatedModule};
use super::{compile_module, CompiledModule};
use parity_wasm::{deserialize_buffer, elements::Module};
use isa;
use parity_wasm::builder::module;
use parity_wasm::elements::{
deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr,
Instruction, Instructions, MemoryType, Module, TableType, ValueType,
};
use wabt;
#[test]
fn empty_is_valid() {
let module = module().build();
assert!(validate_module(module).is_ok());
}
#[test]
fn limits() {
let test_cases = vec![
// min > max
(10, Some(9), false),
// min = max
(10, Some(10), true),
// table/memory is always valid without max
(10, None, true),
];
for (min, max, is_valid) in test_cases {
// defined table
let m = module().table().with_min(min).with_max(max).build().build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max)),
))
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// defined memory
let m = module()
.memory()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max)),
))
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
}
}
#[test]
fn global_init_const() {
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(m).is_ok());
// init expr type differs from declared global type
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn global_init_global() {
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(m).is_ok());
// get_global can reference only previously defined globals
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
// get_global can reference only const globals
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
// get_global in init_expr can only refer to imported globals.
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn global_init_misc() {
// without delimiting End opcode
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42)]),
))
.build();
assert!(validate_module(m).is_err());
// empty init expr
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
// not an constant opcode used
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
))
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn module_limits_validity() {
// module cannot contain more than 1 memory atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None)),
))
.memory()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
// module cannot contain more than 1 table atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None)),
))
.table()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn funcs() {
// recursive function calls is legal.
let m = module()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(1),
Instruction::End,
]))
.build()
.build()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(0),
Instruction::End,
]))
.build()
.build()
.build();
assert!(validate_module(m).is_ok());
}
#[test]
fn globals() {
// import immutable global is legal.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.build();
assert!(validate_module(m).is_ok());
// import mutable global is invalid.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn if_else_with_return_type_validation() {
let m = module()
.function()
.signature()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::I32Const(1),
Instruction::If(BlockType::NoResult),
Instruction::I32Const(1),
Instruction::If(BlockType::Value(ValueType::I32)),
Instruction::I32Const(1),
Instruction::Else,
Instruction::I32Const(2),
Instruction::End,
Instruction::Drop,
Instruction::End,
Instruction::End,
]))
.build()
.build()
.build();
validate_module(m).unwrap();
}
fn validate(wat: &str) -> ValidatedModule {
fn validate(wat: &str) -> CompiledModule {
let wasm = wabt::wat2wasm(wat).unwrap();
let module = deserialize_buffer::<Module>(&wasm).unwrap();
let validated_module = validate_module(module).unwrap();
validated_module
let compiled_module = compile_module(module).unwrap();
compiled_module
}
fn compile(module: &ValidatedModule) -> (Vec<isa::Instruction>, Vec<u32>) {
fn compile(module: &CompiledModule) -> (Vec<isa::Instruction>, Vec<u32>) {
let code = &module.code_map[0];
let mut instructions = Vec::new();
let mut pcs = Vec::new();
@ -806,6 +538,68 @@ fn loop_empty() {
)
}
#[test]
fn spec_as_br_if_value_cond() {
use self::isa::Instruction::*;
let module = validate(
r#"
(func (export "as-br_if-value-cond") (result i32)
(block (result i32)
(drop
(br_if 0
(i32.const 6)
(br_table 0 0
(i32.const 9)
(i32.const 0)
)
)
)
(i32.const 7)
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
I32Const(6),
I32Const(9),
I32Const(0),
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 1,
keep: isa::Keep::Single
}
},
isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 1,
keep: isa::Keep::Single
}
}
]),
BrIfNez(isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single
}
}),
Drop,
I32Const(7),
Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::Single
})
]
);
}
#[test]
fn brtable() {
let module = validate(

View File

@ -1,6 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use alloc::prelude::v1::*;
use core::fmt;
use core::ops;
use core::{u32, usize};
@ -12,6 +11,7 @@ use memory_units::Pages;
use module::ModuleRef;
use nan_preserving_float::{F32, F64};
use parity_wasm::elements::Local;
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use value::{
ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto,
TryTruncateInto, WrapInto,

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::fmt;

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,6 @@ set -eux
cd $(dirname $0)
time cargo test
time cargo test --all
cd -

19
validation/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "wasmi-validation"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
parity-wasm = { version = "0.31", default-features = false }
hashbrown = { version = "0.1.8", optional = true }
[dev-dependencies]
assert_matches = "1.1"
[features]
default = ["std"]
std = ["parity-wasm/std"]
core = [
"hashbrown/nightly"
]

View File

@ -1,9 +1,9 @@
use crate::Error;
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use parity_wasm::elements::{
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
};
use validation::Error;
#[derive(Default, Debug)]
pub struct ModuleContext {

1219
validation/src/func.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,28 @@
// TODO: Uncomment
// #![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
//// alloc is required in no_std
#![cfg_attr(not(feature = "std"), feature(alloc, alloc_prelude))]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
pub mod stack;
/// Index of default linear memory.
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
/// Maximal number of pages that a wasm instance supports.
pub const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use core::fmt;
#[cfg(feature = "std")]
use std::error;
@ -10,24 +33,22 @@ use hashbrown::HashSet;
use std::collections::HashSet;
use self::context::ModuleContextBuilder;
use self::func::FunctionReader;
use common::stack;
use isa;
use memory_units::Pages;
use parity_wasm::elements::{
BlockType, External, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, MemoryType,
Module, ResizableLimits, TableType, Type, ValueType,
BlockType, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction, Internal,
MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
};
mod context;
mod func;
mod util;
pub mod context;
pub mod func;
pub mod util;
#[cfg(test)]
mod tests;
// TODO: Consider using a type other than String, because
// of formatting machinary is not welcomed in substrate runtimes.
#[derive(Debug)]
pub struct Error(String);
pub struct Error(pub String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -48,137 +69,77 @@ impl From<stack::Error> for Error {
}
}
#[derive(Clone)]
pub struct ValidatedModule {
pub code_map: Vec<isa::Instructions>,
pub module: Module,
pub trait Validator {
type Output;
type FuncValidator: FuncValidator;
fn new(module: &Module) -> Self;
fn on_function_validated(
&mut self,
index: u32,
output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
);
fn finish(self) -> Self::Output;
}
impl ::core::ops::Deref for ValidatedModule {
type Target = Module;
fn deref(&self) -> &Module {
&self.module
pub trait FuncValidator {
type Output;
fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self;
fn next_instruction(
&mut self,
ctx: &mut func::FunctionValidationContext,
instruction: &Instruction,
) -> Result<(), Error>;
fn finish(self) -> Self::Output;
}
/// A module validator that just validates modules and produces no result.
pub struct PlainValidator;
impl Validator for PlainValidator {
type Output = ();
type FuncValidator = PlainFuncValidator;
fn new(_module: &Module) -> PlainValidator {
PlainValidator
}
fn on_function_validated(
&mut self,
_index: u32,
_output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
) -> () {
()
}
fn finish(self) -> () {
()
}
}
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
if let Some(code) = module.code_section() {
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
use parity_wasm::elements::Instruction::*;
/// A function validator that just validates modules and produces no result.
pub struct PlainFuncValidator;
macro_rules! match_eq {
($pattern:pat) => {
|val| if let $pattern = *val { true } else { false }
};
}
impl FuncValidator for PlainFuncValidator {
type Output = ();
const DENIED: &[fn(&Instruction) -> bool] = &[
match_eq!(F32Load(_, _)),
match_eq!(F64Load(_, _)),
match_eq!(F32Store(_, _)),
match_eq!(F64Store(_, _)),
match_eq!(F32Const(_)),
match_eq!(F64Const(_)),
match_eq!(F32Eq),
match_eq!(F32Ne),
match_eq!(F32Lt),
match_eq!(F32Gt),
match_eq!(F32Le),
match_eq!(F32Ge),
match_eq!(F64Eq),
match_eq!(F64Ne),
match_eq!(F64Lt),
match_eq!(F64Gt),
match_eq!(F64Le),
match_eq!(F64Ge),
match_eq!(F32Abs),
match_eq!(F32Neg),
match_eq!(F32Ceil),
match_eq!(F32Floor),
match_eq!(F32Trunc),
match_eq!(F32Nearest),
match_eq!(F32Sqrt),
match_eq!(F32Add),
match_eq!(F32Sub),
match_eq!(F32Mul),
match_eq!(F32Div),
match_eq!(F32Min),
match_eq!(F32Max),
match_eq!(F32Copysign),
match_eq!(F64Abs),
match_eq!(F64Neg),
match_eq!(F64Ceil),
match_eq!(F64Floor),
match_eq!(F64Trunc),
match_eq!(F64Nearest),
match_eq!(F64Sqrt),
match_eq!(F64Add),
match_eq!(F64Sub),
match_eq!(F64Mul),
match_eq!(F64Div),
match_eq!(F64Min),
match_eq!(F64Max),
match_eq!(F64Copysign),
match_eq!(F32ConvertSI32),
match_eq!(F32ConvertUI32),
match_eq!(F32ConvertSI64),
match_eq!(F32ConvertUI64),
match_eq!(F32DemoteF64),
match_eq!(F64ConvertSI32),
match_eq!(F64ConvertUI32),
match_eq!(F64ConvertSI64),
match_eq!(F64ConvertUI64),
match_eq!(F64PromoteF32),
match_eq!(F32ReinterpretI32),
match_eq!(F64ReinterpretI64),
match_eq!(I32TruncSF32),
match_eq!(I32TruncUF32),
match_eq!(I32TruncSF64),
match_eq!(I32TruncUF64),
match_eq!(I64TruncSF32),
match_eq!(I64TruncUF32),
match_eq!(I64TruncSF64),
match_eq!(I64TruncUF64),
match_eq!(I32ReinterpretF32),
match_eq!(I64ReinterpretF64),
];
if DENIED.iter().any(|is_denied| is_denied(op)) {
return Err(Error(format!("Floating point operation denied: {:?}", op)));
}
}
fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator {
PlainFuncValidator
}
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
use parity_wasm::elements::{Type, ValueType};
let types = types.types();
for sig in sec.entries() {
if let Some(typ) = types.get(sig.type_ref() as usize) {
match *typ {
Type::Function(ref func) => {
if func
.params()
.iter()
.chain(func.return_type().as_ref())
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
{
return Err(Error(format!("Use of floating point types denied")));
}
}
}
}
}
fn next_instruction(
&mut self,
ctx: &mut func::FunctionValidationContext,
instruction: &Instruction,
) -> Result<(), Error> {
ctx.step(instruction)
}
Ok(())
fn finish(self) -> () {
()
}
}
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();
let mut code_map = Vec::new();
let mut validation = V::new(&module);
// Copy types from module as is.
context_builder.set_types(
@ -265,15 +226,15 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
.bodies()
.get(index as usize)
.ok_or(Error(format!("Missing body for function {}", index)))?;
let code =
FunctionReader::read_function(&context, function, function_body).map_err(|e| {
let Error(ref msg) = e;
let output = func::drive::<V::FuncValidator>(&context, function, function_body)
.map_err(|Error(ref msg)| {
Error(format!(
"Function #{} reading/validation error: {}",
index, msg
))
})?;
code_map.push(code);
validation.on_function_validated(index as u32, output);
}
}
@ -381,7 +342,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
}
}
Ok(ValidatedModule { module, code_map })
Ok(validation.finish())
}
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
@ -398,9 +359,34 @@ fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
}
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
let initial: Pages = Pages(memory_type.limits().initial() as usize);
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
::memory::validate_memory(initial, maximum).map_err(Error)
let initial = memory_type.limits().initial();
let maximum: Option<u32> = memory_type.limits().maximum();
validate_memory(initial, maximum).map_err(Error)
}
pub fn validate_memory(initial: u32, maximum: Option<u32>) -> Result<(), String> {
if initial > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"initial memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES
));
}
if let Some(maximum) = maximum {
if initial > maximum {
return Err(format!(
"maximum limit {} is less than minimum {}",
maximum, initial,
));
}
if maximum > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"maximum memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES
));
}
}
Ok(())
}
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use core::fmt;
#[cfg(feature = "std")]

277
validation/src/tests.rs Normal file
View File

@ -0,0 +1,277 @@
use crate::{Error, PlainValidator};
use parity_wasm::{
builder::module,
elements::{
BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction,
Instructions, MemoryType, Module, TableType, ValueType,
},
};
fn validate_module(module: &Module) -> Result<(), Error> {
super::validate_module::<PlainValidator>(module)
}
#[test]
fn empty_is_valid() {
let module = module().build();
assert!(validate_module(&module).is_ok());
}
#[test]
fn limits() {
let test_cases = vec![
// min > max
(10, Some(9), false),
// min = max
(10, Some(10), true),
// table/memory is always valid without max
(10, None, true),
];
for (min, max, is_valid) in test_cases {
// defined table
let m = module().table().with_min(min).with_max(max).build().build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max)),
))
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// defined memory
let m = module()
.memory()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max)),
))
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
}
}
#[test]
fn global_init_const() {
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_ok());
// init expr type differs from declared global type
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn global_init_global() {
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_ok());
// get_global can reference only previously defined globals
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// get_global can reference only const globals
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// get_global in init_expr can only refer to imported globals.
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn global_init_misc() {
// without delimiting End opcode
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42)]),
))
.build();
assert!(validate_module(&m).is_err());
// empty init expr
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// not an constant opcode used
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn module_limits_validity() {
// module cannot contain more than 1 memory atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None)),
))
.memory()
.with_min(10)
.build()
.build();
assert!(validate_module(&m).is_err());
// module cannot contain more than 1 table atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None)),
))
.table()
.with_min(10)
.build()
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn funcs() {
// recursive function calls is legal.
let m = module()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(1),
Instruction::End,
]))
.build()
.build()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(0),
Instruction::End,
]))
.build()
.build()
.build();
assert!(validate_module(&m).is_ok());
}
#[test]
fn globals() {
// import immutable global is legal.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.build();
assert!(validate_module(&m).is_ok());
// import mutable global is invalid.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn if_else_with_return_type_validation() {
let m = module()
.function()
.signature()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::I32Const(1),
Instruction::If(BlockType::NoResult),
Instruction::I32Const(1),
Instruction::If(BlockType::Value(ValueType::I32)),
Instruction::I32Const(1),
Instruction::Else,
Instruction::I32Const(2),
Instruction::End,
Instruction::Drop,
Instruction::End,
Instruction::End,
]))
.build()
.build()
.build();
validate_module(&m).unwrap();
}

View File

@ -1,7 +1,10 @@
use crate::Error;
#[allow(unused_imports)]
use alloc::prelude::*;
use alloc::prelude::v1::*;
use parity_wasm::elements::{Local, ValueType};
use validation::Error;
#[cfg(test)]
use assert_matches::assert_matches;
/// Locals are the concatenation of a slice of function parameters
/// with function declared local variables.