use std::{
panic::{AssertUnwindSafe, catch_unwind},
ptr,
};
use crate::net_types::{in_addr_t, sa_family_t, sockaddr, sockaddr_data, sockaddr_in};
pub trait FfiSentinel {
fn ffi_panic_sentinel() -> Self;
}
impl FfiSentinel for () {
fn ffi_panic_sentinel() {}
}
impl FfiSentinel for std::ffi::c_int {
fn ffi_panic_sentinel() -> Self {
-1
}
}
impl<T> FfiSentinel for *mut T {
fn ffi_panic_sentinel() -> Self {
ptr::null_mut()
}
}
impl<T> FfiSentinel for Option<Box<T>> {
fn ffi_panic_sentinel() -> Self {
None
}
}
impl FfiSentinel for sockaddr {
fn ffi_panic_sentinel() -> Self {
sockaddr {
sa_family: sa_family_t(0),
sa_data: sockaddr_data {
sockaddr_in: sockaddr_in {
sin_port: 0,
sin_addr: in_addr_t([0; 4]),
},
},
}
}
}
pub fn ffi_guard<R: FfiSentinel>(f: impl FnOnce() -> R) -> R {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(r) => r,
Err(payload) => {
let msg = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>");
tracing::error!(
panic = msg,
"ts_ffi: caught panic at the C boundary; returning failure sentinel"
);
R::ffi_panic_sentinel()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn guard_passes_through_ok() {
assert_eq!(ffi_guard(|| 42i32), 42);
assert!(ffi_guard(|| Some(Box::new(7u8))).is_some());
}
#[test]
fn guard_returns_sentinel_on_panic() {
let v: std::ffi::c_int = ffi_guard(|| panic!("boom"));
assert_eq!(v, -1, "c_int panic sentinel must be -1");
let p: *mut u8 = ffi_guard(|| panic!("boom"));
assert!(p.is_null(), "pointer panic sentinel must be null");
let h: Option<Box<u8>> = ffi_guard(|| panic!("boom"));
assert!(h.is_none(), "handle panic sentinel must be None");
let sa: sockaddr = ffi_guard(|| panic!("boom"));
assert_eq!(
sa.sa_family.0, 0,
"sockaddr panic sentinel must be AF_UNSPEC"
);
let v2: std::ffi::c_int = ffi_guard(|| panic!("{}", String::from("dynamic")));
assert_eq!(v2, -1);
}
}