Extract validation into a separate crate (#176)
* Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
This commit is contained in:
parent
0267b20e6e
commit
a3aad8a549
27
Cargo.toml
27
Cargo.toml
|
@ -10,15 +10,8 @@ description = "WebAssembly interpreter"
|
||||||
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
|
||||||
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
|
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]
|
[dependencies]
|
||||||
|
wasmi-validation = { path = "validation", default-features = false }
|
||||||
parity-wasm = { version = "0.31", default-features = false }
|
parity-wasm = { version = "0.31", default-features = false }
|
||||||
hashbrown = { version = "0.1.8", optional = true }
|
hashbrown = { version = "0.1.8", optional = true }
|
||||||
memory_units = "0.3.0"
|
memory_units = "0.3.0"
|
||||||
|
@ -28,3 +21,21 @@ libm = { version = "0.1.2", optional = true }
|
||||||
assert_matches = "1.1"
|
assert_matches = "1.1"
|
||||||
rand = "0.4.2"
|
rand = "0.4.2"
|
||||||
wabt = "0.6"
|
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"]
|
||||||
|
|
|
@ -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.
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use alloc::rc::{Rc, Weak};
|
use alloc::rc::{Rc, Weak};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use host::Externals;
|
use host::Externals;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
12
src/isa.rs
12
src/isa.rs
|
@ -68,7 +68,7 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
|
|
||||||
/// Should we keep a value before "discarding" a stack frame?
|
/// Should we keep a value before "discarding" a stack frame?
|
||||||
///
|
///
|
||||||
|
@ -82,6 +82,16 @@ pub enum Keep {
|
||||||
Single,
|
Single,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Keep {
|
||||||
|
/// Reutrns a number of items that should be kept on the stack.
|
||||||
|
pub fn count(&self) -> u32 {
|
||||||
|
match *self {
|
||||||
|
Keep::None => 0,
|
||||||
|
Keep::Single => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specifies how many values we should keep and how many we should drop.
|
/// Specifies how many values we should keep and how many we should drop.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct DropKeep {
|
pub struct DropKeep {
|
||||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -97,7 +97,7 @@
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
//// alloc is required in 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"))]
|
#[cfg(not(feature = "std"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -110,18 +110,19 @@ extern crate std as alloc;
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate wabt;
|
|
||||||
#[cfg(test)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate assert_matches;
|
extern crate assert_matches;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate wabt;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
extern crate hashbrown;
|
extern crate hashbrown;
|
||||||
extern crate memory_units as memory_units_crate;
|
extern crate memory_units as memory_units_crate;
|
||||||
extern crate parity_wasm;
|
extern crate parity_wasm;
|
||||||
|
|
||||||
|
extern crate wasmi_validation as validation;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::error;
|
use std::error;
|
||||||
|
@ -380,7 +381,6 @@ impl From<validation::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod common;
|
|
||||||
mod func;
|
mod func;
|
||||||
mod global;
|
mod global;
|
||||||
mod host;
|
mod host;
|
||||||
|
@ -389,10 +389,10 @@ mod isa;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod module;
|
mod module;
|
||||||
pub mod nan_preserving_float;
|
pub mod nan_preserving_float;
|
||||||
|
mod prepare;
|
||||||
mod runner;
|
mod runner;
|
||||||
mod table;
|
mod table;
|
||||||
mod types;
|
mod types;
|
||||||
mod validation;
|
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -454,8 +454,7 @@ impl Module {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
|
||||||
use validation::{validate_module, ValidatedModule};
|
let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?;
|
||||||
let ValidatedModule { code_map, module } = validate_module(module)?;
|
|
||||||
|
|
||||||
Ok(Module { code_map, module })
|
Ok(Module { code_map, module })
|
||||||
}
|
}
|
||||||
|
@ -517,7 +516,7 @@ impl Module {
|
||||||
/// assert!(module.deny_floating_point().is_err());
|
/// assert!(module.deny_floating_point().is_err());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn deny_floating_point(&self) -> Result<(), Error> {
|
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.
|
/// Create `Module` from a given buffer.
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use core::cell::{Cell, RefCell};
|
use core::{
|
||||||
use core::cmp;
|
cell::{Cell, RefCell},
|
||||||
use core::fmt;
|
cmp, fmt,
|
||||||
use core::ops::Range;
|
ops::Range,
|
||||||
use core::u32;
|
u32,
|
||||||
|
};
|
||||||
use memory_units::{Bytes, Pages, RoundUpTo};
|
use memory_units::{Bytes, Pages, RoundUpTo};
|
||||||
use parity_wasm::elements::ResizableLimits;
|
use parity_wasm::elements::ResizableLimits;
|
||||||
use value::LittleEndianConvert;
|
use value::LittleEndianConvert;
|
||||||
|
@ -18,9 +19,6 @@ use Error;
|
||||||
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
/// [`MemoryInstance`]: struct.MemoryInstance.html
|
||||||
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
|
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).
|
/// Reference to a memory (See [`MemoryInstance`] for details).
|
||||||
///
|
///
|
||||||
/// This reference has a reference-counting semantics.
|
/// This reference has a reference-counting semantics.
|
||||||
|
@ -111,7 +109,22 @@ impl MemoryInstance {
|
||||||
///
|
///
|
||||||
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
|
||||||
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
|
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);
|
let memory = MemoryInstance::new(initial, maximum);
|
||||||
Ok(MemoryRef(Rc::new(memory)))
|
Ok(MemoryRef(Rc::new(memory)))
|
||||||
|
@ -271,7 +284,9 @@ impl MemoryInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_size: Pages = size_before_grow + additional;
|
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 {
|
if new_size > maximum {
|
||||||
return Err(Error::Memory(format!(
|
return Err(Error::Memory(format!(
|
||||||
"Trying to grow memory by {} pages when already have {}",
|
"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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
@ -10,7 +10,6 @@ use hashbrown::HashMap;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use core::cell::Ref;
|
use core::cell::Ref;
|
||||||
use func::{FuncBody, FuncInstance, FuncRef};
|
use func::{FuncBody, FuncInstance, FuncRef};
|
||||||
use global::{GlobalInstance, GlobalRef};
|
use global::{GlobalInstance, GlobalRef};
|
||||||
|
@ -21,6 +20,7 @@ use memory_units::Pages;
|
||||||
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
|
use parity_wasm::elements::{External, InitExpr, Instruction, Internal, ResizableLimits, Type};
|
||||||
use table::TableRef;
|
use table::TableRef;
|
||||||
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
|
||||||
|
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
|
use {Error, MemoryInstance, Module, RuntimeValue, Signature, TableInstance};
|
||||||
|
|
||||||
/// Reference to a [`ModuleInstance`].
|
/// Reference to a [`ModuleInstance`].
|
||||||
|
|
|
@ -153,9 +153,11 @@ mod tests {
|
||||||
|
|
||||||
use super::{F32, F64};
|
use super::{F32, F64};
|
||||||
|
|
||||||
use core::fmt::Debug;
|
use core::{
|
||||||
use core::iter;
|
fmt::Debug,
|
||||||
use core::ops::{Add, Div, Mul, Neg, Sub};
|
iter,
|
||||||
|
ops::{Add, Div, Mul, Neg, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
fn test_ops<T, F, I>(iter: I)
|
fn test_ops<T, F, I>(iter: I)
|
||||||
where
|
where
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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(())
|
||||||
|
}
|
|
@ -1,285 +1,17 @@
|
||||||
use super::{validate_module, ValidatedModule};
|
use super::{compile_module, CompiledModule};
|
||||||
|
use parity_wasm::{deserialize_buffer, elements::Module};
|
||||||
|
|
||||||
use isa;
|
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;
|
use wabt;
|
||||||
|
|
||||||
#[test]
|
fn validate(wat: &str) -> CompiledModule {
|
||||||
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 {
|
|
||||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||||
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||||
let validated_module = validate_module(module).unwrap();
|
let compiled_module = compile_module(module).unwrap();
|
||||||
validated_module
|
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 code = &module.code_map[0];
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
let mut pcs = 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]
|
#[test]
|
||||||
fn brtable() {
|
fn brtable() {
|
||||||
let module = validate(
|
let module = validate(
|
|
@ -1,6 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops;
|
use core::ops;
|
||||||
use core::{u32, usize};
|
use core::{u32, usize};
|
||||||
|
@ -12,6 +11,7 @@ use memory_units::Pages;
|
||||||
use module::ModuleRef;
|
use module::ModuleRef;
|
||||||
use nan_preserving_float::{F32, F64};
|
use nan_preserving_float::{F32, F64};
|
||||||
use parity_wasm::elements::Local;
|
use parity_wasm::elements::Local;
|
||||||
|
use validation::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
use value::{
|
use value::{
|
||||||
ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto,
|
ArithmeticOps, ExtendInto, Float, Integer, LittleEndianConvert, RuntimeValue, TransmuteInto,
|
||||||
TryTruncateInto, WrapInto,
|
TryTruncateInto, WrapInto,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
2
test.sh
2
test.sh
|
@ -4,6 +4,6 @@ set -eux
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
|
|
||||||
time cargo test
|
time cargo test --all
|
||||||
|
|
||||||
cd -
|
cd -
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
|
@ -1,9 +1,9 @@
|
||||||
|
use crate::Error;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
|
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
|
||||||
};
|
};
|
||||||
use validation::Error;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ModuleContext {
|
pub struct ModuleContext {
|
File diff suppressed because it is too large
Load Diff
|
@ -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)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::error;
|
use std::error;
|
||||||
|
@ -10,24 +33,22 @@ use hashbrown::HashSet;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use self::context::ModuleContextBuilder;
|
use self::context::ModuleContextBuilder;
|
||||||
use self::func::FunctionReader;
|
|
||||||
use common::stack;
|
|
||||||
use isa;
|
|
||||||
use memory_units::Pages;
|
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
BlockType, External, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, MemoryType,
|
BlockType, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction, Internal,
|
||||||
Module, ResizableLimits, TableType, Type, ValueType,
|
MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod context;
|
pub mod context;
|
||||||
mod func;
|
pub mod func;
|
||||||
mod util;
|
pub mod util;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
// TODO: Consider using a type other than String, because
|
||||||
|
// of formatting machinary is not welcomed in substrate runtimes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error(String);
|
pub struct Error(pub String);
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
@ -48,137 +69,77 @@ impl From<stack::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub trait Validator {
|
||||||
pub struct ValidatedModule {
|
type Output;
|
||||||
pub code_map: Vec<isa::Instructions>,
|
type FuncValidator: FuncValidator;
|
||||||
pub module: Module,
|
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 {
|
pub trait FuncValidator {
|
||||||
type Target = Module;
|
type Output;
|
||||||
fn deref(&self) -> &Module {
|
fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self;
|
||||||
&self.module
|
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> {
|
/// A function validator that just validates modules and produces no result.
|
||||||
if let Some(code) = module.code_section() {
|
pub struct PlainFuncValidator;
|
||||||
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
|
|
||||||
use parity_wasm::elements::Instruction::*;
|
|
||||||
|
|
||||||
macro_rules! match_eq {
|
impl FuncValidator for PlainFuncValidator {
|
||||||
($pattern:pat) => {
|
type Output = ();
|
||||||
|val| if let $pattern = *val { true } else { false }
|
|
||||||
};
|
fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator {
|
||||||
|
PlainFuncValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
const DENIED: &[fn(&Instruction) -> bool] = &[
|
fn next_instruction(
|
||||||
match_eq!(F32Load(_, _)),
|
&mut self,
|
||||||
match_eq!(F64Load(_, _)),
|
ctx: &mut func::FunctionValidationContext,
|
||||||
match_eq!(F32Store(_, _)),
|
instruction: &Instruction,
|
||||||
match_eq!(F64Store(_, _)),
|
) -> Result<(), Error> {
|
||||||
match_eq!(F32Const(_)),
|
ctx.step(instruction)
|
||||||
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 finish(self) -> () {
|
||||||
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
|
pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error> {
|
||||||
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")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
|
||||||
let mut context_builder = ModuleContextBuilder::new();
|
let mut context_builder = ModuleContextBuilder::new();
|
||||||
let mut imported_globals = Vec::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.
|
// Copy types from module as is.
|
||||||
context_builder.set_types(
|
context_builder.set_types(
|
||||||
|
@ -265,15 +226,15 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
|
||||||
.bodies()
|
.bodies()
|
||||||
.get(index as usize)
|
.get(index as usize)
|
||||||
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
.ok_or(Error(format!("Missing body for function {}", index)))?;
|
||||||
let code =
|
|
||||||
FunctionReader::read_function(&context, function, function_body).map_err(|e| {
|
let output = func::drive::<V::FuncValidator>(&context, function, function_body)
|
||||||
let Error(ref msg) = e;
|
.map_err(|Error(ref msg)| {
|
||||||
Error(format!(
|
Error(format!(
|
||||||
"Function #{} reading/validation error: {}",
|
"Function #{} reading/validation error: {}",
|
||||||
index, msg
|
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> {
|
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> {
|
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
|
||||||
let initial: Pages = Pages(memory_type.limits().initial() as usize);
|
let initial = memory_type.limits().initial();
|
||||||
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
|
let maximum: Option<u32> = memory_type.limits().maximum();
|
||||||
::memory::validate_memory(initial, maximum).map_err(Error)
|
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> {
|
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
|
|
@ -1,5 +1,5 @@
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
|
@ -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();
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
|
use crate::Error;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use alloc::prelude::*;
|
use alloc::prelude::v1::*;
|
||||||
use parity_wasm::elements::{Local, ValueType};
|
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
|
/// Locals are the concatenation of a slice of function parameters
|
||||||
/// with function declared local variables.
|
/// with function declared local variables.
|
Loading…
Reference in New Issue