Skip to content

Conversation

pchickey
Copy link
Contributor

@pchickey pchickey commented Oct 1, 2025

It would be nice to have a sanitizer that could tell guest program authors where, in the guest program, resources were constructed (or, at least, taken ownership of) and last used, for any resources that have not been dropped at the end of execution.

My current opinion is, I thought this would be pretty easy, and it turns out to be somewhat difficult, and I can't prioritize getting it done right now. Please respond on this PR if you want to pick up working on this.

This is a rapid prototype that I have decided is designed completely wrong. It gives a backtrace in the host, but not the guest.

There are two design problems as currently written:

  1. it captures a host backtrace (i.e. std::backtrace::Backtrace::force_capture()), not a guest backtrace. This tells you nothing about where the guest program was in its execution. Wasmtime's backtrace construction requires an impl AsContext argument, and the site at which these backtraces are captured already has a mut borrow of the AsContextMut, so we can't create a lazy way to capture backtraces
  2. if we could solve the above, we'd have to plumb it through every single bindgen trait to every single use of the ResourceTable, which is a huge pain for just a little gain

Instead, this sanitizer probably be implemented as part of a wasmtime::vm::component::HandleTable, and the plumbing can then all stay internal to wasmtime::component's internals. It would still be messy, but not quite as messy?

Additionally, because I knew this was bogus, the capture of backtraces at every single resource use site, as well as the reporting of leaks, is unconditional on every wasmtime run invocation, because I knew this was a throwaway and couldn't be bothered to plumb it through properly. (Additionally the wasip1 ctx stuff in Runner makes it artificially hard.)

For this deliberately faulty guest program:

use std::io::Write as _;

wasip2::cli::command::export!(Example);

struct Example;

impl wasip2::exports::cli::run::Guest for Example {
    fn run() -> Result<(), ()> {
        let mut stdout = wasip2::cli::stdout::get_stdout();
        stdout.write_all(b"Hello, WASI!\n").unwrap();
        stdout.flush().unwrap();
        std::mem::forget(stdout);
        Ok(())
    }
}

this PR prints:

Hello, WASI!
LEAK resource 0: alloc::boxed::Box<dyn wasmtime_wasi_io::streams::OutputStream>
LEAK allocated at Backtrace [
    { fn: "std::backtrace_rs::backtrace::libunwind::trace", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/../../backtrace/src/backtrace/libunwind.rs", line: 117 },
    { fn: "std::backtrace_rs::backtrace::trace_unsynchronized", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/../../backtrace/src/backtrace/mod.rs", line: 66 },
    { fn: "std::backtrace::Backtrace::create", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/backtrace.rs", line: 331 },
    { fn: "wasmtime::runtime::component::resource_table::ResourceTable::push", file: "./crates/wasmtime/src/runtime/component/resource_table.rs", line: 179 },
    { fn: "wasmtime_wasi::p2::stdio::<impl wasmtime_wasi::p2::bindings::async_io::wasi::cli::stdout::Host for wasmtime_wasi::cli::WasiCliCtxView>::get_stdout", file: "./crates/wasi/src/p2/stdio.rs", line: 25 },
    { fn: "wasmtime_wasi::p2::bindings::async_io::wasi::cli::stdout::add_to_linker::{{closure}}", file: "./crates/wasi/src/p2/bindings.rs", line: 336 },
    { fn: "wasmtime::runtime::component::func::host::HostFunc::from_closure::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 66 },
    { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint::{{closure}}::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 113 },
    { fn: "wasmtime::runtime::component::func::host::call_host", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 343 },
    { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 107 },
    { fn: "wasmtime::runtime::component::func::host::call_host_and_handle_result::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 689 },
    { fn: "wasmtime::runtime::vm::component::ComponentInstance::enter_host_from_wasm::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/component.rs", line: 237 },
    { fn: "wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 130 },
    { fn: "<core::result::Result<T,E> as wasmtime::runtime::vm::traphandlers::HostResult>::maybe_catch_unwind::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 243 },
    { fn: "<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs", line: 272 },
    { fn: "std::panicking::catch_unwind::do_call", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panicking.rs", line: 589 },
    { fn: "___rust_try" },
    { fn: "std::panicking::catch_unwind", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panicking.rs", line: 552 },
    { fn: "std::panic::catch_unwind", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panic.rs", line: 359 },
    { fn: "<core::result::Result<T,E> as wasmtime::runtime::vm::traphandlers::HostResult>::maybe_catch_unwind", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 252 },
    { fn: "wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 130 },
    { fn: "wasmtime::runtime::vm::component::ComponentInstance::enter_host_from_wasm", file: "./crates/wasmtime/src/runtime/vm/component.rs", line: 237 },
    { fn: "wasmtime::runtime::component::func::host::call_host_and_handle_result", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 686 },
    { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 106 },
]
LEAK last used at Some(
    Backtrace [
        { fn: "std::backtrace_rs::backtrace::libunwind::trace", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/../../backtrace/src/backtrace/libunwind.rs", line: 117 },
        { fn: "std::backtrace_rs::backtrace::trace_unsynchronized", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/../../backtrace/src/backtrace/mod.rs", line: 66 },
        { fn: "std::backtrace::Backtrace::create", file: "/rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/backtrace.rs", line: 331 },
        { fn: "wasmtime::runtime::component::resource_table::ResourceTable::get_any_mut", file: "./crates/wasmtime/src/runtime/component/resource_table.rs", line: 369 },
        { fn: "wasmtime::runtime::component::resource_table::ResourceTable::get_mut", file: "./crates/wasmtime/src/runtime/component/resource_table.rs", line: 360 },
        { fn: "wasmtime_wasi_io::impls::<impl wasmtime_wasi_io::bindings::wasi::io::streams::HostOutputStream for wasmtime::runtime::component::resource_table::ResourceTable>::flush", file: "./crates/wasi-io/src/impls.rs", line: 177 },
        { fn: "<&mut _T as wasmtime_wasi_io::bindings::wasi::io::streams::HostOutputStream>::flush", file: "./crates/wasi-io/src/bindings.rs", line: 1 },
        { fn: "wasmtime_wasi_io::bindings::wasi::io::streams::add_to_linker::{{closure}}", file: "./crates/wasi-io/src/bindings.rs", line: 1 },
        { fn: "wasmtime::runtime::component::func::host::HostFunc::from_closure::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 66 },
        { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint::{{closure}}::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 113 },
        { fn: "wasmtime::runtime::component::func::host::call_host", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 343 },
        { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 107 },
        { fn: "wasmtime::runtime::component::func::host::call_host_and_handle_result::{{closure}}", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 689 },
        { fn: "wasmtime::runtime::vm::component::ComponentInstance::enter_host_from_wasm::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/component.rs", line: 237 },
        { fn: "wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 130 },
        { fn: "<core::result::Result<T,E> as wasmtime::runtime::vm::traphandlers::HostResult>::maybe_catch_unwind::{{closure}}", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 243 },
        { fn: "<core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs", line: 272 },
        { fn: "std::panicking::catch_unwind::do_call", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panicking.rs", line: 589 },
        { fn: "___rust_try" },
        { fn: "std::panicking::catch_unwind", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panicking.rs", line: 552 },
        { fn: "std::panic::catch_unwind", file: "/Users/p.hickey/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/panic.rs", line: 359 },
        { fn: "<core::result::Result<T,E> as wasmtime::runtime::vm::traphandlers::HostResult>::maybe_catch_unwind", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 252 },
        { fn: "wasmtime::runtime::vm::traphandlers::catch_unwind_and_record_trap", file: "./crates/wasmtime/src/runtime/vm/traphandlers.rs", line: 130 },
        { fn: "wasmtime::runtime::vm::component::ComponentInstance::enter_host_from_wasm", file: "./crates/wasmtime/src/runtime/vm/component.rs", line: 237 },
        { fn: "wasmtime::runtime::component::func::host::call_host_and_handle_result", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 686 },
        { fn: "wasmtime::runtime::component::func::host::HostFunc::entrypoint", file: "./crates/wasmtime/src/runtime/component/func/host.rs", line: 106 },
    ],
)

@github-actions github-actions bot added wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself labels Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant