Does `extern "C"` actually support unwind?

I wanted to reproduce this behavior, but instead of abort got an unwind. Note that I haven't compiled it with "panic=abort".

Static lib code:

#[no_mangle]
pub extern "C" fn do_panic() {
    println!("Inner panic");
    panic!()
}

Caller code:

use std::panic::catch_unwind;

fn main() {
    catch_unwind(|| {
        println!("Entering catch");
        unsafe { do_panic() }
    })
    .expect_err("Caught!");

    println!("I'm still here");
}

extern "C" {
    fn do_panic();
}

The output:

Entering catch
Inner panic
thread 'main' panicked at 'explicit panic', src/lib.rs:4:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   2: core::panicking::panic
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:48:5
   3: do_panic
   4: handle_ffi_panic::main::{{closure}}
             at ./src/main.rs:6:18
   5: std::panicking::try::do_call
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:492:40
   6: __rust_try
   7: std::panicking::try
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:456:19
   8: std::panic::catch_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panic.rs:137:14
   9: handle_ffi_panic::main
             at ./src/main.rs:4:5
  10: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
I'm still here
  1. I guess nomicon info is outdated? Did I misunderstand something? This also works if I encapsulate panicking lib call inside C-lib call

  2. Should that be the case only when it is extern "C-unwind"?

extern "C" is not supposed to support unwinding. This is theoretically forbidden and may cause UB.

extern "C-unwind" is the right way, although it's still unstable.

Alternatively, you should use catch_unwind in the body of extern "C" functions and make them report errors some other way.

With #![feature(c_unwind)] attempting to unwind from rust out of an extern "C" function will abort. Currently it is UB. Attempting to unwindind into rust through extern "C" is UB and will remain so.

4 Likes

But if it is not supported, then why do we see an unwind instead of abort?

UB can do anything including behave the way you expected or the exact opposite of what you expected.

8 Likes

The nomicon info is not outdated, it is too early.^^ What the nomicon says will be true in a few releases, depending on the discussion in Tracking Issue for "C-unwind ABI", RFC 2945 · Issue #74990 · rust-lang/rust · GitHub.

It might be worth actually explicitly stating that in the Nomicon though, since the current wording does not match the current stable compiler.

6 Likes

Panicking in extern "C" fn is subtly broken, and will soon stop working completely. These functions are marked as nounwind in LLVM and it can use this information to miscompile code in more complex cases.

But the actual reason is that I've made noise about it :slight_smile: Rust was going to unconditionally hard abort on unwind here, before "C-unwind" existed, but I've abused panics to recover from libjpeg errors, and it'd make my library unusable. Now that Rust has added extern "C-unwind" aborting in extern "C" is coming back.

8 Likes

Btw, does panic catching from extern "Rust" functions also an UB? Is it due to the fact that Rust doesn't have a stable ABI?

Catching a panic from extern "Rust" fn is fully defined. In fact all functions are extern "Rust" fn by default. You just have to make sure that both sides are compiled with the same rustc version and use the same function signature if you use extern "Rust" { fn ... }.

1 Like