How do I fix "may not be safely transferred across an unwind boundary" for VaList?

Viewed 904

I am trying to override/wrap the Libc vprintf(format, va_list) function with Rust code. To do so, I need to pass a VaList argument into unsafe code that also needs to catch unwind errors:

#![feature(c_variadic)]
extern crate libc;

use libc::{c_char, c_int};

pub unsafe extern "C" fn vprintf(format: *const c_char, args: std::ffi::VaList) -> c_int {
    if true {
        ::std::panic::catch_unwind(|| hook_fn(format, args)).ok()
    } else {
        None
    }
    .unwrap_or_else(|| hook_fn(format, args))
}

pub unsafe fn hook_fn(format: *const c_char, args: std::ffi::VaList) -> c_int {
    0
}

fn main() {
    println!("Hello, world!");
}

My code does not compile:

error[E0277]: the type `&mut std::ffi::VaListImpl<'_>` may not be safely transferred across an unwind boundary
   --> src/main.rs:8:9
    |
8   |         ::std::panic::catch_unwind(|| hook_fn(format, args)).ok()
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ ------------------------ within this `[closure@src/main.rs:8:36: 8:60 format:&*const i8, args:std::ffi::VaList<'_, '_>]`
    |         |
    |         `&mut std::ffi::VaListImpl<'_>` may not be safely transferred across an unwind boundary
    |
    = help: within `[closure@src/main.rs:8:36: 8:60 format:&*const i8, args:std::ffi::VaList<'_, '_>]`, the trait `std::panic::UnwindSafe` is not implemented for `&mut std::ffi::VaListImpl<'_>`
    = note: `std::panic::UnwindSafe` is implemented for `&std::ffi::VaListImpl<'_>`, but not for `&mut std::ffi::VaListImpl<'_>`
    = note: required because it appears within the type `std::ffi::VaList<'_, '_>`
    = note: required because it appears within the type `[closure@src/main.rs:8:36: 8:60 format:&*const i8, args:std::ffi::VaList<'_, '_>]`
2 Answers

Some types, especially FFI types, can cause Undefined Behaviour if used after a panic. This safety is tracked by whether or not a type implements UnwindSafe, and VaList does not.

This is explained in the first "help" line of the error message:

= help: within `[closure@src/main.rs:8:36: 8:61 format:&*const i8, args:std::ffi::VaList<'_, '_>]`, 
the trait `std::panic::UnwindSafe` is not implemented for `&mut std::ffi::VaListImpl<'_>`

The first "note" also gives you a possible solution:

note: `std::panic::UnwindSafe` is implemented for `&std::ffi::VaListImpl<'_>`, but not for `&mut std::ffi::VaListImpl<'_>`

It's telling you that it is safe to share immutable references to a VaListImpl across an unwind boundary. So you can fix your code by passing the value by reference instead:

pub unsafe extern "C" fn vprintf(format: *const c_char, args: std::ffi::VaList) -> c_int {
    if true {
        ::std::panic::catch_unwind(|| hook_fn(format, &args)).ok()
    } else {
        None
    }
    .unwrap_or_else(|| hook_fn(format, &args))
}

pub unsafe fn hook_fn(format: *const c_char, args: &std::ffi::VaList) -> c_int {
    0
}

This helped fix the problem. Not sure if this is the right way to fix this. Peter Halls suggestion above might still be right one. But it did not seem to fix the error for me.

#![feature(c_variadic)]
extern crate libc;

use libc::{c_char, c_int};

pub unsafe extern "C" fn vprintf(format: *const c_char, args: std::ffi::VaList) -> c_int {
    let ap : VaListImpl = args.clone();
    if true {
        ::std::panic::catch_unwind(|| hook_fn(format, ap.clone())).ok()
    } else {
        None
    }
    .unwrap_or_else(|| hook_fn(format, args))
}

pub unsafe fn hook_fn(format: *const c_char, args: std::ffi::VaList) -> c_int {
    0
}

fn main() {
    println!("Hello, world!");
}
Related