use libc::c_int;
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
mod glibc_compat;
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
mod macos_compat;
#[cfg(target_os = "linux")]
use glibc_compat as struct_defs;
#[cfg(target_os = "macos")]
use macos_compat as struct_defs;
pub use crate::struct_defs::{JmpBufFields, JmpBufStruct};
pub use crate::struct_defs::{SigJmpBufFields, SigJmpBufStruct};
pub type JmpBuf = *const JmpBufFields;
pub type SigJmpBuf = *const SigJmpBufFields;
extern "C" {
pub fn longjmp(jbuf: JmpBuf, val: c_int) -> !;
pub fn siglongjmp(jbuf: SigJmpBuf, val: c_int) -> !;
}
#[cfg(not(feature = "use_c_to_interface_with_setjmp"))]
mod asm_based;
#[cfg(not(feature = "use_c_to_interface_with_setjmp"))]
pub use asm_based::{call_with_setjmp, call_with_sigsetjmp};
#[cfg(feature = "use_c_to_interface_with_setjmp")]
mod cee_based;
#[cfg(feature = "use_c_to_interface_with_setjmp")]
pub use cee_based::{call_with_setjmp, call_with_sigsetjmp};
#[cfg(test)]
mod tests {
#![allow(unreachable_code)]
use super::*;
use expect_test::expect;
#[test]
fn setjmp_basically_works() {
assert_eq!(call_with_setjmp(|_env| { 0 }), 0);
assert_eq!(call_with_setjmp(|_env| { 3 }), 3);
assert_eq!(
call_with_setjmp(|env| {
unsafe {
longjmp(env, 4);
}
3
}),
4
);
}
#[test]
fn sigsetjmp_basically_works() {
assert_eq!(call_with_sigsetjmp(true, |_env| { 0 }), 0);
assert_eq!(call_with_sigsetjmp(true, |_env| { 3 }), 3);
assert_eq!(
call_with_sigsetjmp(true, |env| {
unsafe {
siglongjmp(env, 4);
}
3
}),
4
);
}
#[test]
fn check_control_flow_details_1() {
let mut record = String::new();
let result = call_with_setjmp(|env| {
record.push_str("A");
unsafe {
longjmp(env, 4);
}
record.push_str(" B");
0
});
assert_eq!(result, 4);
expect![["A"]].assert_eq(&record);
}
#[test]
fn check_control_flow_details_2() {
let mut record = String::new();
let result = call_with_setjmp(|_env1| {
record.push_str("A");
let ret = call_with_setjmp(|env2| {
record.push_str(" B");
unsafe {
longjmp(env2, 4);
}
record.push_str(" C");
0
});
record.push_str(" D");
ret + 1
});
assert_eq!(result, 5);
expect![["A B D"]].assert_eq(&record);
}
#[test]
fn check_control_flow_details_3() {
let mut record = String::new();
let result = call_with_setjmp(|env1| {
record.push_str("A");
let ret = call_with_setjmp(|_env2| {
record.push_str(" B");
unsafe {
longjmp(env1, 4);
}
record.push_str(" C");
0
});
record.push_str(" D");
ret + 1
});
assert_eq!(result, 4);
expect![["A B"]].assert_eq(&record);
}
#[cfg(feature = "test_c_integration")]
#[test]
fn c_integration() {
extern "C" {
fn subtract_but_longjmp_if_underflow(env: JmpBuf, a: u32, b: u32) -> u32;
}
assert_eq!(
call_with_setjmp(|env| {
(unsafe { subtract_but_longjmp_if_underflow(env, 10, 3) }) as c_int
}),
7
);
assert_eq!(
call_with_setjmp(|env| {
unsafe {
subtract_but_longjmp_if_underflow(env, 3, 10);
panic!("should never get here.");
}
}),
7
);
}
#[cfg(feature = "test_c_integration")]
#[test]
fn check_c_layout() {
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
struct LayoutOfJmpBufs {
jb_size: usize,
jb_align: usize,
sigjb_size: usize,
sigjb_align: usize,
}
extern "C" {
fn get_c_jmpbuf_layout() -> LayoutOfJmpBufs;
}
let cinfo = unsafe { get_c_jmpbuf_layout() };
eprintln!("Note: C jmp_buf/sigjmp_buf layout info: {cinfo:?}");
assert_eq!(cinfo.jb_size, core::mem::size_of::<JmpBufStruct>());
assert_eq!(cinfo.jb_align, core::mem::align_of::<JmpBufStruct>());
assert_eq!(cinfo.sigjb_size, core::mem::size_of::<SigJmpBufStruct>());
assert_eq!(cinfo.sigjb_align, core::mem::align_of::<SigJmpBufStruct>());
}
}
#[cfg(test)]
mod tests_of_drop_interaction {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::{call_with_setjmp, call_with_sigsetjmp};
struct IncrementOnDrop(&'static str, &'static AtomicUsize);
impl IncrementOnDrop {
fn new(name: &'static str, state: &'static AtomicUsize) -> Self {
println!("called new for {name}");
IncrementOnDrop(name, state)
}
}
impl Drop for IncrementOnDrop {
fn drop(&mut self) {
println!("called drop on {}", self.0);
self.1.fetch_add(1, Ordering::Relaxed);
}
}
#[test]
fn does_ptr_read_cause_a_double_drop_for_setjmp() {
static STATE: AtomicUsize = AtomicUsize::new(0);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_setjmp(move |_env| {
println!("at callback 1 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
assert_eq!(STATE.load(Ordering::Relaxed), 1);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_setjmp(move |_env| {
println!("at callback 2 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
assert_eq!(STATE.load(Ordering::Relaxed), 2);
}
#[test]
fn does_ptr_read_cause_a_double_drop_for_sigsetjmp() {
static STATE: AtomicUsize = AtomicUsize::new(0);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_sigsetjmp(false, move |_env| {
println!("at callback 3 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
assert_eq!(STATE.load(Ordering::Relaxed), 1);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_sigsetjmp(true, move |_env| {
println!("at callback 4 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
assert_eq!(STATE.load(Ordering::Relaxed), 2);
}
#[test]
fn mix_drop_with_longjmp() {
use crate::longjmp;
static STATE: AtomicUsize = AtomicUsize::new(0);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_setjmp(move |env1| {
println!("at callback 1 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
unsafe { longjmp(env1, 4) }
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
assert_eq!(STATE.load(Ordering::Relaxed), 0);
}
}