From b6a27c3a28839bd5ef549a087baa1beb02149d8e Mon Sep 17 00:00:00 2001 From: Alasdair Allan Date: Thu, 7 May 2026 14:37:24 +0100 Subject: [PATCH] Fix BaseException escape from host callback trampoline (#336) The host-callback trampoline in `wasmtime/_func.py` caught `Exception`, which let `BaseException` subclasses (`KeyboardInterrupt`, `SystemExit`, custom `BaseException` subclasses) escape into the Rust array_call trampoline with an undefined `c_void_p` return value, causing a libmalloc SIGABRT inside `HostFunc::array_call_trampoline`. Broadens the catch to `BaseException`. The existing `LAST_EXCEPTION` / `Trap("python exception")` machinery handles propagation back to the Python caller unchanged. Adds regression tests for `KeyboardInterrupt`, `SystemExit`, and a custom `BaseException` subclass raised from host callbacks. Fixes #336 --- tests/test_func.py | 33 +++++++++++++++++++++++++++++++++ wasmtime/_func.py | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/test_func.py b/tests/test_func.py index 00be6c3f..ef7ddb8e 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -85,6 +85,39 @@ def do_raise(): with self.assertRaises(Exception, msg="hello"): func(store) + def test_host_base_exception(self): + # Regression test for #336: a BaseException subclass raised from a + # host callback (KeyboardInterrupt, SystemExit, custom BaseException + # subclasses) used to escape the trampoline's `except Exception` + # handler and abort the process inside Rust's array_call_trampoline + # with a libmalloc SIGABRT. It must propagate cleanly to the caller. + store = Store() + ty = FuncType([], []) + + def raise_keyboard_interrupt(): + raise KeyboardInterrupt + + func = Func(store, ty, raise_keyboard_interrupt) + with self.assertRaises(KeyboardInterrupt): + func(store) + + def raise_system_exit(): + raise SystemExit(0) + + func = Func(store, ty, raise_system_exit) + with self.assertRaises(SystemExit): + func(store) + + class CustomBaseException(BaseException): + pass + + def raise_custom(): + raise CustomBaseException("custom") + + func = Func(store, ty, raise_custom) + with self.assertRaises(CustomBaseException): + func(store) + def test_type(self): store = Store() i32 = ValType.i32() diff --git a/wasmtime/_func.py b/wasmtime/_func.py index ccfdbf3c..64623aa4 100644 --- a/wasmtime/_func.py +++ b/wasmtime/_func.py @@ -209,7 +209,7 @@ def trampoline(idx, caller, params, nparams, results, nresults): # type: ignore for i, result in enumerate(pyresults): results[i] = Val._convert_to_raw(caller, result_tys[i], result) return 0 - except Exception as e: + except BaseException as e: global LAST_EXCEPTION LAST_EXCEPTION = e trap = Trap("python exception")._consume()