bolero_engine/
panic.rs

1use core::{
2    cell::RefCell,
3    fmt::{Debug, Display},
4    panic::PanicInfo,
5};
6use lazy_static::lazy_static;
7use std::{
8    panic::{catch_unwind, AssertUnwindSafe, RefUnwindSafe},
9    thread_local,
10};
11
12macro_rules! backtrace {
13    () => {{
14        if CAPTURE_BACKTRACE.with(|capture| *capture.borrow()) {
15            Some(std::backtrace::Backtrace::capture())
16        } else {
17            None
18        }
19    }};
20}
21
22thread_local! {
23    static ERROR: RefCell<Option<PanicError>> = const { RefCell::new(None) };
24    static CAPTURE_BACKTRACE: RefCell<bool> = RefCell::new(*RUST_BACKTRACE);
25    static FORWARD_PANIC: RefCell<bool> = const { RefCell::new(true) };
26    static THREAD_NAME: String = String::from(std::thread::current().name().unwrap_or("main"));
27}
28
29lazy_static! {
30    static ref RUST_BACKTRACE: bool = std::env::var("RUST_BACKTRACE")
31        .ok()
32        .map(|v| v == "1")
33        .unwrap_or(false);
34    static ref PANIC_HOOK: () = {
35        let prev_hook = std::panic::take_hook();
36
37        std::panic::set_hook(Box::new(move |reason| {
38            let panic = PanicError {
39                message: reason.to_string(),
40                location: reason.location().map(|l| l.to_string()),
41                backtrace: backtrace!(),
42                thread_name: thread_name(),
43            };
44            ERROR.with(|error| {
45                *error.borrow_mut() = Some(panic);
46            });
47            if FORWARD_PANIC.with(|forward| *forward.borrow()) {
48                prev_hook(reason);
49            }
50        }));
51    };
52}
53
54#[derive(Debug)]
55pub struct PanicError {
56    pub(crate) message: String,
57    // in some fuzzing modes we don't use these fields so ignore unused warnings
58    #[allow(dead_code)]
59    pub(crate) location: Option<String>,
60    #[allow(dead_code)]
61    pub(crate) backtrace: Option<std::backtrace::Backtrace>,
62    #[allow(dead_code)]
63    pub(crate) thread_name: String,
64}
65
66impl std::error::Error for PanicError {}
67
68impl Display for PanicError {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        write!(f, "{}", self.message)
71    }
72}
73
74impl PanicError {
75    pub(crate) fn new(message: String) -> Self {
76        Self {
77            message,
78            location: None,
79            backtrace: backtrace!(),
80            thread_name: thread_name(),
81        }
82    }
83}
84
85#[inline]
86pub fn catch<F: RefUnwindSafe + FnOnce() -> Result<bool, PanicError>>(
87    fun: F,
88) -> Result<bool, PanicError> {
89    let res = catch_unwind(AssertUnwindSafe(|| __panic_marker_start__(fun)));
90    match res {
91        Ok(Ok(v)) => Ok(v),
92        Ok(Err(err)) => Err(err),
93        Err(err) => {
94            if let Some(err) = take_panic() {
95                return Err(err);
96            }
97            macro_rules! try_downcast {
98                ($ty:ty, $fmt:expr) => {
99                    if let Some(err) = err.downcast_ref::<$ty>() {
100                        return Err(PanicError::new(format!($fmt, err)));
101                    }
102                };
103            }
104
105            // if an `any::Error` was returned, then the input wasn't valid
106            #[cfg(feature = "any")]
107            if err.downcast_ref::<bolero_generator::any::Error>().is_some() {
108                return Ok(false);
109            }
110
111            try_downcast!(PanicInfo, "{}");
112            try_downcast!(anyhow::Error, "{}");
113            try_downcast!(String, "{}");
114            try_downcast!(&'static str, "{}");
115            try_downcast!(Box<dyn Display>, "{}");
116            try_downcast!(Box<dyn Debug>, "{:?}");
117            Err(PanicError::new(
118                "thread panicked with an unknown error".to_string(),
119            ))
120        }
121    }
122}
123
124#[inline]
125pub fn take_panic() -> Option<PanicError> {
126    ERROR.with(|error| error.borrow_mut().take())
127}
128
129#[inline]
130pub fn capture_backtrace(value: bool) -> bool {
131    CAPTURE_BACKTRACE.with(|cell| {
132        let prev = *cell.borrow();
133        *cell.borrow_mut() = value;
134        prev
135    })
136}
137
138#[inline]
139pub fn forward_panic(value: bool) -> bool {
140    FORWARD_PANIC.with(|cell| {
141        let prev = *cell.borrow();
142        *cell.borrow_mut() = value;
143        prev
144    })
145}
146
147#[inline]
148pub fn set_hook() {
149    *PANIC_HOOK
150}
151
152#[inline]
153pub fn rust_backtrace() -> bool {
154    *RUST_BACKTRACE
155}
156
157#[inline]
158pub fn thread_name() -> String {
159    THREAD_NAME.with(|cell| cell.clone())
160}
161
162#[inline(never)]
163fn __panic_marker_start__<F: FnOnce() -> R, R>(f: F) -> R {
164    f()
165}