Skip to main content

memlink_runtime/
panic.rs

1//! Panic isolation for safe FFI calls.
2
3use std::panic::{self, AssertUnwindSafe, UnwindSafe};
4use std::sync::Once;
5
6use crate::error::{Error, Result};
7
8#[derive(Debug, Clone)]
9pub struct PanicError {
10    pub message: String,
11}
12
13impl std::fmt::Display for PanicError {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        write!(f, "Module panicked: {}", self.message)
16    }
17}
18
19impl std::error::Error for PanicError {}
20
21pub fn setup_panic_hook() {
22    static SETUP: Once = Once::new();
23    SETUP.call_once(|| {
24        let default_hook = panic::take_hook();
25        panic::set_hook(Box::new(move |panic_info| {
26            eprintln!("[RUNTIME PANIC] {}", format_panic_info(panic_info));
27            default_hook(panic_info);
28        }));
29    });
30}
31
32#[allow(clippy::incompatible_msrv)]
33fn format_panic_info(panic_info: &panic::PanicHookInfo<'_>) -> String {
34    let location = panic_info
35        .location()
36        .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
37        .unwrap_or_else(|| "unknown location".to_string());
38
39    let payload = panic_info
40        .payload()
41        .downcast_ref::<&str>()
42        .map(|s| s.to_string())
43        .or_else(|| panic_info.payload().downcast_ref::<String>().cloned())
44        .unwrap_or_else(|| format!("{:?}", panic_info.payload()));
45
46    format!("{} at {}", payload, location)
47}
48
49pub fn safe_call<F, R>(f: F) -> Result<R>
50where
51    F: FnOnce() -> R + UnwindSafe,
52{
53    match panic::catch_unwind(f) {
54        Ok(result) => Ok(result),
55        Err(panic_payload) => {
56            let message = extract_panic_message(&panic_payload);
57            Err(Error::ModulePanicked(message))
58        }
59    }
60}
61
62pub fn safe_call_unchecked<F, R>(f: F) -> Result<R>
63where
64    F: FnOnce() -> R,
65{
66    safe_call(AssertUnwindSafe(f))
67}
68
69fn extract_panic_message(payload: &(dyn std::any::Any + Send)) -> String {
70    if let Some(s) = payload.downcast_ref::<&str>() {
71        s.to_string()
72    } else if let Some(s) = payload.downcast_ref::<String>() {
73        s.clone()
74    } else {
75        format!("{:?}", payload)
76    }
77}