1use std::panic::{AssertUnwindSafe, catch_unwind};
23
24use crate::boundary::{PluginError, PluginErrorCode, PluginResult};
25
26pub fn guard<T>(f: impl FnOnce() -> Result<T, PluginError>) -> PluginResult<T> {
31 let result = catch_unwind(AssertUnwindSafe(f));
32 match result {
33 Ok(Ok(t)) => PluginResult::Ok(t),
34 Ok(Err(e)) => PluginResult::Err(e),
35 Err(payload) => {
36 let message = panic_message(payload.as_ref());
37 drop_payload(payload);
38 PluginResult::Err(PluginError::new(PluginErrorCode::Panic, message))
39 }
40 }
41}
42
43pub fn guard_infallible<T>(thunk_name: &str, f: impl FnOnce() -> T) -> T {
51 match catch_unwind(AssertUnwindSafe(f)) {
52 Ok(t) => t,
53 Err(payload) => {
54 let msg = panic_message(payload.as_ref());
55 drop_payload(payload);
56 log::error!(
57 target: "nautilus_plugin",
58 "plug-in panicked in `{thunk_name}` thunk; aborting process: {msg}",
59 );
60 std::process::abort();
61 }
62 }
63}
64
65pub fn guard_or_null<T>(thunk_name: &str, f: impl FnOnce() -> *mut T) -> *mut T {
71 match catch_unwind(AssertUnwindSafe(f)) {
72 Ok(ptr) => ptr,
73 Err(payload) => {
74 let msg = panic_message(payload.as_ref());
75 drop_payload(payload);
76 let _ = catch_unwind(AssertUnwindSafe(|| {
79 log::error!(
80 target: "nautilus_plugin",
81 "plug-in panicked in `{thunk_name}` thunk; returning null: {msg}",
82 );
83 }));
84 std::ptr::null_mut()
85 }
86 }
87}
88
89pub fn guard_drop(thunk_name: &str, f: impl FnOnce()) {
95 if let Err(payload) = catch_unwind(AssertUnwindSafe(f)) {
96 let msg = panic_message(payload.as_ref());
97 drop_payload(payload);
98 let _ = catch_unwind(AssertUnwindSafe(|| {
101 log::error!(
102 target: "nautilus_plugin",
103 "plug-in panicked in `{thunk_name}` thunk; value leaked: {msg}",
104 );
105 }));
106 }
107}
108
109pub fn drop_payload(payload: Box<dyn std::any::Any + Send>) {
118 let _ = catch_unwind(AssertUnwindSafe(move || drop(payload)));
119}
120
121pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
122 if let Some(s) = payload.downcast_ref::<&'static str>() {
123 (*s).to_string()
124 } else if let Some(s) = payload.downcast_ref::<String>() {
125 s.clone()
126 } else {
127 "plug-in panicked with non-string payload".to_string()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use std::sync::atomic::{AtomicUsize, Ordering};
134
135 use rstest::rstest;
136
137 use super::*;
138
139 #[rstest]
140 fn returns_ok_on_success() {
141 let r = guard(|| Ok::<u32, PluginError>(7));
142 assert_eq!(r.into_result().unwrap(), 7);
143 }
144
145 #[rstest]
146 fn returns_err_on_returned_error() {
147 let r = guard(|| Err::<u32, _>(PluginError::generic("boom")));
148 let e = r.into_result().unwrap_err();
149 assert_eq!(e.code, PluginErrorCode::Generic);
150 assert_eq!(e.message_string(), "boom");
151 }
152
153 #[rstest]
154 fn returns_err_on_string_panic() {
155 let r = guard(|| -> Result<u32, PluginError> { panic!("oops") });
156 let e = r.into_result().unwrap_err();
157 assert_eq!(e.code, PluginErrorCode::Panic);
158 assert!(e.message_string().contains("oops"));
159 }
160
161 #[rstest]
162 fn returns_err_on_non_string_panic() {
163 let r = guard(|| -> Result<u32, PluginError> {
164 std::panic::panic_any(42_u32);
165 });
166 let e = r.into_result().unwrap_err();
167 assert_eq!(e.code, PluginErrorCode::Panic);
168 assert!(e.message_string().contains("non-string"));
169 }
170
171 #[rstest]
172 fn guard_infallible_returns_inner_on_success() {
173 let v = guard_infallible("test", || 42u64);
174 assert_eq!(v, 42);
175 }
176
177 #[rstest]
178 fn guard_or_null_returns_inner_on_success() {
179 let boxed = Box::into_raw(Box::new(7u32));
180 let v = guard_or_null("test", || boxed);
181 assert_eq!(v, boxed);
182 unsafe { drop(Box::from_raw(boxed)) };
184 }
185
186 #[rstest]
187 fn guard_or_null_returns_null_on_panic() {
188 let v: *mut u32 = guard_or_null("test", || panic!("create panic"));
189 assert!(v.is_null());
190 }
191
192 #[rstest]
193 fn guard_drop_runs_inner_on_success() {
194 let mut ran = false;
195 guard_drop("test", || ran = true);
196 assert!(ran);
197 }
198
199 #[rstest]
200 fn guard_drop_swallows_panic() {
201 guard_drop("test", || panic!("drop panic"));
202 }
203
204 #[rstest]
205 fn drop_payload_swallows_panicking_drop() {
206 use std::{
211 any::Any,
212 sync::atomic::{AtomicUsize, Ordering},
213 };
214
215 static DROPS_OBSERVED: AtomicUsize = AtomicUsize::new(0);
216 struct Bomb;
217 impl Drop for Bomb {
218 fn drop(&mut self) {
219 DROPS_OBSERVED.fetch_add(1, Ordering::SeqCst);
220 panic!("drop panic");
221 }
222 }
223 DROPS_OBSERVED.store(0, Ordering::SeqCst);
224
225 let payload: Box<dyn Any + Send> = Box::new(Bomb);
226 drop_payload(payload);
227 assert_eq!(DROPS_OBSERVED.load(Ordering::SeqCst), 1);
228 }
229
230 #[rstest]
231 fn guard_survives_panic_any_with_panicking_drop() {
232 static DROPS_OBSERVED: AtomicUsize = AtomicUsize::new(0);
237 struct Bomb;
238 impl Drop for Bomb {
239 fn drop(&mut self) {
240 DROPS_OBSERVED.fetch_add(1, Ordering::SeqCst);
241 panic!("drop panic");
242 }
243 }
244
245 DROPS_OBSERVED.store(0, Ordering::SeqCst);
246 let r = guard(|| -> Result<u32, PluginError> {
247 std::panic::panic_any(Bomb);
248 });
249 let e = r.into_result().unwrap_err();
250 assert_eq!(e.code, PluginErrorCode::Panic);
251
252 assert_eq!(DROPS_OBSERVED.load(Ordering::SeqCst), 1);
254 }
255}