From cc77db8337d177e6dac590c4f31b24329fffcafe Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Thu, 5 Feb 2026 15:19:30 +0100 Subject: [PATCH 01/11] feat(validation): error on too many funcs,tables,mems,globals Signed-off-by: Florian Hartung --- src/core/error.rs | 16 ++++++++++++++++ src/validation/mod.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/core/error.rs b/src/core/error.rs index 37b7b4140..ce594a15d 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -153,6 +153,18 @@ pub enum ValidationError { /// The mode of an element was invalid. Only values in the range 0..=7 are /// allowed. InvalidElementMode(u32), + /// The module contains too many functions, i.e. imported or locally-defined + /// functions. The maximum number of functions is [`u32::MAX`]. + TooManyFunctions, + /// The module contains too many tables, i.e. imported or locally-defined + /// tables. The maximum number of tables is [`u32::MAX`]. + TooManyTables, + /// The module contains too many memories, i.e. imported or locally-defined + /// memories. The maximum number of memories is [`u32::MAX`]. + TooManyMemories, + /// The module contains too many globals, i.e. imported or locally-defined + /// globals. The maximum number of memories is [`u32::MAX`]. + TooManyGlobals, } impl core::error::Error for ValidationError {} @@ -226,6 +238,10 @@ impl Display for ValidationError { ValidationError::MissingDataCountSection => f.write_str("Some instructions could not be validated because the data count section is missing"), ValidationError::InvalidDataSegmentMode(mode) => write!(f, "The mode of a data segment was invalid (only 0..=2 is allowed): {mode}"), ValidationError::InvalidElementMode(mode) => write!(f, "The mode of an element was invalid (only 0..=7 is allowed): {mode}"), + ValidationError::TooManyFunctions => f.write_str("The module contains too many functions. The maximum number of functions (either imported or locally-defined) is 2^32 - 1"), + ValidationError::TooManyTables => f.write_str("The module contains too many tables. The maximum number of tables (either imported or locally-defined) is 2^32 - 1"), + ValidationError::TooManyMemories => f.write_str("The module contains too many memories. The maximum number of memories (either imported or locally-defined) is 2^32 - 1"), + ValidationError::TooManyGlobals => f.write_str("The module contains too many globals. The maximum number of globals (either imported or locally-defined) is 2^32 - 1"), } } } diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 7b5c5c01a..d12e376a2 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -237,6 +237,13 @@ pub fn validate(wasm: &[u8]) -> Result, ValidationError> { .chain(local_functions.iter().cloned()) .collect::>(); + // All functions need to be addressable by a u32 + if all_functions.len() + > usize::try_from(u32::MAX).expect("pointer width to be at least 32 bits") + { + return Err(ValidationError::TooManyFunctions); + } + while (skip_section(&mut wasm, &mut header)?).is_some() {} let imported_tables = imports @@ -257,6 +264,11 @@ pub fn validate(wasm: &[u8]) -> Result, ValidationError> { temp }; + // All tables need to be addressable by a u32 + if all_tables.len() > usize::try_from(u32::MAX).expect("pointer width to be at least 32 bits") { + return Err(ValidationError::TooManyTables); + } + while (skip_section(&mut wasm, &mut header)?).is_some() {} let imported_memories = imports @@ -277,10 +289,18 @@ pub fn validate(wasm: &[u8]) -> Result, ValidationError> { temp.extend(memories.clone()); temp }; + if all_memories.len() > 1 { return Err(ValidationError::UnsupportedMultipleMemoriesProposal); } + // Note: Without the multiple memory proposal, this is dead code due to the previous check. + // All memories need to be addressable by a u32 + if all_memories.len() > usize::try_from(u32::MAX).expect("pointer width to be at least 32 bits") + { + return Err(ValidationError::TooManyMemories); + } + while (skip_section(&mut wasm, &mut header)?).is_some() {} // we start off with the imported globals @@ -313,6 +333,12 @@ pub fn validate(wasm: &[u8]) -> Result, ValidationError> { all_globals.push(*item) } + // All globals need to be addressable by a u32 + if all_globals.len() > usize::try_from(u32::MAX).expect("pointer width to be at least 32 bits") + { + return Err(ValidationError::TooManyGlobals); + } + while (skip_section(&mut wasm, &mut header)?).is_some() {} let exports = handle_section(&mut wasm, &mut header, SectionTy::Export, |wasm, _| { From accec0887825baa3f8460329801a482431aa1aa1 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Thu, 5 Feb 2026 17:28:36 +0100 Subject: [PATCH 02/11] fix(core): remove unused ExportDesc methods Signed-off-by: Florian Hartung --- src/core/reader/types/export.rs | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/core/reader/types/export.rs b/src/core/reader/types/export.rs index 86c30e7b0..243f0c887 100644 --- a/src/core/reader/types/export.rs +++ b/src/core/reader/types/export.rs @@ -44,6 +44,7 @@ impl ExportDesc { /// /// Note: This method may panic if `self` does not come from the given [`ValidationInfo`]. /// + #[allow(unused)] // reason = "this function is analogous to ImportDesc::extern_type, however it is not yet clear if it is needed in the future" pub fn extern_type(&self, validation_info: &ValidationInfo) -> ExternType { // TODO clean up logic for checking if an exported definition is an // import @@ -125,34 +126,6 @@ impl ExportDesc { } } } - - pub fn get_function_idx(&self) -> Option { - match self { - ExportDesc::FuncIdx(func_idx) => Some(*func_idx), - _ => None, - } - } - - pub fn get_global_idx(&self) -> Option { - match self { - ExportDesc::GlobalIdx(global_idx) => Some(*global_idx), - _ => None, - } - } - - pub fn get_memory_idx(&self) -> Option { - match self { - ExportDesc::MemIdx(mem_idx) => Some(*mem_idx), - _ => None, - } - } - - pub fn get_table_idx(&self) -> Option { - match self { - ExportDesc::TableIdx(table_idx) => Some(*table_idx), - _ => None, - } - } } impl ExportDesc { From f307615488133e892f8742290d0d76c27f8a2c20 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Thu, 5 Feb 2026 17:19:57 +0100 Subject: [PATCH 03/11] fix: don't export ExportDesc Signed-off-by: Florian Hartung --- src/lib.rs | 4 ++-- src/validation/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0422eda34..e26ca4c70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ extern crate log_wrapper; pub use core::error::ValidationError; pub use core::reader::types::{ - export::ExportDesc, global::GlobalType, ExternType, FuncType, Limits, MemType, NumType, - RefType, ResultType, TableType, ValType, + global::GlobalType, ExternType, FuncType, Limits, MemType, NumType, RefType, ResultType, + TableType, ValType, }; pub use core::rw_spinlock; pub use execution::error::{RuntimeError, TrapError}; diff --git a/src/validation/mod.rs b/src/validation/mod.rs index d12e376a2..93c5445bf 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -8,13 +8,13 @@ use crate::core::reader::section_header::{SectionHeader, SectionTy}; use crate::core::reader::span::Span; use crate::core::reader::types::data::DataSegment; use crate::core::reader::types::element::ElemType; -use crate::core::reader::types::export::Export; +use crate::core::reader::types::export::{Export, ExportDesc}; use crate::core::reader::types::global::{Global, GlobalType}; use crate::core::reader::types::import::{Import, ImportDesc}; use crate::core::reader::types::{ExternType, FuncType, MemType, ResultType, TableType}; use crate::core::reader::WasmReader; use crate::core::sidetable::Sidetable; -use crate::{ExportDesc, ValidationError}; +use crate::ValidationError; pub(crate) mod code; pub(crate) mod data; From 8b009d9ce22b7527d13c7aa4295ac0795b2a6be8 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Thu, 5 Feb 2026 17:35:01 +0100 Subject: [PATCH 04/11] refactor(core): rename ExportDesc variants Signed-off-by: Florian Hartung --- src/core/reader/types/export.rs | 25 ++++++++++++------------- src/execution/store/mod.rs | 10 ++++------ src/validation/mod.rs | 10 +++++----- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/core/reader/types/export.rs b/src/core/reader/types/export.rs index 243f0c887..6916b0f8f 100644 --- a/src/core/reader/types/export.rs +++ b/src/core/reader/types/export.rs @@ -26,16 +26,15 @@ impl Export { #[derive(Debug, Clone)] #[allow(clippy::all)] -// TODO: change enum labels from FuncIdx -> Func pub enum ExportDesc { #[allow(warnings)] - FuncIdx(FuncIdx), + Func(FuncIdx), #[allow(warnings)] - TableIdx(TableIdx), + Table(TableIdx), #[allow(warnings)] - MemIdx(MemIdx), + Mem(MemIdx), #[allow(warnings)] - GlobalIdx(GlobalIdx), + Global(GlobalIdx), } impl ExportDesc { @@ -49,7 +48,7 @@ impl ExportDesc { // TODO clean up logic for checking if an exported definition is an // import match self { - ExportDesc::FuncIdx(func_idx) => { + ExportDesc::Func(func_idx) => { let type_idx = match func_idx .checked_sub(validation_info.imports_length.imported_functions) { @@ -71,7 +70,7 @@ impl ExportDesc { // TODO ugly clone that should disappear when types are directly parsed from bytecode instead of vector copies ExternType::Func(func_type.clone()) } - ExportDesc::TableIdx(table_idx) => { + ExportDesc::Table(table_idx) => { let table_type = match table_idx .checked_sub(validation_info.imports_length.imported_tables) { @@ -88,7 +87,7 @@ impl ExportDesc { }; ExternType::Table(table_type) } - ExportDesc::MemIdx(mem_idx) => { + ExportDesc::Mem(mem_idx) => { let mem_type = match mem_idx .checked_sub(validation_info.imports_length.imported_memories) { @@ -105,7 +104,7 @@ impl ExportDesc { }; ExternType::Mem(mem_type) } - ExportDesc::GlobalIdx(global_idx) => { + ExportDesc::Global(global_idx) => { let global_type = match global_idx.checked_sub(validation_info.imports_length.imported_globals) { Some(local_global_idx) => { @@ -134,10 +133,10 @@ impl ExportDesc { let desc_idx = wasm.read_var_u32()? as usize; let desc = match desc_id { - 0x00 => ExportDesc::FuncIdx(desc_idx), - 0x01 => ExportDesc::TableIdx(desc_idx), - 0x02 => ExportDesc::MemIdx(desc_idx), - 0x03 => ExportDesc::GlobalIdx(desc_idx), + 0x00 => ExportDesc::Func(desc_idx), + 0x01 => ExportDesc::Table(desc_idx), + 0x02 => ExportDesc::Mem(desc_idx), + 0x03 => ExportDesc::Global(desc_idx), other => return Err(ValidationError::MalformedExportDescDiscriminator(other)), }; Ok(desc) diff --git a/src/execution/store/mod.rs b/src/execution/store/mod.rs index ed5f69e57..91b9262d5 100644 --- a/src/execution/store/mod.rs +++ b/src/execution/store/mod.rs @@ -290,14 +290,12 @@ impl<'b, T: Config> Store<'b, T> { .map(|Export { name, desc }| { let module_inst = self.modules.get(module_addr); let value = match desc { - ExportDesc::FuncIdx(func_idx) => { + ExportDesc::Func(func_idx) => { ExternVal::Func(module_inst.func_addrs[*func_idx]) } - ExportDesc::TableIdx(table_idx) => { - ExternVal::Table(table_addrs_mod[*table_idx]) - } - ExportDesc::MemIdx(mem_idx) => ExternVal::Mem(mem_addrs_mod[*mem_idx]), - ExportDesc::GlobalIdx(global_idx) => { + ExportDesc::Table(table_idx) => ExternVal::Table(table_addrs_mod[*table_idx]), + ExportDesc::Mem(mem_idx) => ExternVal::Mem(mem_addrs_mod[*mem_idx]), + ExportDesc::Global(global_idx) => { ExternVal::Global(module_inst.global_addrs[*global_idx]) } }; diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 93c5445bf..c0e22d246 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -69,7 +69,7 @@ fn validate_exports(validation_info: &ValidationInfo) -> Result<(), ValidationEr } found_export_names.insert(export.name.as_str()); match export.desc { - FuncIdx(func_idx) => { + Func(func_idx) => { if validation_info.functions.len() + validation_info.imports_length.imported_functions <= func_idx @@ -77,21 +77,21 @@ fn validate_exports(validation_info: &ValidationInfo) -> Result<(), ValidationEr return Err(ValidationError::InvalidFuncIdx(func_idx)); } } - TableIdx(table_idx) => { + Table(table_idx) => { if validation_info.tables.len() + validation_info.imports_length.imported_tables <= table_idx { return Err(ValidationError::InvalidTableIdx(table_idx)); } } - MemIdx(mem_idx) => { + Mem(mem_idx) => { if validation_info.memories.len() + validation_info.imports_length.imported_memories <= mem_idx { return Err(ValidationError::InvalidMemIdx(mem_idx)); } } - GlobalIdx(global_idx) => { + Global(global_idx) => { if validation_info.globals.len() + validation_info.imports_length.imported_globals <= global_idx { @@ -347,7 +347,7 @@ pub fn validate(wasm: &[u8]) -> Result, ValidationError> { .unwrap_or_default(); validation_context_refs.extend(exports.iter().filter_map( |Export { name: _, desc }| match *desc { - ExportDesc::FuncIdx(func_idx) => Some(func_idx), + ExportDesc::Func(func_idx) => Some(func_idx), _ => None, }, )); From d43b45ac2ea7d33c694880783f141a23e02c217b Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Thu, 5 Feb 2026 17:55:08 +0100 Subject: [PATCH 05/11] refactor: remove unnecessarily allowed unused/dead_code lints Signed-off-by: Florian Hartung --- src/core/indices.rs | 1 - src/core/reader/mod.rs | 2 -- src/core/reader/types/export.rs | 7 ------- src/core/reader/types/import.rs | 3 --- src/core/reader/types/mod.rs | 1 - src/core/slotmap.rs | 2 +- src/execution/config.rs | 3 +-- src/execution/resumable.rs | 7 +------ src/validation/mod.rs | 1 - tests/specification/reports.rs | 2 -- 10 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/core/indices.rs b/src/core/indices.rs index 13e8c4c9f..75cd1015b 100644 --- a/src/core/indices.rs +++ b/src/core/indices.rs @@ -24,5 +24,4 @@ pub type GlobalIdx = usize; pub type ElemIdx = usize; pub type DataIdx = usize; pub type LocalIdx = usize; -#[allow(dead_code)] pub type LabelIdx = usize; diff --git a/src/core/reader/mod.rs b/src/core/reader/mod.rs index 8e1583b84..25d4edfc8 100644 --- a/src/core/reader/mod.rs +++ b/src/core/reader/mod.rs @@ -137,7 +137,6 @@ impl<'a> WasmReader<'a> { /// more than 0 further bytes would panick. However, it can not move the [`pc`](Self::pc) any /// further than that, instead an error is returned. For further information, refer to the /// [field documentation of `pc`] (WasmReader::pc). - #[allow(dead_code)] pub fn skip(&mut self, num_bytes: usize) -> Result<(), ValidationError> { if num_bytes > self.full_wasm_binary.len() - self.pc { return Err(ValidationError::Eof); @@ -155,7 +154,6 @@ impl<'a> WasmReader<'a> { /// /// The provided closure will be called with `&mut self` and its result will be returned. /// However if the closure returns `Err(_)`, `self` will be reset as if the closure was never called. - #[allow(dead_code)] pub fn handle_transaction( &mut self, f: impl FnOnce(&mut WasmReader<'a>) -> Result, diff --git a/src/core/reader/types/export.rs b/src/core/reader/types/export.rs index 6916b0f8f..54c9c399e 100644 --- a/src/core/reader/types/export.rs +++ b/src/core/reader/types/export.rs @@ -10,9 +10,7 @@ use super::ExternType; #[derive(Debug, Clone)] pub struct Export { - #[allow(dead_code)] pub name: String, - #[allow(dead_code)] pub desc: ExportDesc, } @@ -25,15 +23,10 @@ impl Export { } #[derive(Debug, Clone)] -#[allow(clippy::all)] pub enum ExportDesc { - #[allow(warnings)] Func(FuncIdx), - #[allow(warnings)] Table(TableIdx), - #[allow(warnings)] Mem(MemIdx), - #[allow(warnings)] Global(GlobalIdx), } diff --git a/src/core/reader/types/import.rs b/src/core/reader/types/import.rs index 494589c7e..c40331cb6 100644 --- a/src/core/reader/types/import.rs +++ b/src/core/reader/types/import.rs @@ -10,11 +10,8 @@ use super::{ExternType, MemType, TableType}; #[derive(Debug, Clone)] pub struct Import { - #[allow(warnings)] pub module_name: String, - #[allow(warnings)] pub name: String, - #[allow(warnings)] pub desc: ImportDesc, } diff --git a/src/core/reader/types/mod.rs b/src/core/reader/types/mod.rs index 69f73174c..73761f3cb 100644 --- a/src/core/reader/types/mod.rs +++ b/src/core/reader/types/mod.rs @@ -84,7 +84,6 @@ impl RefType { /// /// TODO flatten [NumType] and [RefType] enums, as they are not used individually and `wasmparser` also does it. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[allow(clippy::all)] pub enum ValType { NumType(NumType), VecType, diff --git a/src/core/slotmap.rs b/src/core/slotmap.rs index a2ae32d37..a5ba0a8b5 100644 --- a/src/core/slotmap.rs +++ b/src/core/slotmap.rs @@ -68,7 +68,7 @@ impl SlotMap { } } - #[allow(unused)] + #[allow(unused)] // reason = "this function might be used in the future" pub fn get(&self, key: &SlotMapKey) -> Option<&T> { let slot = self.slots.get(key.index)?; if slot.generation != key.generation { diff --git a/src/execution/config.rs b/src/execution/config.rs index 660b554da..1f8fc2c59 100644 --- a/src/execution/config.rs +++ b/src/execution/config.rs @@ -15,9 +15,8 @@ pub trait Config { /// /// This allows the most intricate insight into the interpreters behavior, at the cost of a /// hefty performance penalty - #[allow(unused_variables)] #[inline(always)] - fn instruction_hook(&mut self, bytecode: &[u8], pc: usize) {} + fn instruction_hook(&mut self, _bytecode: &[u8], _pc: usize) {} /// Amount of fuel to be deducted when a single byte `instr` is hit. The cost corresponding to `UNREACHABLE` and /// `END` instructions and other bytes that do not correspond to any Wasm instruction are ignored. diff --git a/src/execution/resumable.rs b/src/execution/resumable.rs index d1f953ce4..4e1e31917 100644 --- a/src/execution/resumable.rs +++ b/src/execution/resumable.rs @@ -26,11 +26,6 @@ pub(crate) struct Resumable { pub(crate) struct Dormitory(pub(crate) Arc>>); impl Dormitory { - #[allow(unused)] - pub(crate) fn new() -> Self { - Self::default() - } - pub(crate) fn insert(&self, resumable: Resumable) -> InvokedResumableRef { let key = self.0.write().insert(resumable); @@ -103,7 +98,7 @@ mod test { /// Test that a dormitory can be constructed and that a resumable can be inserted #[test] fn dormitory_constructor() { - let dorm = Dormitory::new(); + let dorm = Dormitory::default(); let empty_stack = Stack::new::<()>( Vec::new(), diff --git a/src/validation/mod.rs b/src/validation/mod.rs index c0e22d246..d887c3e86 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -46,7 +46,6 @@ pub struct ValidationInfo<'bytecode> { pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) globals: Vec, - #[allow(dead_code)] pub(crate) exports: Vec, /// Each block contains the validated code section and the stp corresponding to /// the beginning of that code section diff --git a/tests/specification/reports.rs b/tests/specification/reports.rs index 264358b21..4af8a4209 100644 --- a/tests/specification/reports.rs +++ b/tests/specification/reports.rs @@ -148,9 +148,7 @@ pub struct ScriptError { /// Boxed because of struct size pub error: Box, pub context: String, - #[allow(unused)] pub line_number: Option, - #[allow(unused)] pub command: Option, } From ec7fdbc93d0d18557eefcfce3cc502281ead42d7 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Fri, 6 Feb 2026 12:55:59 +0100 Subject: [PATCH 06/11] fix: make (elem|data).drop operations infallible Signed-off-by: Florian Hartung --- src/execution/interpreter_loop.rs | 10 ++++------ src/execution/store/mod.rs | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/execution/interpreter_loop.rs b/src/execution/interpreter_loop.rs index 6a3348d40..27dced7e8 100644 --- a/src/execution/interpreter_loop.rs +++ b/src/execution/interpreter_loop.rs @@ -2520,7 +2520,7 @@ pub(super) fn run( DATA_DROP => { decrement_fuel!(T::get_fc_extension_flat_cost(DATA_DROP)); let data_idx = wasm.read_var_u32().unwrap_validated() as DataIdx; - data_drop(&store.modules, &mut store.data, current_module, data_idx)?; + data_drop(&store.modules, &mut store.data, current_module, data_idx); } // See https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-memory-mathsf-memory-copy MEMORY_COPY => { @@ -2663,7 +2663,7 @@ pub(super) fn run( &mut store.elements, current_module, elem_idx, - )?; + ); } // https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-copy-x-y TABLE_COPY => { @@ -5683,7 +5683,7 @@ pub(super) fn elem_drop( store_elements: &mut AddrVec, current_module: ModuleAddr, elem_idx: usize, -) -> Result<(), RuntimeError> { +) { // WARN: i'm not sure if this is okay or not let elem_addr = *store_modules .get(current_module) @@ -5691,7 +5691,6 @@ pub(super) fn elem_drop( .get(elem_idx) .unwrap_validated(); store_elements.get_mut(elem_addr).references.clear(); - Ok(()) } #[inline(always)] @@ -5737,7 +5736,7 @@ pub(super) fn data_drop( store_data: &mut AddrVec, current_module: ModuleAddr, data_idx: usize, -) -> Result<(), RuntimeError> { +) { // Here is debatable // If we were to be on par with the spec we'd have to use a DataInst struct // But since memory.init is specifically made for Passive data segments @@ -5751,7 +5750,6 @@ pub(super) fn data_drop( .get(data_idx) .unwrap_validated(); store_data.get_mut(data_addr).data.clear(); - Ok(()) } #[inline(always)] diff --git a/src/execution/store/mod.rs b/src/execution/store/mod.rs index 91b9262d5..3b70df5b4 100644 --- a/src/execution/store/mod.rs +++ b/src/execution/store/mod.rs @@ -357,14 +357,14 @@ impl<'b, T: Config> Store<'b, T> { s, d, )?; - elem_drop(&self.modules, &mut self.elements, module_addr, i)?; + elem_drop(&self.modules, &mut self.elements, module_addr, i); } ElemMode::Declarative => { // instantiation step 15: // TODO (for now, we are doing hopefully what is equivalent to it) // execute: // elem.drop i - elem_drop(&self.modules, &mut self.elements, module_addr, i)?; + elem_drop(&self.modules, &mut self.elements, module_addr, i); } ElemMode::Passive => (), } @@ -409,7 +409,7 @@ impl<'b, T: Config> Store<'b, T> { s, d, )?; - data_drop(&self.modules, &mut self.data, module_addr, i)?; + data_drop(&self.modules, &mut self.data, module_addr, i); } crate::core::reader::types::data::DataMode::Passive => (), } From 171ba57557ec03df21bacf5de12c2809b43b68be Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Mon, 9 Feb 2026 21:52:06 +0100 Subject: [PATCH 07/11] refactor(execution): make FuncInst::ty non-cloning Signed-off-by: Florian Hartung --- src/execution/interpreter_loop.rs | 14 +++++++------- src/execution/store/instances.rs | 6 +++--- src/execution/store/mod.rs | 6 ++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/execution/interpreter_loop.rs b/src/execution/interpreter_loop.rs index 27dced7e8..34ee40bad 100644 --- a/src/execution/interpreter_loop.rs +++ b/src/execution/interpreter_loop.rs @@ -246,8 +246,8 @@ pub(super) fn run( if returns.len() != func_to_call_ty.returns.valtypes.len() { return Err(RuntimeError::HostFunctionSignatureMismatch); } - for (value, ty) in zip(returns, func_to_call_ty.returns.valtypes) { - if value.to_ty() != ty { + for (value, ty) in zip(returns, &func_to_call_ty.returns.valtypes) { + if value.to_ty() != *ty { return Err(RuntimeError::HostFunctionSignatureMismatch); } stack.push_value::(value)?; @@ -258,7 +258,7 @@ pub(super) fn run( stack.push_call_frame::( current_func_addr, - &func_to_call_ty, + func_to_call_ty, remaining_locals, wasm.pc, stp, @@ -321,7 +321,7 @@ pub(super) fn run( }; let func_to_call_ty = store.functions.get(func_to_call_addr).ty(); - if *func_ty != func_to_call_ty { + if func_ty != func_to_call_ty { return Err(TrapError::SignatureMismatch.into()); } @@ -344,8 +344,8 @@ pub(super) fn run( if returns.len() != func_to_call_ty.returns.valtypes.len() { return Err(RuntimeError::HostFunctionSignatureMismatch); } - for (value, ty) in zip(returns, func_to_call_ty.returns.valtypes) { - if value.to_ty() != ty { + for (value, ty) in zip(returns, &func_to_call_ty.returns.valtypes) { + if value.to_ty() != *ty { return Err(RuntimeError::HostFunctionSignatureMismatch); } stack.push_value::(value)?; @@ -356,7 +356,7 @@ pub(super) fn run( stack.push_call_frame::( current_func_addr, - &func_to_call_ty, + func_to_call_ty, remaining_locals, wasm.pc, stp, diff --git a/src/execution/store/instances.rs b/src/execution/store/instances.rs index 9aec96cdc..71b3c40bb 100644 --- a/src/execution/store/instances.rs +++ b/src/execution/store/instances.rs @@ -47,10 +47,10 @@ pub struct HostFuncInst { } impl FuncInst { - pub fn ty(&self) -> FuncType { + pub fn ty(&self) -> &FuncType { match self { - FuncInst::WasmFunc(wasm_func_inst) => wasm_func_inst.function_type.clone(), - FuncInst::HostFunc(host_func_inst) => host_func_inst.function_type.clone(), + FuncInst::WasmFunc(wasm_func_inst) => &wasm_func_inst.function_type, + FuncInst::HostFunc(host_func_inst) => &host_func_inst.function_type, } } } diff --git a/src/execution/store/mod.rs b/src/execution/store/mod.rs index 3b70df5b4..30397162e 100644 --- a/src/execution/store/mod.rs +++ b/src/execution/store/mod.rs @@ -513,7 +513,7 @@ impl<'b, T: Config> Store<'b, T> { /// [`Store`] object. pub fn func_type_unchecked(&self, func_addr: FuncAddr) -> FuncType { // 1. Return `S.funcs[a].type`. - self.functions.get(func_addr).ty() + self.functions.get(func_addr).ty().clone() // 2. Post-condition: the returned function type is valid. } @@ -1354,7 +1354,9 @@ impl ExternVal { pub fn extern_type(&self, store: &Store) -> ExternType { match self { // TODO: fix ugly clone in function types - ExternVal::Func(func_addr) => ExternType::Func(store.functions.get(*func_addr).ty()), + ExternVal::Func(func_addr) => { + ExternType::Func(store.functions.get(*func_addr).ty().clone()) + } ExternVal::Table(table_addr) => ExternType::Table(store.tables.get(*table_addr).ty), ExternVal::Mem(mem_addr) => ExternType::Mem(store.memories.get(*mem_addr).ty), ExternVal::Global(global_addr) => { From 6e59e870d37186d77e0d8de75b6be71cfaf20272 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Mon, 9 Feb 2026 21:46:07 +0100 Subject: [PATCH 08/11] fix(execution): remove double lookup in call instructions Signed-off-by: Florian Hartung --- src/execution/interpreter_loop.rs | 41 +++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/execution/interpreter_loop.rs b/src/execution/interpreter_loop.rs index 34ee40bad..50ee32985 100644 --- a/src/execution/interpreter_loop.rs +++ b/src/execution/interpreter_loop.rs @@ -225,14 +225,16 @@ pub(super) fn run( .get(current_wasm_func_inst.module_addr) .func_addrs[local_func_idx]; - let func_to_call_ty = store.functions.get(func_to_call_addr).ty(); + let func_to_call_inst = store.functions.get(func_to_call_addr); trace!("Instruction: call [{func_to_call_addr:?}]"); - match store.functions.get(func_to_call_addr) { + match func_to_call_inst { FuncInst::HostFunc(host_func_to_call_inst) => { let params = stack - .pop_tail_iter(func_to_call_ty.params.valtypes.len()) + .pop_tail_iter( + host_func_to_call_inst.function_type.params.valtypes.len(), + ) .collect(); let returns = (host_func_to_call_inst.hostcode)(&mut store.user_data, params); @@ -243,10 +245,15 @@ pub(super) fn run( // Verify that the return parameters match the host function parameters // since we have no validation guarantees for host functions - if returns.len() != func_to_call_ty.returns.valtypes.len() { + if returns.len() + != host_func_to_call_inst.function_type.returns.valtypes.len() + { return Err(RuntimeError::HostFunctionSignatureMismatch); } - for (value, ty) in zip(returns, &func_to_call_ty.returns.valtypes) { + for (value, ty) in zip( + returns, + &host_func_to_call_inst.function_type.returns.valtypes, + ) { if value.to_ty() != *ty { return Err(RuntimeError::HostFunctionSignatureMismatch); } @@ -258,7 +265,7 @@ pub(super) fn run( stack.push_call_frame::( current_func_addr, - func_to_call_ty, + &wasm_func_to_call_inst.function_type, remaining_locals, wasm.pc, stp, @@ -320,17 +327,20 @@ pub(super) fn run( Ref::Extern(_) => unreachable_validated!(), }; - let func_to_call_ty = store.functions.get(func_to_call_addr).ty(); - if func_ty != func_to_call_ty { + let func_to_call_inst = store.functions.get(func_to_call_addr); + + if func_ty != func_to_call_inst.ty() { return Err(TrapError::SignatureMismatch.into()); } trace!("Instruction: call [{func_to_call_addr:?}]"); - match store.functions.get(func_to_call_addr) { + match func_to_call_inst { FuncInst::HostFunc(host_func_to_call_inst) => { let params = stack - .pop_tail_iter(func_to_call_ty.params.valtypes.len()) + .pop_tail_iter( + host_func_to_call_inst.function_type.params.valtypes.len(), + ) .collect(); let returns = (host_func_to_call_inst.hostcode)(&mut store.user_data, params); @@ -341,10 +351,15 @@ pub(super) fn run( // Verify that the return parameters match the host function parameters // since we have no validation guarantees for host functions - if returns.len() != func_to_call_ty.returns.valtypes.len() { + if returns.len() + != host_func_to_call_inst.function_type.returns.valtypes.len() + { return Err(RuntimeError::HostFunctionSignatureMismatch); } - for (value, ty) in zip(returns, &func_to_call_ty.returns.valtypes) { + for (value, ty) in zip( + returns, + &host_func_to_call_inst.function_type.returns.valtypes, + ) { if value.to_ty() != *ty { return Err(RuntimeError::HostFunctionSignatureMismatch); } @@ -356,7 +371,7 @@ pub(super) fn run( stack.push_call_frame::( current_func_addr, - func_to_call_ty, + &wasm_func_to_call_inst.function_type, remaining_locals, wasm.pc, stp, From 5f39e1f0ea3728b60a1b0441b220431611949b50 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Tue, 10 Feb 2026 17:11:24 +0100 Subject: [PATCH 09/11] fix: borrow import names from bytecode instead of allocating them Signed-off-by: Florian Hartung --- src/core/reader/types/import.rs | 17 +++++++---------- src/core/reader/types/values.rs | 10 ++++++---- src/validation/mod.rs | 24 +++++++++++++++--------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/core/reader/types/import.rs b/src/core/reader/types/import.rs index c40331cb6..329bfb3ff 100644 --- a/src/core/reader/types/import.rs +++ b/src/core/reader/types/import.rs @@ -1,6 +1,3 @@ -use alloc::borrow::ToOwned; -use alloc::string::String; - use crate::core::indices::TypeIdx; use crate::core::reader::WasmReader; use crate::{ValidationError, ValidationInfo}; @@ -9,16 +6,16 @@ use super::global::GlobalType; use super::{ExternType, MemType, TableType}; #[derive(Debug, Clone)] -pub struct Import { - pub module_name: String, - pub name: String, +pub struct Import<'wasm> { + pub module_name: &'wasm str, + pub name: &'wasm str, pub desc: ImportDesc, } -impl Import { - pub fn read(wasm: &mut WasmReader) -> Result { - let module_name = wasm.read_name()?.to_owned(); - let name = wasm.read_name()?.to_owned(); +impl<'wasm> Import<'wasm> { + pub fn read(wasm: &mut WasmReader<'wasm>) -> Result { + let module_name = wasm.read_name()?; + let name = wasm.read_name()?; let desc = ImportDesc::read(wasm)?; Ok(Self { diff --git a/src/core/reader/types/values.rs b/src/core/reader/types/values.rs index 12681ec18..85505777d 100644 --- a/src/core/reader/types/values.rs +++ b/src/core/reader/types/values.rs @@ -19,7 +19,7 @@ const CONTINUATION_BIT: u8 = 0b10000000; const INTEGER_BIT_FLAG: u8 = !CONTINUATION_BIT; -impl WasmReader<'_> { +impl<'wasm> WasmReader<'wasm> { /// Tries to read one byte and fails if the end of file is reached. pub fn read_u8(&mut self) -> Result { let byte = self.peek_u8()?; @@ -351,7 +351,7 @@ impl WasmReader<'_> { } /// Note: If `Err`, the [WasmReader] object is no longer guaranteed to be in a valid state - pub fn read_name(&mut self) -> Result<&str, ValidationError> { + pub fn read_name(&mut self) -> Result<&'wasm str, ValidationError> { let len = self.read_var_u32()? as usize; let utf8_str = &self @@ -369,7 +369,8 @@ impl WasmReader<'_> { mut read_element: F, ) -> Result, ValidationError> where - F: FnMut(&mut WasmReader, usize) -> Result, + T: 'wasm, + F: FnMut(&mut WasmReader<'wasm>, usize) -> Result, { let mut idx = 0; self.read_vec(|wasm| { @@ -382,7 +383,8 @@ impl WasmReader<'_> { /// Note: If `Err`, the [WasmReader] object is no longer guaranteed to be in a valid state pub fn read_vec(&mut self, mut read_element: F) -> Result, ValidationError> where - F: FnMut(&mut WasmReader) -> Result, + T: 'wasm, + F: FnMut(&mut WasmReader<'wasm>) -> Result, { let len = self.read_var_u32()?; core::iter::repeat_with(|| read_element(self)) diff --git a/src/validation/mod.rs b/src/validation/mod.rs index d887c3e86..f506b377b 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -41,7 +41,7 @@ pub(crate) struct ImportsLength { pub struct ValidationInfo<'bytecode> { pub(crate) wasm: &'bytecode [u8], pub(crate) types: Vec, - pub(crate) imports: Vec, + pub(crate) imports: Vec>, pub(crate) functions: Vec, pub(crate) tables: Vec, pub(crate) memories: Vec, @@ -490,12 +490,16 @@ fn read_next_header( } #[inline(always)] -fn handle_section Result>( - wasm: &mut WasmReader, +fn handle_section<'wasm, T, F>( + wasm: &mut WasmReader<'wasm>, header: &mut Option, section_ty: SectionTy, handler: F, -) -> Result, ValidationError> { +) -> Result, ValidationError> +where + T: 'wasm, + F: FnOnce(&mut WasmReader<'wasm>, SectionHeader) -> Result, +{ match &header { Some(SectionHeader { ty, .. }) if *ty == section_ty => { let h = header.take().unwrap(); @@ -508,19 +512,21 @@ fn handle_section Result { +impl<'wasm> ValidationInfo<'wasm> { /// Returns the imports of this module as an iterator. Each import consist /// of a module name, a name and an extern type. /// /// See: WebAssembly Specification 2.0 - 7.1.5 - module_imports pub fn imports<'a>( &'a self, - ) -> Map, impl FnMut(&'a Import) -> (&'a str, &'a str, ExternType)> - { + ) -> Map< + core::slice::Iter<'a, Import<'wasm>>, + impl FnMut(&'a Import<'wasm>) -> (&'a str, &'a str, ExternType), + > { self.imports.iter().map(|import| { ( - &*import.module_name, - &*import.name, + import.module_name, + import.name, import.desc.extern_type(self), ) }) From 64104eab3a90e8de3b0d9f095dcdb3c536438f4d Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Tue, 10 Feb 2026 17:21:17 +0100 Subject: [PATCH 10/11] fix: borrow export name from bytecode instead of allocating it Signed-off-by: Florian Hartung --- src/core/reader/types/export.rs | 13 +++++-------- src/execution/store/mod.rs | 19 +++++++++---------- src/validation/mod.rs | 13 ++++++++----- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/core/reader/types/export.rs b/src/core/reader/types/export.rs index 54c9c399e..72ba045c6 100644 --- a/src/core/reader/types/export.rs +++ b/src/core/reader/types/export.rs @@ -1,6 +1,3 @@ -use alloc::borrow::ToOwned; -use alloc::string::String; - use crate::core::indices::{FuncIdx, GlobalIdx, MemIdx, TableIdx}; use crate::core::reader::types::import::ImportDesc; use crate::core::reader::WasmReader; @@ -9,14 +6,14 @@ use crate::{ValidationError, ValidationInfo}; use super::ExternType; #[derive(Debug, Clone)] -pub struct Export { - pub name: String, +pub struct Export<'wasm> { + pub name: &'wasm str, pub desc: ExportDesc, } -impl Export { - pub fn read(wasm: &mut WasmReader) -> Result { - let name = wasm.read_name()?.to_owned(); +impl<'wasm> Export<'wasm> { + pub fn read(wasm: &mut WasmReader<'wasm>) -> Result { + let name = wasm.read_name()?; let desc = ExportDesc::read(wasm)?; Ok(Export { name, desc }) } diff --git a/src/execution/store/mod.rs b/src/execution/store/mod.rs index 30397162e..511f247bd 100644 --- a/src/execution/store/mod.rs +++ b/src/execution/store/mod.rs @@ -9,7 +9,7 @@ use crate::core::indices::TypeIdx; use crate::core::reader::span::Span; use crate::core::reader::types::data::{DataModeActive, DataSegment}; use crate::core::reader::types::element::{ActiveElem, ElemItems, ElemMode, ElemType}; -use crate::core::reader::types::export::{Export, ExportDesc}; +use crate::core::reader::types::export::ExportDesc; use crate::core::reader::types::global::{Global, GlobalType}; use crate::core::reader::types::{ ExternType, FuncType, ImportSubTypeRelation, MemType, ResultType, TableType, @@ -22,6 +22,7 @@ use crate::resumable::{ Dormitory, FreshResumableRef, InvokedResumableRef, Resumable, ResumableRef, RunState, }; use crate::{RefType, RuntimeError, ValidationInfo}; +use alloc::borrow::ToOwned; use alloc::collections::btree_map::BTreeMap; use alloc::string::String; use alloc::sync::Arc; @@ -287,19 +288,17 @@ impl<'b, T: Config> Store<'b, T> { let export_insts: BTreeMap = module .exports .iter() - .map(|Export { name, desc }| { + .map(|export| { let module_inst = self.modules.get(module_addr); - let value = match desc { - ExportDesc::Func(func_idx) => { - ExternVal::Func(module_inst.func_addrs[*func_idx]) - } - ExportDesc::Table(table_idx) => ExternVal::Table(table_addrs_mod[*table_idx]), - ExportDesc::Mem(mem_idx) => ExternVal::Mem(mem_addrs_mod[*mem_idx]), + let value = match export.desc { + ExportDesc::Func(func_idx) => ExternVal::Func(module_inst.func_addrs[func_idx]), + ExportDesc::Table(table_idx) => ExternVal::Table(table_addrs_mod[table_idx]), + ExportDesc::Mem(mem_idx) => ExternVal::Mem(mem_addrs_mod[mem_idx]), ExportDesc::Global(global_idx) => { - ExternVal::Global(module_inst.global_addrs[*global_idx]) + ExternVal::Global(module_inst.global_addrs[global_idx]) } }; - (String::from(name), value) + (export.name.to_owned(), value) }) .collect(); diff --git a/src/validation/mod.rs b/src/validation/mod.rs index f506b377b..12025b373 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -46,7 +46,7 @@ pub struct ValidationInfo<'bytecode> { pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) globals: Vec, - pub(crate) exports: Vec, + pub(crate) exports: Vec>, /// Each block contains the validated code section and the stp corresponding to /// the beginning of that code section pub(crate) func_blocks_stps: Vec<(Span, usize)>, @@ -63,10 +63,10 @@ fn validate_exports(validation_info: &ValidationInfo) -> Result<(), ValidationEr let mut found_export_names: btree_set::BTreeSet<&str> = btree_set::BTreeSet::new(); use crate::core::reader::types::export::ExportDesc::*; for export in &validation_info.exports { - if found_export_names.contains(export.name.as_str()) { + if found_export_names.contains(export.name) { return Err(ValidationError::DuplicateExportName); } - found_export_names.insert(export.name.as_str()); + found_export_names.insert(export.name); match export.desc { Func(func_idx) => { if validation_info.functions.len() @@ -538,9 +538,12 @@ impl<'wasm> ValidationInfo<'wasm> { /// See: WebAssembly Specification 2.0 - 7.1.5 - module_exports pub fn exports<'a>( &'a self, - ) -> Map, impl FnMut(&'a Export) -> (&'a str, ExternType)> { + ) -> Map< + core::slice::Iter<'a, Export<'wasm>>, + impl FnMut(&'a Export<'wasm>) -> (&'a str, ExternType), + > { self.exports .iter() - .map(|export| (&*export.name, export.desc.extern_type(self))) + .map(|export| (export.name, export.desc.extern_type(self))) } } From 3944c98ecd9a5b366d8e24377165663e869e7727 Mon Sep 17 00:00:00 2001 From: Florian Hartung Date: Wed, 11 Feb 2026 16:04:49 +0100 Subject: [PATCH 11/11] chore: apply clippy lints Signed-off-by: Florian Hartung --- tests/memory_as_slice.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/memory_as_slice.rs b/tests/memory_as_slice.rs index 30c90eba3..8585fd514 100644 --- a/tests/memory_as_slice.rs +++ b/tests/memory_as_slice.rs @@ -11,7 +11,7 @@ fn simple_byte_writes() { store .mem_access_mut_slice(mem, |mem_as_slice| { - for (n, x) in mem_as_slice.into_iter().enumerate() { + for (n, x) in mem_as_slice.iter_mut().enumerate() { *x = u8::try_from(n % 256).expect("this to never be larger than 255"); } }) @@ -38,7 +38,7 @@ fn interpret_as_str() { // Read the string again and check if it is equal to the original one store .mem_access_mut_slice(mem, |mem_as_slice| { - let bytes = &mem_as_slice[0..STR_TO_WRITE.as_bytes().len()]; + let bytes = &mem_as_slice[0..STR_TO_WRITE.len()]; let as_str = std::str::from_utf8(bytes).unwrap(); assert_eq!(as_str, STR_TO_WRITE); })