rust-lang / rust-lang/rust

catch_unwind doesn't catch non-C++ exceptions on wasm

Open
#148,273 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug I-unsound O-wasm P-high T-compiler T-libs WG-ffi-unwind
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I tried this code:

#[unsafe(no_mangle)]
pub fn square(num: i32) -> std::thread::Result<i32> {
    std::panic::catch_unwind(|| bar())
}

unsafe extern "C-unwind" {
    safe fn bar() -> i32;
}

compiled with -Cpanic=unwind --crate-type cdylib --target wasm32-wasip1 + -Zbuild-std.

I expected to see this happen: An exception originating from bar which doesn't conform with the wasm C++ exception ABI will cause an abort, just like foreign exceptions would on native platforms when unwinding through catch_unwind.

Instead, this happened: catch_unwind only generates a wasm catch instruction matching the C++ exception tag. For unwinding out of functions that are not allowed to unwind at all we do correctly generate a catch_all instruction to call panic_cannot_unwind.

(func $square (;2;) (type 0) (param i32 i32)
    (local i32 i32 i32)
    global.get $__stack_pointer
    i32.const 16
    i32.sub
    local.tee 2
    global.set $__stack_pointer
    block ;; label = @1
      try ;; label = @2
        call $bar
        local.set 3
      catch 0
        local.set 3
        local.get 2
        global.set $__stack_pointer
        try ;; label = @3
          local.get 2
          i32.const 8
          i32.add
          local.get 3
          call $_ZN3std9panicking12catch_unwind7cleanup17h1c165d98d6186f41E
        catch_all
          local.get 2
          global.set $__stack_pointer
          call $_ZN4core9panicking19panic_cannot_unwind17h45f1d8e8d54a6393E
          unreachable
        end
        local.get 2
        i32.load offset=12
        local.set 3
        local.get 2
        i32.load offset=8
        local.set 4
        local.get 0
        local.get 4
        i32.store
        local.get 0
        local.get 3
        i32.store offset=4
        br 1 (;@1;)
      end
      local.get 0
      i32.const 0
      i32.store
      local.get 0
      local.get 3
      i32.store offset=4
    end
    local.get 2
    i32.const 16
    i32.add
    global.set $__stack_pointer
  )
Meta

rustc --version --verbose:

rustc 1.92.0-nightly (695857bc3 2025-10-21)
binary: rustc
commit-hash: 695857bc3f72ec4f59c79f323460fe488c38a53f
commit-date: 2025-10-21
host: x86_64-unknown-linux-gnu
release: 1.92.0-nightly
LLVM version: 21.1.3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the example with rustc nightly, -Cpanic=unwind, --crate-type cdylib, target wasm32-wasip1, and -Zbuild-std, then inspect the generated WebAssembly around catch_unwind and the shown catch instructions. Done means non-C++ exceptions from bar no longer pass through catch_unwind and instead follow the expected abort behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, wasm
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.