wit-bindgen-cpp: generated C++ resource destructor calls resource.drop on an already-freed handle, trapping with "invalid handle" in wasmtime
Reproduction of a wit-bindgen-cpp bug where the generated C++ resource-export destructor chain (ResourceExportBase::Deregister → ResourceDrop) performs an invalid/duplicate resource.drop, aborting the component with an invalid-handle trap under wasmtime.
The wit-bindgen C++ backend is at fault—its glue layer performs an extra drop in the lift/destructor chain, translating a semantically valid program into a component that contains illegal operations.
Environment
| component |
version |
| OS |
macOS 15.7 (Darwin 24.6.0), x86_64 |
| wasmtime |
wasmtime 41.0.0 (3dda91692 2026-01-20) |
| wac |
wac-cli 0.8.1 |
| wasi-sdk |
27.0 (wasm32-wasip2-clang/clang++) |
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/G006-wit-bindgen-cpp-resource-drop-invalid-handle-trap-in-generated
-
Clone this repository:
git clone <THIS_REPO_URL>
cd <THIS_REPO_DIR>
-
Compile the provider component from the C++ sources in src/provider-Cpp/ (toolchain versions in the table above; the WIT package is in wit/):
wasm32-wasip2-clang++ -std=c++23 -fno-exceptions -o components/provider.wasm -mexec-model=reactor src/provider-Cpp/world9-wi7n9u47_generated.cpp src/provider-Cpp/world9_wi7n9u47.cpp src/provider-Cpp/world9_wi7n9u47_component_type.o
-
Compile the consumer component from the C sources in src/consumer-C/:
wasm32-wasip2-clang -o components/consumer.wasm -mexec-model=reactor src/consumer-C/world9b-mul38v8w_generated.c src/consumer-C/world9b_mul38v8w.c src/consumer-C/world9b_mul38v8w_component_type.o
-
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 wasmtime 41.0.0:
wasmtime 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("temres32")
"c1"
true
"c0"
resource("temres35")
(resource("temres36"), "foo")
"c1"
true
true
resource("temres225")
"bar"
"c2"
true
true
"temres35"
"temres40"
"beta"
"c0"
true
true
5
true
trap(invalid handle)
Actual result
Under wasmtime the same composed.wasm exits with rc=1. stdout:
"resinst0"
"resinst1"
"resinst2"
"resinst0"
resource("temres32")
"c1"
true
"c0"
resource("temres35")
(resource("temres36"), "foo")
"c1"
true
true
resource("temres225")
"bar"
"c2"
true
true
stderr:
Error: failed to run main module `/Users/myuser/WASM/myProject/Wasm_component_testing/mycode/execute/reportrepo/G006-wit-bindgen-cpp-resource-drop-invalid-handle-trap-in-generated/composed.wasm`
Caused by:
0: failed to invoke `run` function
1: error while executing at wasm backtrace:
0: 0x1b078 - provider.wasm!exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2::ResourceDrop(int)
1: 0x12e58 - provider.wasm!wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister::operator()(exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2*) const
2: 0x69f9 - provider.wasm!std::__2::unique_ptr<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2, wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister>::reset[abi:nn200100](exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2*)
3: 0x5b98 - provider.wasm!std::__2::unique_ptr<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2, wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister>::~unique_ptr[abi:nn200100]()
4: 0x1e008 - provider.wasm!std::__2::__tuple_leaf<0ul, std::__2::unique_ptr<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2, wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister>, false>::~__tuple_leaf()
5: 0x1af60 - provider.wasm!std::__2::__tuple_impl<std::__2::__tuple_indices<0ul, 1ul>, std::__2::unique_ptr<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2, wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister>, wit::string>::~__tuple_impl()
6: 0x1ad37 - provider.wasm!std::__2::tuple<std::__2::unique_ptr<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2, wit::ResourceExportBase<exports::mydefined::package3hl16smu1p::itf_ou0j0n064d::Res2>::Deregister>, wit::string>::~tuple()
7: 0x1b45b - provider.wasm!__wasm_export_mydefinedX3Apackage3hl16smu1pX2Fitf_ou0j0n064dX23f_m2yyf8i
8: 0xab2 - <unknown>!<wasm function 29>
9: 0xe5f60 - wit-component:shim!indirect-mydefined:package3hl16smu1p/itf-ou0j0n064d-f-m2yyf8i
10: 0xa9057 - consumer.wasm!mydefined_package3hl16smu1p_itf_ou0j0n064d_f_m2yyf8i
11: 0xa7845 - consumer.wasm!exports_wasi_cli_run_run
12: 0xa9209 - consumer.wasm!__wasm_export_exports_wasi_cli_run_run
note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information
2: handle index 1 used with the wrong type, expected guest-defined resource but found a different guest-defined resource
wit-bindgen-cpp: generated C++ resource destructor calls resource.drop on an already-freed handle, trapping with "invalid handle" in wasmtime
Reproduction of a wit-bindgen-cpp bug where the generated C++ resource-export destructor chain (ResourceExportBase::Deregister → ResourceDrop) performs an invalid/duplicate resource.drop, aborting the component with an invalid-handle trap under wasmtime.
The wit-bindgen C++ backend is at fault—its glue layer performs an extra drop in the lift/destructor chain, translating a semantically valid program into a component that contains illegal operations.
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/G006-wit-bindgen-cpp-resource-drop-invalid-handle-trap-in-generated
Clone this repository:
Compile the provider component from the C++ sources in
src/provider-Cpp/(toolchain versions in the table above; the WIT package is inwit/):wasm32-wasip2-clang++ -std=c++23 -fno-exceptions -o components/provider.wasm -mexec-model=reactor src/provider-Cpp/world9-wi7n9u47_generated.cpp src/provider-Cpp/world9_wi7n9u47.cpp src/provider-Cpp/world9_wi7n9u47_component_type.oCompile the consumer component from the C sources in
src/consumer-C/:wasm32-wasip2-clang -o components/consumer.wasm -mexec-model=reactor src/consumer-C/world9b-mul38v8w_generated.c src/consumer-C/world9b_mul38v8w.c src/consumer-C/world9b_mul38v8w_component_type.oCompose 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 wasmtime 41.0.0:
wasmtime run composed.wasmExpected 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 wasmtime the same
composed.wasmexits with rc=1. stdout:stderr: