From fe43e4816efbf622e8cdc106a552dfb9aadfb80e Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 14 Jul 2026 17:33:43 +0200 Subject: fix: remove `_start` fallback for instantiation Signed-off-by: Henry --- .github/workflows/test.yaml | 2 +- CHANGELOG.md | 6 ++++++ crates/cli/src/cmd/run.rs | 16 ++++++++++------ crates/tinywasm/src/instance.rs | 23 ++++++----------------- crates/tinywasm/src/interpreter/executor.rs | 9 ++------- crates/tinywasm/tests/internal_refs.rs | 5 +++-- crates/tinywasm/tests/start_function.rs | 23 +++++++++++++++++++++++ 7 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 crates/tinywasm/tests/start_function.rs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index be1993b..1566cac 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -106,7 +106,7 @@ jobs: if: matrix.target == '' - name: Run tests (${{ matrix.target }}) - uses: houseabsolute/actions-rust-cross@fe6ede73c7f0a50412ec05994e2aa5a7bf635eac # v1.0.7 + uses: houseabsolute/actions-rust-cross@21b0f18dc621b25bfae556ff2791fca4173121e8 # v1.0.8 with: command: test target: ${{ matrix.target }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c1dc0e3..d35f070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- `instantiate` / `start` / `start_func` no longer fall back to `_start` if the module has no start section. This is technically a breaking change, but it is more consistent with the WebAssembly spec and prevents accidental execution of `_start` in wasi modules that don't have a start section. Call the `_start` function explicitly if you want to run it. + ## [0.9.1] - 2026-06-29 ### Added diff --git a/crates/cli/src/cmd/run.rs b/crates/cli/src/cmd/run.rs index b2f5e03..06f29f1 100644 --- a/crates/cli/src/cmd/run.rs +++ b/crates/cli/src/cmd/run.rs @@ -1,4 +1,4 @@ -use eyre::{Result, bail}; +use eyre::Result; use tinywasm::types::ExportType; use tinywasm::{ModuleInstance, Store}; @@ -34,13 +34,17 @@ pub fn run(args: RunArgs) -> Result<()> { Ok(()) } None => { - if instance.start_func(&store)?.is_none() { - bail!( - "module has no start function or `_start` export; use `tinywasm inspect {module_path}` or `tinywasm run --invoke {module_path}`" - ) + if instance.start_func(&store)?.is_some() { + let _ = instance.start(&mut store)?; + return Ok(()); } - let _ = instance.start(&mut store)?; + let start = instance.func_untyped(&store, "_start").map_err(|_| { + eyre::eyre!( + "module has no start function or `_start` export. Use `tinywasm inspect {module_path}` or `tinywasm run --invoke {module_path}`" + ) + })?; + start.call(&mut store, &[])?; Ok(()) } } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index b75127f..f1003ab 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -604,7 +604,8 @@ impl ModuleInstance { /// # use tinywasm::{ModuleInstance, Store}; /// # let wasm = wat::parse_str(r#" /// # (module - /// # (func (export "_start")) + /// # (func $start) + /// # (start $start) /// # ) /// # "#).expect("valid wat"); /// # let module = tinywasm::parse_bytes(&wasm)?; @@ -619,21 +620,8 @@ impl ModuleInstance { pub fn start_func(&self, store: &Store) -> Result> { self.validate_store(store)?; - let func_addr = match self.0.func_start { - Some(func_index) => func_index, - None => { - // Alternatively, check for a _start function in the exports. - let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else { - return Ok(None); - }; - - 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 Some(func_addr) = self.0.func_start else { + return Ok(None); }; let func_addr = self.resolve_func_addr(func_addr); @@ -656,9 +644,10 @@ impl ModuleInstance { /// # let wasm = wat::parse_str(r#" /// # (module /// # (global $g (mut i32) (i32.const 0)) - /// # (func (export "_start") + /// # (func $start /// # i32.const 7 /// # global.set $g) + /// # (start $start) /// # (export "g" (global $g))) /// # "#).expect("valid wat"); /// # let module = tinywasm::parse_bytes(&wasm)?; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 37b2f43..e54b5e5 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -49,6 +49,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } } + #[allow(clippy::redundant_closure_call)] // we wrap these in closures to get better perf traces #[inline(always)] fn exec(&mut self) -> Result, Trap> { macro_rules! stack_op { @@ -264,7 +265,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }}; } - let next = match self.func.instructions.get(self.cf.instr_ptr as usize) { + let next = match self.func.instructions.get(self.cf.instr_ptr) { Some(instr) => instr, None => { cold_path(); @@ -1654,12 +1655,6 @@ impl<'store> Executor<'store, true> { } } -#[cold] -#[inline(never)] -fn instruction_pointer_out_of_bounds(instr_ptr: usize, instruction_count: usize) -> ! { - panic!("Instruction pointer out of bounds: {instr_ptr} ({instruction_count} instructions)") -} - #[inline(always)] fn cmp_i32(lhs: i32, rhs: i32, op: CmpOp) -> bool { match op { diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index 2382597..18033a7 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -169,14 +169,15 @@ fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<() } #[test] -fn start_prefers_exported_start_without_re_resolving_store_addr() -> Result<()> { +fn start_resolves_module_func_index_to_store_addr() -> Result<()> { let wasm = wat::parse_str( r#" (module (global (export "g") (mut i32) (i32.const 0)) - (func (export "_start") + (func $start i32.const 1 global.set 0) + (start $start) ) "#, )?; diff --git a/crates/tinywasm/tests/start_function.rs b/crates/tinywasm/tests/start_function.rs new file mode 100644 index 0000000..077c0fc --- /dev/null +++ b/crates/tinywasm/tests/start_function.rs @@ -0,0 +1,23 @@ +use eyre::Result; +use tinywasm::{ModuleInstance, Store}; + +#[test] +fn exported_wasi_start_is_not_run_during_instantiation() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (table 1 funcref) + (func $_start (export "_start") + unreachable) + (elem (i32.const 0) func $_start)) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + let mut store = Store::default(); + + let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + + assert!(instance.start_func(&store)?.is_none()); + assert!(instance.func::<(), ()>(&store, "_start")?.call(&mut store, ()).is_err()); + Ok(()) +} -- cgit v1.3.1