Search code examples
rustlanguage-lawyerundefined-behaviorffi

When is a panic on a Rust FFI boundary Undefined Behavior?


The rustnomicon contains the following example:

#[no_mangle]
extern "C" fn assert_nonzero(input: u32) {
    assert!(input != 0)
}

If assert_nonzero is called with the argument 0, the runtime is guaranteed to (safely) abort the process, whether or not compiled with panic=abort.


The Rust Reference on the other hand claims that:

Behavior considered undefined: [...] Calling a function with the wrong call ABI or unwinding from a function with the wrong unwind ABI.


These two statements seem contradictory to me. assert_nonzero has the C ABI, but unwinds from a Rust panic. So is that guaranteed to (safely) abort, or UB? It can't be both.

Chayim Friedman kindly suggested on another question that this is indeed UB.

So is the Rustnomicon simply outdated, or am I misunderstanding something?

More generally: Exactly when is a Rust panic crossing an FFI boundary considered Undefined Behavior?


Solution

  • From the RFC for -unwind ABIs:

    Changes to the behavior of existing ABI strings

    Prior to this RFC, any unwinding operation that crossed an extern "C" boundary, either from a panic! "escaping" from a Rust function defined with extern "C" or by entering Rust from another language via an entrypoint declared with extern "C", caused undefined behavior.

    This RFC retains most of that undefined behavior, with one exception: with the panic=unwind runtime, panic! will cause an abort if it would otherwise "escape" from a function defined with extern "C".

    This change will be applied to all ABI strings other than "Rust", such as "system".

    So it appears that this used to be UB, but after this RFC was accepted, this is no longer UB and guaranteed to abort.

    I don't know if we can say that the reference is outdated, since we can say that we just don't unwind (but abort) on panics from extern "C" Rust functions.