componentize-py: dropped/invalid resource handle yields None instead of an integer, causing a Result::unwrap() panic on TypeError and process abort in jco instead of an invalid-handle trap
Reproduction of a componentize-py runtime crash where an invalid or dangling resource handle is passed to Python as None, making runtime/src/lib.rs unwrap a TypeError PyErr and abort under jco rather than trapping with an invalid-handle error.
When a Python consumer calls a method on an already-dropped resource handle, componentize-py's bindings pass the handle as None instead of an integer. Its Rust runtime then unwrap()s the resulting TypeError and aborts the whole component, instead of surfacing the clean invalid handle trap required by the Component Model. Reproduces identically under both wasmtime and jco.
We deliberately passed a bad handle; in the correct world, the host would look it up in the table and then trap with "invalid handle". In reality, componentize-py's binding layer trips over None and panics before the handle even leaves the guest.
Environment
| component |
version |
| OS |
macOS 15.7 (Darwin 24.6.0), x86_64 |
| jco |
1.16.1 (source checkout, tag jco-v1.16.1) |
| Node.js |
v22.22.2 |
| wac |
wac-cli 0.8.1 |
| componentize-py |
componentize-py 0.17.2 |
Steps to reproduce
The full chain is: compile the guest sources into a provider component and a consumer component, compose them with wac, then run the composed component. Prebuilt copies of every artifact are included (components/provider.wasm, components/consumer.wasm, composed.wasm), so you can also skip straight to step 5.
Github repo:https://github.com/xiaozzzZZzzz240/G007-componentize-py-invalid-resource-handle-none-unwrap-panics-instead-of
-
Clone this repository:
git clone <THIS_REPO_URL>
cd <THIS_REPO_DIR>
-
Compile the provider component from the JavaScript sources in src/provider-JavaScript/ (toolchain versions in the table above; the WIT package is in wit/):
jco componentize src/provider-JavaScript/world0-afno6b9m_generated.js --wit wit --world-name world0-afno6b9m --disable http fetch-event --out components/provider.wasm
-
Compile the consumer component from the Python sources in src/consumer-Python/:
(cd src/consumer-Python && componentize-py --wit-path ../../wit --world world0b-w80o2rak componentize world0b-w80o2rak_generated -o ../../components/consumer.wasm)
-
Compose the two components into one runnable component with wac 0.8.1:
wac plug components/consumer.wasm --plug components/provider.wasm -o composed.wasm
-
Run the composed component with jco:
npm install -g @bytecodealliance/jco@1.16.1
jco run composed.wasm
Expected result
This test case intentionally performs an invalid resource-handle operation at the very end, so the expected behavior is: print the prefix below, then trap cleanly with invalid handle.
"resinst0"
"resinst1"
"resinst2"
"resinst0"
resource("temres9")
true
true
ok(resource("temres13"))
true
true
"alpha"
"beta"
true
"temres335"
trap(invalid handle)
Actual result
Under jco the same composed.wasm exits with rc=1. stdout:
"resinst0"
"resinst0"
"resinst0"
"resinst1"
resource("temres9")
true
true
ok(resource("temres13"))
true
true
"alpha"
"beta"
true
"temres4"
stderr:
thread '<unnamed>' panicked at runtime/src/lib.rs:1217:14:
called `Result::unwrap()` on an `Err` value: PyErr { type: <class 'TypeError'>, value: TypeError("'NoneType' object cannot be interpreted as an integer"), traceback: None }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
RuntimeError: unreachable
at libc.so.abort (wasm://wasm/libc.so-001e65fe:wasm-function[458]:0x1041d)
at wasm://wasm/02945cba:wasm-function[306]:0xf8e8
at libcomponentize_py_runtime.so.std::sys::pal::wasi::helpers::abort_internal::h118dde344a79086c (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[2193]:0xceb72)
at libcomponentize_py_runtime.so.std::process::abort::h2738589d00d7233e (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[1645]:0xb87f6)
at libcomponentize_py_runtime.so.__rustc::__rust_abort (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[2002]:0xc7330)
at libcomponentize_py_runtime.so.__rustc::__rust_start_panic (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[2208]:0xcfc4c)
at libcomponentize_py_runtime.so.__rustc::rust_panic (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[2035]:0xc8a1e)
at libcomponentize_py_runtime.so.std::panicking::rust_panic_with_hook::h02d894de16d94192 (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[2034]:0xc899c)
at libcomponentize_py_runtime.so.std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::he125d401eefa4f33 (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[1573]:0xb44e1)
at libcomponentize_py_runtime.so.std::sys::backtrace::__rust_end_short_backtrace::h31a9416a2c4ea1cb (wasm://wasm/libcomponentize_py_runtime.so-0052fcc2:wasm-function[1572]:0xb4442)
wasmtime on the exact same file: rc=134
"resinst0"
"resinst1"
"resinst2"
"resinst0"
resource("temres9")
true
true
ok(resource("temres13"))
true
true
"alpha"
"beta"
true
"temres335"
--- stderr ---
thread '<unnamed>' panicked at runtime/src/lib.rs:1217:14:
called `Result::unwrap()` on an `Err` value: PyErr { type: <class 'TypeError'>, value: TypeError("'NoneType' object cannot be interpreted as an integer"), traceback: None }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: failed to run main module `/Users/myuser/WASM/myProject/Wasm_component_testing/mycode/execute/reportrepo/G007-componentize-py-invalid-resource-handle-none-unwrap-panics-instead-of/composed.wasm`
Caused by:
0: failed to invoke `run` function
1: error while executing at wasm backtrace:
0: 0x2e53270 - libc.so!abort
1: 0xce0d13 - <unknown>!<wasm function 306>
2: 0x1801e66 - libcomponentize_py_runtime.so!std::sys::pal::wasi::helpers::abort_internal::h118dde344a79086c
3: 0x17ebaea - libcomponentize_py_runtime.so!std::process::abort::h2738589d00d7233e
4: 0x17fa624 - libcomponentize_py_runtime.so!__rustc::__rust_abort
5: 0x1802f40 - libcomponentize_py_runtime.so!__rustc::__rust_start_panic
6: 0x17fbd12 - libcomponentize_py_runtime.so!__rustc::rust_panic
7: 0x17fbc90 - libcomponentize_py_runtime.so!std::panicking::rust_panic_with_hook::h02d894de16d94192
8: 0x17e77d5 - libcomponentize_py_runtime.so!std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::he125d401eefa4f33
9: 0x17e7736 - libcomponentize_py_runtime.so!std::sys::backtrace::__rust_end_short_backtrace::h31a9416a2c4ea1cb
10: 0x17fb588 - libcomponentize_py_runtime.so!__rustc::rust_begin_unwind
11: 0x181cbb1 - libcomponentize_py_runtime.so!core::panicking::panic_fmt::h52dbee1629029330
12: 0x183c177 - libcomponentize_py_runtime.so!core::result::unwrap_failed::h68b1db2abd074508
13: 0x177816a - libcomponentize_py_runtime.so!componentize-py#ToCanonHandle
14: 0x2ebfd2e - libcomponentize_py_bindings.so!mydefined:package3hl16smu1p/itf-arpoig8f1c#[method]res0.get-name-import
15: 0x2ec8b79 - libcomponentize_py_bindings.so!componentize-py#CallIndirect
16: 0xce0d4b - <unknown>!<wasm function 310>
17: 0x1778707 - libcomponentize_py_runtime.so!componentize_py_runtime::_$LT$impl$u20$componentize_py_runtime..call_import..MakeDef$GT$::_PYO3_DEF::trampoline::h88da0896868f0eed
18: 0x1ac78d8 - libpython3.12.so!_PyEval_EvalFrameDefault
19: 0x1acba17 - libpython3.12.so!_PyEval_Vector
20: 0x19a9390 - libpython3.12.so!_PyFunction_Vectorcall
21: 0x19ab8fd - libpython3.12.so!method_vectorcall
22: 0x19a8d4f - libpython3.12.so!_PyVectorcall_Call
23: 0x19a9132 - libpython3.12.so!_PyObject_Call
24: 0x19a91cc - libpython3.12.so!PyObject_Call
25: 0xce0921 - <unknown>!<wasm
componentize-py: dropped/invalid resource handle yields None instead of an integer, causing a
Result::unwrap()panic onTypeErrorand process abort in jco instead of an invalid-handle trapReproduction of a componentize-py runtime crash where an invalid or dangling resource handle is passed to Python as None, making runtime/src/lib.rs unwrap a TypeError PyErr and abort under jco rather than trapping with an invalid-handle error.
When a Python consumer calls a method on an already-dropped resource handle, componentize-py's bindings pass the handle as None instead of an integer. Its Rust runtime then unwrap()s the resulting TypeError and aborts the whole component, instead of surfacing the clean invalid handle trap required by the Component Model. Reproduces identically under both wasmtime and jco.
We deliberately passed a bad handle; in the correct world, the host would look it up in the table and then trap with "invalid handle". In reality, componentize-py's binding layer trips over None and panics before the handle even leaves the guest.
Environment
Steps to reproduce
The full chain is: compile the guest sources into a provider component and a consumer component, compose them with
wac, then run the composed component. Prebuilt copies of every artifact are included (components/provider.wasm,components/consumer.wasm,composed.wasm), so you can also skip straight to step 5.Github repo:https://github.com/xiaozzzZZzzz240/G007-componentize-py-invalid-resource-handle-none-unwrap-panics-instead-of
Clone this repository:
Compile the provider component from the JavaScript sources in
src/provider-JavaScript/(toolchain versions in the table above; the WIT package is inwit/):jco componentize src/provider-JavaScript/world0-afno6b9m_generated.js --wit wit --world-name world0-afno6b9m --disable http fetch-event --out components/provider.wasmCompile the consumer component from the Python sources in
src/consumer-Python/:(cd src/consumer-Python && componentize-py --wit-path ../../wit --world world0b-w80o2rak componentize world0b-w80o2rak_generated -o ../../components/consumer.wasm)Compose the two components into one runnable component with wac 0.8.1:
wac plug components/consumer.wasm --plug components/provider.wasm -o composed.wasmRun the composed component with jco:
Expected result
This test case intentionally performs an invalid resource-handle operation at the very end, so the expected behavior is: print the prefix below, then trap cleanly with
invalid handle.Actual result
Under jco the same
composed.wasmexits with rc=1. stdout:stderr:
wasmtime on the exact same file: rc=134