diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/parser/src/conversion.rs | 64 | ||||
| -rw-r--r-- | crates/parser/src/error.rs | 2 | ||||
| -rw-r--r-- | crates/parser/src/lib.rs | 20 | ||||
| -rw-r--r-- | crates/parser/src/module.rs | 10 | ||||
| -rw-r--r-- | crates/parser/src/visit.rs | 7 | ||||
| -rw-r--r-- | crates/tinywasm/src/error.rs | 34 | ||||
| -rw-r--r-- | crates/tinywasm/src/instance.rs | 53 | ||||
| -rw-r--r-- | crates/tinywasm/src/interpreter/executor.rs | 2 | ||||
| -rw-r--r-- | crates/tinywasm/src/reference.rs | 15 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/memory/mod.rs | 8 | ||||
| -rw-r--r-- | crates/tinywasm/src/store/mod.rs | 8 | ||||
| -rw-r--r-- | crates/tinywasm/tests/import_linking.rs | 4 | ||||
| -rw-r--r-- | crates/tinywasm/tests/internal_refs.rs | 97 | ||||
| -rw-r--r-- | crates/tinywasm/tests/module_descriptors.rs | 63 | ||||
| -rw-r--r-- | crates/tinywasm/tests/store_ownership.rs | 46 | ||||
| -rw-r--r-- | crates/types/src/archive.rs | 2 | ||||
| -rw-r--r-- | crates/types/src/lib.rs | 72 | ||||
| -rw-r--r-- | crates/types/src/value.rs | 3 |
18 files changed, 433 insertions, 77 deletions
diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 487f992..8a12daa 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -3,7 +3,7 @@ use alloc::sync::Arc; use crate::{Result, module::FunctionCode, visit::process_operators_and_validate}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use tinywasm_types::*; -use wasmparser::{FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources}; +use wasmparser::{CompositeInnerType, FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources}; pub(crate) fn convert_module_elements<'a, T: IntoIterator<Item = wasmparser::Result<wasmparser::Element<'a>>>>( elements: T, @@ -39,7 +39,7 @@ pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result .collect::<Result<Vec<_>>>()? .into_boxed_slice(); - Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty), range: element.range }) + Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty)?, range: element.range }) } } } @@ -74,7 +74,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im let kind = match import.ty { wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { - element_type: convert_reftype(ty.element_type), + element_type: convert_reftype(ty.element_type)?, size_initial: ty.initial.try_into().map_err(|_| { crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) })?, @@ -87,7 +87,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result<Im }), wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), wasmparser::TypeRef::Global(ty) => { - ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type), ty.mutable)) + ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type)?, ty.mutable)) } wasmparser::TypeRef::Tag(ty) => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); @@ -129,7 +129,7 @@ pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result<Table let size_max = table.ty.maximum.map(|max| max.try_into()).transpose(); let size_max = size_max.map_err(|e| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {e}")))?; - Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max }) + Ok(TableType { element_type: convert_reftype(table.ty.element_type)?, size_initial, size_max }) } pub(crate) fn convert_module_globals( @@ -139,7 +139,7 @@ pub(crate) fn convert_module_globals( .into_iter() .map(|global| { let global = global?; - let ty = convert_valtype(&global.ty.content_type); + let ty = convert_valtype(&global.ty.content_type)?; let ops = global.init_expr.get_operators_reader(); Ok(Global { init: process_const_operators(ops)?, ty: GlobalType::new(ty, global.ty.mutable) }) }) @@ -211,27 +211,37 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<Arc<FuncTy )); } - let ty = types.next().unwrap().unwrap_func(); + let ty = types.next().unwrap(); + let CompositeInnerType::Func(ty) = &ty.composite_type.inner else { + return Err(crate::ParseError::UnsupportedOperator(format!( + "Unsupported non-function type in type section: {}", + ty.composite_type + ))); + }; let params: Vec<_> = ty.params().iter().map(convert_valtype).collect(); - let results: Vec<_> = ty.results().iter().map(convert_valtype).collect(); + let params = params.into_iter().collect::<Result<Vec<_>>>()?; + let results = ty.results().iter().map(convert_valtype).collect::<Result<Vec<_>>>()?; Ok(FuncType::new(¶ms, &results).into()) } -pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> WasmType { +pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> Result<WasmType> { match reftype { - _ if reftype.is_func_ref() => WasmType::RefFunc, - _ if reftype.is_extern_ref() => WasmType::RefExtern, - _ => unimplemented!("Unsupported reference type: {:?}, {:?}", reftype, reftype.heap_type()), + _ if reftype.is_func_ref() => Ok(WasmType::RefFunc), + _ if reftype.is_extern_ref() => Ok(WasmType::RefExtern), + _ => Err(crate::ParseError::UnsupportedOperator(format!( + "Unsupported reference type: {reftype:?}, {:?}", + reftype.heap_type() + ))), } } -pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> WasmType { +pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> Result<WasmType> { match valtype { - wasmparser::ValType::I32 => WasmType::I32, - wasmparser::ValType::I64 => WasmType::I64, - wasmparser::ValType::F32 => WasmType::F32, - wasmparser::ValType::F64 => WasmType::F64, - wasmparser::ValType::V128 => WasmType::V128, + wasmparser::ValType::I32 => Ok(WasmType::I32), + wasmparser::ValType::I64 => Ok(WasmType::I64), + wasmparser::ValType::F32 => Ok(WasmType::F32), + wasmparser::ValType::F64 => Ok(WasmType::F64), + wasmparser::ValType::V128 => Ok(WasmType::V128), wasmparser::ValType::Ref(r) => convert_reftype(*r), } } @@ -247,10 +257,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C let mut out = Vec::with_capacity(ops.len().saturating_sub(1)); for op in ops.iter().take(ops.len() - 1) { let instr = match op { - wasmparser::Operator::RefNull { hty } => match convert_heaptype(*hty) { + wasmparser::Operator::RefNull { hty } => match convert_heaptype(*hty)? { WasmType::RefFunc => ConstInstruction::RefFunc(None), WasmType::RefExtern => ConstInstruction::RefExtern(None), - _ => unimplemented!("Unsupported heap type: {:?}", hty), + other => { + return Err(crate::ParseError::UnsupportedOperator(format!( + "Unsupported ref.null heap type lowered to {other:?}" + ))); + } }, wasmparser::Operator::RefFunc { function_index } => ConstInstruction::RefFunc(Some(*function_index)), wasmparser::Operator::I32Const { value } => ConstInstruction::I32Const(*value), @@ -277,12 +291,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C Ok(out.into_boxed_slice()) } -pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> WasmType { +pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> Result<WasmType> { match heap { - wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Func } => WasmType::RefFunc, + wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Func } => { + Ok(WasmType::RefFunc) + } wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Extern } => { - WasmType::RefExtern + Ok(WasmType::RefExtern) } - _ => unimplemented!("Unsupported heap type: {:?}", heap), + _ => Err(crate::ParseError::UnsupportedOperator(format!("Unsupported heap type: {heap:?}"))), } } diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index 22edd39..7b5dc34 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -2,7 +2,7 @@ use alloc::string::{String, ToString}; use core::fmt::{Debug, Display}; use wasmparser::Encoding; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] /// Errors that can occur when parsing a WebAssembly module pub enum ParseError { /// An invalid type was encountered diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index c4f6ff7..c6ae326 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -283,7 +283,25 @@ impl Parser { continue; } - if eof || reader.end_reached { + if reader.end_reached { + if !buffer.is_empty() { + return Err(ParseError::Other("trailing bytes after end of module".into())); + } + + if !eof { + let read_bytes = Self::read_more(&mut stream, &mut buffer, 1)?; + eof = read_bytes == 0; + + if !eof { + return Err(ParseError::Other("trailing bytes after end of module".into())); + } + } + + reader.process_pending_functions(&self.options)?; + return reader.into_module(&self.options); + } + + if eof { reader.process_pending_functions(&self.options)?; return reader.into_module(&self.options); } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 02a3297..5f5e5e2 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -377,6 +377,15 @@ impl<'a> ModuleReader<'a> { }; let mut funcs = Vec::with_capacity(self.code.len()); + let mut func_type_idxs = self + .imports + .iter() + .filter_map(|import| match import.kind { + ImportKind::Function(type_idx) => Some(type_idx), + _ => None, + }) + .collect::<Vec<_>>(); + func_type_idxs.extend(self.code_type_addrs.iter().copied()); for (code, ty_idx) in self.code.into_iter().zip(self.code_type_addrs) { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); @@ -402,6 +411,7 @@ impl<'a> ModuleReader<'a> { Ok(ModuleInner { funcs: funcs.into(), func_types: self.func_types.into(), + func_type_idxs: func_type_idxs.into(), globals: self.globals.into(), table_types: self.table_types.into(), imports: self.imports.into(), diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index f8d5cfd..62db307 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -417,7 +417,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild // Reference Types fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { - self.instructions.push(Instruction::RefNull(convert_heaptype(ty))); + match convert_heaptype(ty) { + Ok(ty) => self.instructions.push(Instruction::RefNull(ty)), + Err(err) => { + self.error.get_or_insert(err); + } + }; } fn visit_typed_select_multi(&mut self, tys: Vec<wasmparser::ValType>) -> Self::Output { diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 0d1f368..adac5a7 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -36,9 +36,6 @@ pub enum Error { /// An invalid label type was encountered InvalidLabelType, - /// The store is not the one that the module instance was instantiated in - InvalidStore, - #[cfg(feature = "std")] /// An I/O error occurred Io(crate::std::io::Error), @@ -51,9 +48,27 @@ pub enum Error { Twasm(TwasmError), } +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Trap(a), Self::Trap(b)) => a == b, + (Self::Linker(a), Self::Linker(b)) => a == b, + (Self::UnsupportedFeature(a), Self::UnsupportedFeature(b)) => a == b, + (Self::Other(a), Self::Other(b)) => a == b, + #[cfg(feature = "std")] + (Self::Io(a), Self::Io(b)) => a.kind() == b.kind(), + #[cfg(feature = "parser")] + (Self::Parser(a), Self::Parser(b)) => a == b, + (Self::Twasm(a), Self::Twasm(b)) => a == b, + _ => false, + } + } +} + /// Errors that can occur when linking a WebAssembly module #[non_exhaustive] #[cfg_attr(feature = "debug", derive(Debug))] +#[derive(PartialEq, Eq)] pub enum LinkingError { /// An unknown import was encountered UnknownImport { @@ -120,6 +135,9 @@ pub enum Trap { /// Invalid Integer Conversion InvalidConversionToInt, + /// The store is not the one that the module instance was instantiated in + InvalidStore, + /// Integer Overflow IntegerOverflow, @@ -173,11 +191,18 @@ impl Trap { Self::UninitializedElement { .. } => "uninitialized element", Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch", Self::HostFunction(_) => "host function trap", + Self::InvalidStore => "invalid store", Self::Other(message) => message, } } } +impl PartialEq for Trap { + fn eq(&self, other: &Self) -> bool { + self.message() == other.message() + } +} + impl LinkingError { /// Get the message of the linking error pub fn message(&self) -> &'static str { @@ -199,6 +224,7 @@ impl From<TwasmError> for Error { Self::Twasm(value) } } + impl From<Trap> for Error { fn from(value: Trap) -> Self { Self::Trap(value) @@ -226,7 +252,6 @@ impl Display for Error { } #[cfg(not(feature = "debug"))] Self::InvalidHostFnReturn { .. } => write!(f, "invalid host function return"), - Self::InvalidStore => write!(f, "invalid store"), } } } @@ -264,6 +289,7 @@ impl Display for Trap { Self::UninitializedElement { index } => { write!(f, "uninitialized element: index={index}") } + Self::InvalidStore => write!(f, "invalid store"), #[cfg(feature = "debug")] Self::IndirectCallTypeMismatch { expected, actual } => { write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}") diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 8ef572d..e5a2d88 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -6,7 +6,7 @@ use alloc::{format, rc::Rc}; use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, WasmTypesFromTuple}; -use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, Table}; +use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, Table, Trap}; /// A typed view over an exported extern value. pub enum ExternItem { @@ -34,6 +34,7 @@ struct ModuleInstanceInner { store_id: usize, idx: ModuleInstanceAddr, types: Arc<[Arc<FuncType>]>, + func_type_idxs: Arc<[u32]>, func_addrs: Box<[FuncAddr]>, table_addrs: Box<[TableAddr]>, mem_addrs: Box<[MemAddr]>, @@ -50,12 +51,25 @@ impl ModuleInstance { self.0.idx } + /// Type indices come from the module type section and are used by indirect calls. #[inline] - pub(crate) fn func_ty(&self, addr: FuncAddr) -> &Arc<FuncType> { - match self.0.types.get(addr as usize) { + pub(crate) fn func_type_by_type_index(&self, type_idx: u32) -> &Arc<FuncType> { + match self.0.types.get(type_idx as usize) { Some(ty) => ty, None => { cold_path(); + unreachable!("invalid type index: {type_idx}") + } + } + } + + /// Function indices need their own lookup because they are not type-section indices. + #[inline] + pub(crate) fn func_type_idx(&self, addr: FuncAddr) -> u32 { + match self.0.func_type_idxs.get(addr as usize) { + Some(idx) => *idx, + None => { + cold_path(); unreachable!("invalid function address: {addr}") } } @@ -66,7 +80,7 @@ impl ModuleInstance { &self.0.func_addrs } - // resolve a function address to the global store address + /// resolve a function address to the global store address #[inline] pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { match self.0.func_addrs.get(addr as usize) { @@ -78,7 +92,7 @@ impl ModuleInstance { } } - // resolve a table address to the global store address + /// resolve a table address to the global store address #[inline] pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { match self.0.table_addrs.get(addr as usize) { @@ -90,7 +104,7 @@ impl ModuleInstance { } } - // resolve a memory address to the global store address + /// resolve a memory address to the global store address #[inline] pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { match self.0.mem_addrs.get(addr as usize) { @@ -102,7 +116,7 @@ impl ModuleInstance { } } - // resolve a data address to the global store address + /// resolve a data address to the global store address #[inline] pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { match self.0.data_addrs.get(addr as usize) { @@ -114,7 +128,7 @@ impl ModuleInstance { } } - // resolve an element address to the global store address + /// resolve an element address to the global store address #[inline] pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { match self.0.elem_addrs.get(addr as usize) { @@ -126,7 +140,7 @@ impl ModuleInstance { } } - // resolve a global address to the global store address + /// resolve a global address to the global store address #[inline] pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { match self.0.global_addrs.get(addr as usize) { @@ -142,7 +156,7 @@ impl ModuleInstance { pub(crate) fn validate_store(&self, store: &Store) -> Result<()> { if self.0.store_id != store.id() { cold_path(); - return Err(Error::InvalidStore); + return Err(Trap::InvalidStore.into()); } Ok(()) } @@ -167,7 +181,6 @@ impl ModuleInstance { pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option<Imports>) -> Result<Self> { let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, module)?; - addrs.funcs.extend(store.init_funcs(&module.funcs, idx)); addrs.tables.extend(store.init_tables(&module.table_types)); match module.local_memory_allocation { @@ -186,6 +199,7 @@ impl ModuleInstance { store_id: store.id(), idx, types: module.func_types.clone(), + func_type_idxs: module.func_type_idxs.clone(), func_addrs: addrs.funcs.into_boxed_slice(), table_addrs: addrs.tables.into_boxed_slice(), mem_addrs: addrs.memories.into_boxed_slice(), @@ -230,7 +244,7 @@ impl ModuleInstance { item: crate::StoreItem::new(self.0.store_id, func_addr), module_addr: self.id(), addr: func_addr, - ty: self.func_ty(export.index).clone(), + ty: self.func_type_by_type_index(self.func_type_idx(export.index)).clone(), }) } ExternalKind::Table => { @@ -281,7 +295,7 @@ impl ModuleInstance { item: crate::StoreItem::new(self.0.store_id, addr), module_addr: self.id(), addr, - ty: self.func_ty(export.index).clone(), + ty: self.func_type_by_type_index(self.func_type_idx(export.index)).clone(), })) } ExternVal::Memory(addr) => Ok(ExternItem::Memory(Memory::from_store_addr(self.0.store_id, addr))), @@ -461,19 +475,24 @@ impl ModuleInstance { pub fn start_func(&self, store: &Store) -> Result<Option<Function>> { self.validate_store(store)?; - let func_index = match self.0.func_start { + let func_addr = match self.0.func_start { Some(func_index) => func_index, None => { - // alternatively, check for a _start function in the exports + // Alternatively, check for a _start function in the exports. let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else { return Ok(None); }; - func_addr + return Ok(Some(Function { + item: crate::StoreItem::new(self.0.store_id, func_addr), + module_addr: self.id(), + addr: func_addr, + ty: store.state.get_func(func_addr).ty().clone(), + })); } }; - let func_addr = self.resolve_func_addr(func_index); + let func_addr = self.resolve_func_addr(func_addr); Ok(Some(Function { item: crate::StoreItem::new(self.0.store_id, func_addr), module_addr: self.id(), diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 4d862d6..4c941c8 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -1046,7 +1046,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { return Err(Trap::UninitializedElement { index: table_idx as usize }); }; - let call_ty = self.module.func_ty(type_addr); + let call_ty = self.module.func_type_by_type_index(type_addr); match self.store.state.get_func(func_ref) { crate::FunctionInstance::Wasm(wasm_func) => { if wasm_func.ty() != call_ty { diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 5f8d46f..8f02317 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -7,8 +7,7 @@ use alloc::{ffi::CString, format}; use crate::store::{GlobalInstance, TableElement, TableInstance}; use crate::{Error, MemoryInstance, Result, Store, Trap}; use tinywasm_types::{ - Addr, ExternRef, FuncRef, GlobalAddr, GlobalType, MemAddr, MemoryArch, MemoryType, TableAddr, TableType, WasmType, - WasmValue, + Addr, ExternRef, FuncRef, GlobalAddr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue, }; #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -27,7 +26,7 @@ impl StoreItem { #[inline] pub(crate) fn validate_store(&self, store: &Store) -> Result<(), Trap> { if self.store_id != store.id() { - return Err(Trap::Other("invalid store")); + return Err(Trap::InvalidStore); } Ok(()) } @@ -146,9 +145,6 @@ impl Memory { /// Create a new memory in the given store. pub fn new(store: &mut Store, ty: MemoryType) -> Result<Self> { - if let MemoryArch::I64 = ty.arch() { - return Err(Error::UnsupportedFeature("64-bit memories")); - } let addr = store.state.memories.len() as MemAddr; store.state.memories.push(MemoryInstance::new(ty, &store.engine.config().memory_backend)?); Ok(Self::from_store_addr(store.id(), addr)) @@ -399,7 +395,8 @@ impl Table { /// Grow the table and return the previous size. pub fn grow(&self, store: &mut Store, delta: i32, init: WasmValue) -> Result<usize> { - let table = self.instance_mut(store)?; + self.0.validate_store(store)?; + let table = store.state.get_table_mut(self.0.addr); let old_size = table.size() as usize; let init = table_value_to_element(table.kind.element_type, init)?; table.grow(delta, init)?; @@ -415,6 +412,10 @@ impl Global { /// Create a new global in the given store. pub fn new(store: &mut Store, ty: GlobalType, value: WasmValue) -> Result<Self> { + if WasmType::from(value) != ty.ty { + cold_path(); + return Err(Error::Other("invalid global value type".to_string())); + } let addr = store.state.globals.len() as GlobalAddr; store.state.globals.push(GlobalInstance::new(ty, value.into())); Ok(Self::from_store_addr(store.id(), addr)) diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs index 50ad520..2e58939 100644 --- a/crates/tinywasm/src/store/memory/mod.rs +++ b/crates/tinywasm/src/store/memory/mod.rs @@ -103,8 +103,8 @@ pub trait LinearMemory { let mut offset = 0; while offset < len { let chunk_len = min(len - offset, 1024); - let chunk = vec![0; chunk_len]; - self.read_exact(src + offset, &mut chunk.clone())?; + let mut chunk = vec![0; chunk_len]; + self.read_exact(src + offset, &mut chunk)?; self.write_all(dst + offset, &chunk)?; offset += chunk_len; } @@ -114,8 +114,8 @@ pub trait LinearMemory { while offset > 0 { let chunk_len = min(offset, 1024); offset -= chunk_len; - let chunk = vec![0; chunk_len]; - self.read_exact(src + offset, &mut chunk.clone())?; + let mut chunk = vec![0; chunk_len]; + self.read_exact(src + offset, &mut chunk)?; self.write_all(dst + offset, &chunk)?; } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 2b8ba73..a4d26e3 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -249,7 +249,7 @@ impl State { } /// Get the global at the actual index in the store - pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { + pub(crate) fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { match self.globals.get(addr as usize) { Some(global) => global.value.get(), None => { @@ -260,7 +260,7 @@ impl State { } /// Set the global at the actual index in the store - pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + pub(crate) fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { match self.globals.get_mut(addr as usize) { Some(global) => global.value.set(value), None => { @@ -288,13 +288,13 @@ impl Store { /// Get the global at the actual index in the store #[doc(hidden)] - pub fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { + pub fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { self.state.get_global_val(addr) } /// Set the global at the actual index in the store #[doc(hidden)] - pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + pub fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { self.state.set_global_val(addr, value); } } diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs index 712148e..e7948fa 100644 --- a/crates/tinywasm/tests/import_linking.rs +++ b/crates/tinywasm/tests/import_linking.rs @@ -1,5 +1,5 @@ use eyre::Result; -use tinywasm::{Error, Imports, Module, ModuleInstance, Store}; +use tinywasm::{Error, Imports, Module, ModuleInstance, Store, Trap}; const WASM_ADD: &str = r#" (module @@ -52,6 +52,6 @@ fn link_module_rejects_cross_store_instance() -> Result<()> { imports.link_module("adder", add_instance)?; let err = ModuleInstance::instantiate(&mut target_store, &import_module, Some(imports)).unwrap_err(); - assert!(matches!(err, Error::InvalidStore)); + assert_eq!(err, Error::from(Trap::InvalidStore)); Ok(()) } diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index 9e96d4c..2382597 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -94,3 +94,100 @@ fn extern_item_lookup_returns_expected_kinds() -> Result<()> { Ok(()) } + +#[test] +fn extern_item_and_exports_use_actual_function_type() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (type $local_ty (func)) + (type $import_ty (func (param i64))) + (import "host" "imported" (func (type $import_ty))) + (func (export "f") (type $local_ty) + nop) + ) + "#, + )?; + + let module = tinywasm::parse_bytes(&wasm)?; + let mut store = Store::default(); + let mut imports = tinywasm::Imports::new(); + imports.define( + "host", + "imported", + tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), + ); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + + let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") }; + assert_eq!(func.call(&mut store, &[])?, vec![]); + + let (_, ExternItem::Func(func)) = instance.exports().find(|(name, _)| *name == "f").expect("export f not found") + else { + panic!("expected function export") + }; + assert_eq!(func.call(&mut store, &[])?, vec![]); + + Ok(()) +} + +#[test] +fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (type $local_ty (func)) + (type $import_ty (func (param i64))) + (import "spectest" "print_i64" (func (type $import_ty))) + (func (export "f") (type $local_ty) + nop) + ) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + + let export = module.exports.iter().find(|export| export.name.as_ref() == "f").expect("export f not found"); + let old_lookup_ty = module.func_types.get(export.index as usize).expect("old lookup type missing"); + + assert_eq!(old_lookup_ty.params(), &[tinywasm::types::WasmType::I64]); + assert_eq!(module.funcs[0].ty.params(), &[]); + assert_ne!(old_lookup_ty.params(), module.funcs[0].ty.params()); + + let mut store = Store::default(); + let mut imports = tinywasm::Imports::new(); + imports.define( + "spectest", + "print_i64", + tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), + ); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + + let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") }; + assert_eq!(func.call(&mut store, &[])?, vec![]); + + Ok(()) +} + +#[test] +fn start_prefers_exported_start_without_re_resolving_store_addr() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (global (export "g") (mut i32) (i32.const 0)) + (func (export "_start") + i32.const 1 + global.set 0) + ) + "#, + )?; + + let module = tinywasm::parse_bytes(&wasm)?; + let mut store = Store::default(); + let _unused = tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, (): ()| Ok(())); + let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?; + + instance.start(&mut store)?; + assert_eq!(instance.global_get(&store, "g")?, WasmValue::I32(1)); + + Ok(()) +} diff --git a/crates/tinywasm/tests/module_descriptors.rs b/crates/tinywasm/tests/module_descriptors.rs index 3804df8..d7fabfa 100644 --- a/crates/tinywasm/tests/module_descriptors.rs +++ b/crates/tinywasm/tests/module_descriptors.rs @@ -89,3 +89,66 @@ fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> { Ok(()) } + +#[test] +fn module_descriptors_resolve_imported_and_local_table_and_memory_exports() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (import "host" "itable" (table 2 4 funcref)) + (import "host" "imemory" (memory 1 3)) + (table $ltable 5 7 funcref) + (memory $lmemory 2 6) + (export "itable_export" (table 0)) + (export "imemory_export" (memory 0)) + (export "ltable_export" (table 1)) + (export "lmemory_export" (memory 1)) + ) + "#, + )?; + + let module = tinywasm::parse_bytes(&wasm)?; + let exports: Vec<_> = module.exports().collect(); + + let itable_export = exports.iter().find(|export| export.name == "itable_export").expect("itable export not found"); + match itable_export.ty { + ExportType::Table(ty) => { + assert_eq!(ty.element_type, WasmType::RefFunc); + assert_eq!(ty.size_initial, 2); + assert_eq!(ty.size_max, Some(4)); + } + _ => panic!("itable export should resolve to imported table type"), + } + + let imemory_export = + exports.iter().find(|export| export.name == "imemory_export").expect("imemory export not found"); + match imemory_export.ty { + ExportType::Memory(ty) => { + assert_eq!(ty.page_count_initial(), 1); + assert_eq!(ty.page_count_max(), 3); + } + _ => panic!("imemory export should resolve to imported memory type"), + } + + let ltable_export = exports.iter().find(|export| export.name == "ltable_export").expect("ltable export not found"); + match ltable_export.ty { + ExportType::Table(ty) => { + assert_eq!(ty.element_type, WasmType::RefFunc); + assert_eq!(ty.size_initial, 5); + assert_eq!(ty.size_max, Some(7)); + } + _ => panic!("ltable export should resolve to local table type"), + } + + let lmemory_export = + exports.iter().find(|export| export.name == "lmemory_export").expect("lmemory export not found"); + match lmemory_export.ty { + ExportType::Memory(ty) => { + assert_eq!(ty.page_count_initial(), 2); + assert_eq!(ty.page_count_max(), 6); + } + _ => panic!("lmemory export should resolve to local memory type"), + } + + Ok(()) +} diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs index acf8e42..61cc88c 100644 --- a/crates/tinywasm/tests/store_ownership.rs +++ b/crates/tinywasm/tests/store_ownership.rs @@ -38,7 +38,51 @@ fn memory_access_rejects_wrong_store() -> Result<()> { let memory = instance.memory("memory")?; let other_store = Store::default(); let err = memory.len(&other_store).unwrap_err(); - assert!(err.to_string().contains("invalid store")); + assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore)); + + Ok(()) +} + +#[test] +fn global_access_rejects_wrong_store() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (global (export "g") (mut i32) (i32.const 1)) + ) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + + let mut owner_store = Store::default(); + let instance = ModuleInstance::instantiate(&mut owner_store, &module, None)?; + let global = instance.global("g")?; + + let other_store = Store::default(); + let err = global.get(&other_store).unwrap_err(); + assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore)); + + Ok(()) +} + +#[test] +fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (table (export "t") 1 funcref) + ) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + + let mut owner_store = Store::default(); + let instance = ModuleInstance::instantiate(&mut owner_store, &module, None)?; + let table = instance.table("t")?; + + let mut other_store = Store::default(); + let err = table.grow(&mut other_store, 1, tinywasm::types::FuncRef::null().into()).unwrap_err(); + assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore)); Ok(()) } diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 2636230..92bb87a 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -23,7 +23,7 @@ fn validate_magic(wasm: &[u8]) -> Result<usize, TwasmError> { Ok(TWASM_MAGIC.len()) } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum TwasmError { InvalidMagic, InvalidVersion, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index b65e4b7..8057518 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -101,6 +101,9 @@ pub struct ModuleInner { /// Corresponds to the `type` section of the original WebAssembly module. pub func_types: Arc<[Arc<FuncType>]>, + /// Function index to type index mapping in module index space, including imports. + pub func_type_idxs: Arc<[u32]>, + /// Exported items of the WebAssembly module. /// /// Corresponds to the `export` section of the original WebAssembly module. @@ -161,6 +164,22 @@ impl Module { /// /// The returned data mirrors the module's export section and preserves order. pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> { + fn imported_count(module: &ModuleInner, kind: ExternalKind) -> usize { + module + .imports + .iter() + .filter(|import| { + matches!( + (kind, &import.kind), + (ExternalKind::Func, ImportKind::Function(_)) + | (ExternalKind::Table, ImportKind::Table(_)) + | (ExternalKind::Memory, ImportKind::Memory(_)) + | (ExternalKind::Global, ImportKind::Global(_)) + ) + }) + .count() + } + fn imported_func_type(module: &ModuleInner, function_index: usize) -> Option<&FuncType> { let mut seen = 0usize; for import in module.imports.iter() { @@ -174,6 +193,32 @@ impl Module { None } + fn imported_table_type(module: &ModuleInner, table_index: usize) -> Option<&TableType> { + let mut seen = 0usize; + for import in module.imports.iter() { + if let ImportKind::Table(table_ty) = &import.kind { + if seen == table_index { + return Some(table_ty); + } + seen += 1; + } + } + None + } + + fn imported_memory_type(module: &ModuleInner, memory_index: usize) -> Option<&MemoryType> { + let mut seen = 0usize; + for import in module.imports.iter() { + if let ImportKind::Memory(memory_ty) = &import.kind { + if seen == memory_index { + return Some(memory_ty); + } + seen += 1; + } + } + None + } + fn imported_global_type(module: &Module, global_index: usize) -> Option<&GlobalType> { let mut seen = 0usize; for import in module.imports.iter() { @@ -188,12 +233,10 @@ impl Module { } self.0.exports.iter().filter_map(move |export| { - let imports = self.0.imports.iter(); let idx = export.index as usize; let ty = match export.kind { ExternalKind::Func => { - let imported_funcs = - imports.filter(|import| matches!(import.kind, ImportKind::Function(_))).count(); + let imported_funcs = imported_count(&self.0, ExternalKind::Func); if idx < imported_funcs { ExportType::Func(imported_func_type(&self.0, idx)?) } else { @@ -201,11 +244,26 @@ impl Module { ExportType::Func(&self.0.funcs.get(local_idx)?.ty) } } - ExternalKind::Table => ExportType::Table(self.0.table_types.get(idx)?), - ExternalKind::Memory => ExportType::Memory(self.0.memory_types.get(idx)?), + ExternalKind::Table => { + let imported_tables = imported_count(&self.0, ExternalKind::Table); + if idx < imported_tables { + ExportType::Table(imported_table_type(&self.0, idx)?) + } else { + let local_idx = idx - imported_tables; + ExportType::Table(self.0.table_types.get(local_idx)?) + } + } + ExternalKind::Memory => { + let imported_memories = imported_count(&self.0, ExternalKind::Memory); + if idx < imported_memories { + ExportType::Memory(imported_memory_type(&self.0, idx)?) + } else { + let local_idx = idx - imported_memories; + ExportType::Memory(self.0.memory_types.get(local_idx)?) + } + } ExternalKind::Global => { - let imported_globals = - imports.filter(|import| matches!(import.kind, ImportKind::Global(_))).count(); + let imported_globals = imported_count(&self.0, ExternalKind::Global); if idx < imported_globals { ExportType::Global(imported_global_type(self, idx)?) } else { diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 20c822e..c57edc0 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -341,8 +341,7 @@ impl From<WasmValue> for WasmType { } /// Type of a WebAssembly value. -#[derive(Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum WasmType { /// A 32-bit integer. |
