Skip to content

Commit

Permalink
Final invalid assertion tests: memory size and limit validation (#305)
Browse files Browse the repository at this point in the history
All spec tests are now enabled and passing!
  • Loading branch information
jblebrun authored Jan 23, 2024
1 parent 0150bd5 commit 52cc670
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 93 deletions.
6 changes: 6 additions & 0 deletions tests-integration/src/spec/error_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,13 @@ fn matches_validation_error(failure: &str, err: &ValidationError) -> bool {
ValidationErrorKind::InvalidConstantInstruction => {
failure == "constant expression required"
}
ValidationErrorKind::InvalidLimits => {
failure == "size minimum must not be greater than maximum"
}
ValidationErrorKind::ImmutableGlobal => failure == "global is immutable",
ValidationErrorKind::MemoryTooLarge => {
failure == "memory size must be at most 65536 pages (4GiB)"
}
ValidationErrorKind::MultipleMemories => failure == "multiple memories",
ValidationErrorKind::TypeMismatch { .. } => failure == "type mismatch",
ValidationErrorKind::UndeclaredFunctionRef => failure == "undeclared function reference",
Expand Down
5 changes: 1 addition & 4 deletions tests-integration/tests/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ use {
wrausmt_format::ValidationMode,
};

const GLOBAL_FAILURES_TO_IGNORE: &[&str] = &[
"memory size must be at most 65536 pages (4GiB)",
"size minimum must not be greater than maximum",
];
const GLOBAL_FAILURES_TO_IGNORE: &[&str] = &[];
// To regenerate the spectest! lines below using the transform this macro
// expects: "".join(["spectest!(r#{});
// ".format(i.replace(".wast","").replace("-","_x_")) for i in
Expand Down
6 changes: 4 additions & 2 deletions wrausmt-format/src/binary/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use {
BinaryParser, ParserReader,
},
crate::{binary::read_with_location::Locate, pctx},
wrausmt_runtime::syntax::{ImportDesc, ImportField, Resolved},
wrausmt_runtime::syntax::{ImportDesc, ImportField, Resolved, Unvalidated},
};

/// A trait to allow parsing of an imports section from something implementing
Expand All @@ -18,7 +18,9 @@ impl<R: ParserReader> BinaryParser<R> {
/// 0x01 (table) tt:tabletype
/// 0x02 (memory) mt:memorytype
/// 0x03 (global) gt:globaltype
pub(in crate::binary) fn read_imports_section(&mut self) -> Result<Vec<ImportField<Resolved>>> {
pub(in crate::binary) fn read_imports_section(
&mut self,
) -> Result<Vec<ImportField<Resolved, Unvalidated>>> {
pctx!(self, "read imports section");
let location = self.location();
self.read_vec(|_, s| {
Expand Down
6 changes: 3 additions & 3 deletions wrausmt-format/src/binary/mems.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use {
super::{error::Result, BinaryParser, ParserReader},
crate::{binary::read_with_location::Locate, pctx},
wrausmt_runtime::syntax::MemoryField,
wrausmt_runtime::syntax::{MemoryField, Unvalidated},
};

/// Read the tables section of a binary module from a std::io::Read.
impl<R: ParserReader> BinaryParser<R> {
/// Read a funcs section. This is just a vec(TypeIndex).
/// The values here don't correspond to a real module section, instead they
/// correlate with the rest of the function data in the code section.
pub(in crate::binary) fn read_mems_section(&mut self) -> Result<Vec<MemoryField>> {
pub(in crate::binary) fn read_mems_section(&mut self) -> Result<Vec<MemoryField<Unvalidated>>> {
pctx!(self, "read mems section");
self.read_vec(|_, s| s.read_memory_field())
}

fn read_memory_field(&mut self) -> Result<MemoryField> {
fn read_memory_field(&mut self) -> Result<MemoryField<Unvalidated>> {
pctx!(self, "read memory field");
let location = self.location();
Ok(MemoryField {
Expand Down
8 changes: 5 additions & 3 deletions wrausmt-format/src/binary/tables.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
use {
super::{error::Result, BinaryParser, ParserReader},
crate::pctx,
wrausmt_runtime::syntax::TableField,
wrausmt_runtime::syntax::{TableField, Unvalidated},
};

/// Read the tables section of a binary module from a std::io::Read.
impl<R: ParserReader> BinaryParser<R> {
/// Read a funcs section. This is just a vec(TypeIndex).
/// The values here don't correspond to a real module section, instead they
/// correlate with the rest of the function data in the code section.
pub(in crate::binary) fn read_tables_section(&mut self) -> Result<Vec<TableField>> {
pub(in crate::binary) fn read_tables_section(
&mut self,
) -> Result<Vec<TableField<Unvalidated>>> {
pctx!(self, "read tables section");
self.read_vec(|_, s| s.read_table_field())
}

fn read_table_field(&mut self) -> Result<TableField> {
fn read_table_field(&mut self) -> Result<TableField<Unvalidated>> {
pctx!(self, "read table field");
let location = self.reader.location();
Ok(TableField {
Expand Down
17 changes: 7 additions & 10 deletions wrausmt-format/src/binary/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use {
},
wrausmt_runtime::syntax::{
types::{GlobalType, Limits, MemType, NumType, RefType, TableType, ValueType},
FParam, FResult, FunctionType, Index, Resolved, TypeField, TypeUse,
FParam, FResult, FunctionType, Index, Resolved, TypeField, TypeUse, Unvalidated,
},
};

Expand Down Expand Up @@ -59,19 +59,16 @@ impl<R: ParserReader> BinaryParser<R> {
Ok(TypeUse::ByIndex(self.read_index_use()?))
}

pub(in crate::binary) fn read_memory_type(&mut self) -> Result<MemType> {
pub(in crate::binary) fn read_memory_type(&mut self) -> Result<MemType<Unvalidated>> {
pctx!(self, "read memory type");
Ok(MemType {
limits: self.read_limits()?,
})
Ok(MemType::new(self.read_limits()?))
}

pub(in crate::binary) fn read_table_type(&mut self) -> Result<TableType> {
pub(in crate::binary) fn read_table_type(&mut self) -> Result<TableType<Unvalidated>> {
pctx!(self, "read table type");
Ok(TableType {
reftype: self.read_ref_type()?,
limits: self.read_limits()?,
})
let reftype = self.read_ref_type()?;
let limits = self.read_limits()?;
Ok(TableType::new(limits, reftype))
}

pub(in crate::binary) fn read_global_type(&mut self) -> Result<GlobalType> {
Expand Down
90 changes: 84 additions & 6 deletions wrausmt-format/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ use {
validation::{KindResult, Result},
wrausmt_common::true_or::TrueOr,
wrausmt_runtime::syntax::{
location::Location, types::NumType, CompiledExpr, DataField, DataInit, ElemField, ElemList,
ExportDesc, ExportField, FuncField, FuncIndex, GlobalField, Index, ModeEntry, Module,
Resolved, StartField, TablePosition, UncompiledExpr, Unvalidated, Validated,
location::Location,
types::{MemType, NumType, TableType},
CompiledExpr, DataField, DataInit, ElemField, ElemList, ExportDesc, ExportField, FuncField,
FuncIndex, GlobalField, ImportDesc, ImportField, Index, MemoryField, ModeEntry, Module,
Resolved, StartField, TableField, TablePosition, UncompiledExpr, Unvalidated, Validated,
},
};

Expand Down Expand Up @@ -74,6 +76,24 @@ pub fn compile_module(
.collect();
let exports = exports?;

let memories: Result<Vec<_>> = std::mem::take(&mut module.memories)
.into_iter()
.map(validate_memory)
.collect();
let memories = memories?;

let tables: Result<Vec<_>> = std::mem::take(&mut module.tables)
.into_iter()
.map(validate_table)
.collect();
let tables = tables?;

let imports: Result<Vec<_>> = std::mem::take(&mut module.imports)
.into_iter()
.map(validate_import)
.collect();
let imports = imports?;

let module_context = module_context.update_func_refs(funcrefs);
let funcs: Result<Vec<_>> = std::mem::take(&mut module.funcs)
.into_iter()
Expand All @@ -88,9 +108,9 @@ pub fn compile_module(
customs: module.customs,
types: module.types,
funcs,
tables: module.tables,
memories: module.memories,
imports: module.imports,
tables,
memories,
imports,
exports,
globals,
start,
Expand Down Expand Up @@ -209,6 +229,64 @@ fn validate_start(
}
}

fn validate_memory(memory: MemoryField<Unvalidated>) -> Result<MemoryField<Validated>> {
Ok(MemoryField {
id: memory.id,
exports: memory.exports,
memtype: validate_memtype(memory.memtype).validation_error(memory.location)?,
location: memory.location,
})
}

fn validate_table(table: TableField<Unvalidated>) -> Result<TableField<Validated>> {
Ok(TableField {
id: table.id,
exports: table.exports,
tabletype: validate_tabletype(table.tabletype).validation_error(table.location)?,
location: table.location,
})
}

fn validate_memtype(memtype: MemType<Unvalidated>) -> KindResult<MemType<Validated>> {
if let Some(upper) = memtype.limits.upper {
(memtype.limits.lower <= upper).true_or(ValidationErrorKind::InvalidLimits)?;
}
(memtype.limits.upper.unwrap_or(memtype.limits.lower) <= 65536)
.true_or(ValidationErrorKind::MemoryTooLarge)?;
Ok(MemType::new(memtype.limits))
}

fn validate_tabletype(tabletype: TableType<Unvalidated>) -> KindResult<TableType<Validated>> {
if let Some(upper) = tabletype.limits.upper {
(tabletype.limits.lower <= upper).true_or(ValidationErrorKind::InvalidLimits)?
}
Ok(TableType::new(tabletype.limits, tabletype.reftype))
}

fn validate_import(
import: ImportField<Resolved, Unvalidated>,
) -> Result<ImportField<Resolved, Validated>> {
Ok(ImportField {
id: import.id,
modname: import.modname,
name: import.name,
exports: import.exports,
desc: validate_import_desc(import.desc).validation_error(import.location)?,
location: import.location,
})
}

fn validate_import_desc(
desc: ImportDesc<Resolved, Unvalidated>,
) -> KindResult<ImportDesc<Resolved, Validated>> {
Ok(match desc {
ImportDesc::Func(t) => ImportDesc::Func(t),
ImportDesc::Global(g) => ImportDesc::Global(g),
ImportDesc::Mem(m) => ImportDesc::Mem(validate_memtype(m)?),
ImportDesc::Table(t) => ImportDesc::Table(validate_tabletype(t)?),
})
}

fn compile_export_desc(
module: &ModuleContext,
funcrefs: &mut Vec<Index<Resolved, FuncIndex>>,
Expand Down
9 changes: 5 additions & 4 deletions wrausmt-format/src/compiler/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub enum ValidationErrorKind {
ImmutableGlobal,
InvalidConstantGlobal,
InvalidConstantInstruction,
InvalidLimits,
MemoryTooLarge,
MultipleMemories,
OpcodeMismatch,
Expand Down Expand Up @@ -156,8 +157,8 @@ pub struct GlobalValidationType {
pub struct ModuleContext {
pub types: Vec<FunctionType>,
pub funcs: Vec<FunctionType>,
pub tables: Vec<TableType>,
pub mems: Vec<MemType>,
pub tables: Vec<TableType<Unvalidated>>,
pub mems: Vec<MemType<Unvalidated>>,
pub globals: Vec<GlobalValidationType>,
pub elems: Vec<RefType>,
pub datas: usize,
Expand All @@ -170,8 +171,8 @@ impl ModuleContext {
/// out of the module.
pub fn new(module: &Module<Resolved, Unvalidated, UncompiledExpr<Resolved>>) -> Result<Self> {
let mut funcs: Vec<FunctionType> = Vec::new();
let mut tables: Vec<TableType> = Vec::new();
let mut mems: Vec<MemType> = Vec::new();
let mut tables: Vec<TableType<Unvalidated>> = Vec::new();
let mut mems: Vec<MemType<Unvalidated>> = Vec::new();
let mut globals: Vec<GlobalValidationType> = Vec::new();

for import in module.imports.iter() {
Expand Down
6 changes: 3 additions & 3 deletions wrausmt-format/src/text/module_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl ModuleBuilder {
Ok(())
}

pub fn add_tablefield(&mut self, f: TableField) -> Result<()> {
pub fn add_tablefield(&mut self, f: TableField<Unvalidated>) -> Result<()> {
add_ident!(self, f, tableindices, tables, self.tableidx_offset; DuplicateTable);

// export field may define new exports.
Expand All @@ -127,7 +127,7 @@ impl ModuleBuilder {
Ok(())
}

pub fn add_memoryfield(&mut self, f: MemoryField) -> Result<()> {
pub fn add_memoryfield(&mut self, f: MemoryField<Unvalidated>) -> Result<()> {
add_ident!(self, f, memindices, memories, self.memidx_offset; DuplicateMem);

// export field may define new exports.
Expand All @@ -144,7 +144,7 @@ impl ModuleBuilder {
Ok(())
}

pub fn add_importfield(&mut self, f: ImportField<Unresolved>) -> Result<()> {
pub fn add_importfield(&mut self, f: ImportField<Unresolved, Unvalidated>) -> Result<()> {
(self.module.funcs.is_empty()).true_or(ResolveError::ImportAfterFunction)?;
(self.module.globals.is_empty()).true_or(ResolveError::ImportAfterGlobal)?;
(self.module.memories.is_empty()).true_or(ResolveError::ImportAfterMemory)?;
Expand Down
28 changes: 14 additions & 14 deletions wrausmt-format/src/text/parse/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ use {
pub enum Field<R: ResolvedState, V: ValidatedState> {
Type(TypeField),
Func(FuncField<R, UncompiledExpr<R>>),
Table(TableField, Option<ElemField<R, UncompiledExpr<R>>>),
Memory(MemoryField, Option<Box<[u8]>>),
Import(ImportField<R>),
Table(TableField<V>, Option<ElemField<R, UncompiledExpr<R>>>),
Memory(MemoryField<V>, Option<Box<[u8]>>),
Import(ImportField<R, V>),
Export(ExportField<R, V>),
Global(GlobalField<UncompiledExpr<R>>),
Start(StartField<R, V>),
Expand Down Expand Up @@ -310,12 +310,10 @@ impl<R: Read> Parser<R> {
if inline_data.len() % PAGE_SIZE > 0 {
n += 1;
}
let memtype = MemType {
limits: Limits {
lower: n,
upper: Some(n),
},
};
let memtype = MemType::new(Limits {
lower: n,
upper: Some(n),
});
return Ok(Some(Field::Memory(
MemoryField {
id,
Expand All @@ -329,7 +327,7 @@ impl<R: Read> Parser<R> {

let limits = self.expect_limits()?;

let memtype = MemType { limits };
let memtype = MemType::new(limits);

if let Some(import) = import {
self.expect_close()?;
Expand Down Expand Up @@ -383,7 +381,9 @@ impl<R: Read> Parser<R> {
})))
}

pub fn expect_importdesc(&mut self) -> Result<(Option<Id>, ImportDesc<Unresolved>)> {
pub fn expect_importdesc(
&mut self,
) -> Result<(Option<Id>, ImportDesc<Unresolved, Unvalidated>)> {
pctx!(self, "expect importdesc");
if self.try_expr_start("func")? {
let id = self.try_id()?;
Expand All @@ -399,7 +399,7 @@ impl<R: Read> Parser<R> {
let id = self.try_id()?;
let limits = self.expect_limits()?;
self.expect_close()?;
Ok((id, ImportDesc::Mem(MemType { limits })))
Ok((id, ImportDesc::Mem(MemType::new(limits))))
} else if self.try_expr_start("global")? {
let id = self.try_id()?;
let globaltype = self.expect_globaltype()?;
Expand All @@ -410,11 +410,11 @@ impl<R: Read> Parser<R> {
}
}

pub fn expect_tabletype(&mut self) -> Result<TableType> {
pub fn expect_tabletype(&mut self) -> Result<TableType<Unvalidated>> {
pctx!(self, "expect tabletype");
let limits = self.expect_limits()?;
let reftype = self.expect_reftype()?;
Ok(TableType { limits, reftype })
Ok(TableType::new(limits, reftype))
}

pub fn expect_globaltype(&mut self) -> Result<GlobalType> {
Expand Down
Loading

0 comments on commit 52cc670

Please sign in to comment.