Is it possible to replace the current #[panic_handler] implemented in `std` crate without removing the dependency of `std` crate?

I found a old thread about that, but that does not help.

I'm currently writting a new Rust lib for R, the crate works perfect in #![no_std] mode since I wrote a simple #[global_allocator] and a nounwind #[panic_handler].

The logic for handling panic is quite easy:

    fn panic_handler(info: &core::panic::PanicInfo) -> ! {
        // print errors.
        // adding extra scope to ensure all the dynamic allocated resources are dropped.
        let strsxp={
            // a lot of logics that write to a `mut message : String`
            RStr::from_str(message.as_str())
        };
        unsafe {
            Rf_error(strsxp.0.data() as *const c_char)
        }
    }

the RF_error function is a ffi which never returns, and the strsxp could be collected by R.

The problem is that, I have no such choice in std.

Firstly, I tried update_hook, which could only accept a fn(&PanicInfo) rather than fn(&PanicInfo)->!, if we terminate the execution in update_hook, Rust could not recognize whether the panic is handled, and thus a double panic would occur if we call the program later and another panic is generated.
Secondly, I tried modify the panic counter, but since std::panicking is a private mod, I can do nothing.

I know there could be a catch_unwind function, but writting it for every ffi function is painful.

Is there a good choice for handling panics for ffi functions?

Does Rf_Error longjmp? If so even directly overriding panic_handler would result in UB. You can't longjmp over rust code.

1 Like

Thank you for mention me about the longjmp, but... according to some search, I found it OK to call longjmp:

I have no idea.
According to this RFC and this unaccepted RFC, extern "C-unwind" should be enough.
Even with panic=unwind, this PR suggests, extern "C" should work fine currently.

That snippet is assuming that we declare longjmp to be allowed (we haven't made a decision either way yet) and in addition that every frame you would jump over does not have any catch_unwind or locals in scope with a Drop impl that would be skipped by the longjmp. In your case you can't guarantee that the latter is the case as there will be user code between the setjmp and the longjmp. Skipping any catch_unwind or Drop impl due to a longjmp or for any other reason is UB. For example:

let mut some_stack_value = 1;

std::thread::scope(|s| {
    s.spawn(|| {
        std::thread::sleep_ms(1000);
        some_stack_value += 1;
    });

    longjmp_to_the_end_of_the_current_thread();
});

println!("{}", some_stack_value);

Here the longjmp_to_the_end_of_the_current_thread() will skip the catch_unwind in thread::scope, causing the child thread to not be awaited and cause thus the parent thread to exit while the child thread still has a reference to the stack of the parent thread, which will then cause a crash once the child thread attempts to increment the value on the stack of the parent thread.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.