1use std::{
12 any::Any,
13 ffi::c_void,
14 mem::MaybeUninit,
15 panic::{catch_unwind, AssertUnwindSafe},
16 ptr,
17};
18
19use super::{
20 bindings as napi,
21 debug_send_wrapper::DebugSendWrapper,
22 error::fatal_error,
23 raw::{Env, Local},
24};
25
26type Panic = Box<dyn Any + Send + 'static>;
27
28const UNKNOWN_PANIC_MESSAGE: &str = "Unknown panic";
29
30pub struct FailureBoundary {
43 pub both: &'static str,
44 pub exception: &'static str,
45 pub panic: &'static str,
46}
47
48impl FailureBoundary {
49 #[track_caller]
50 pub unsafe fn catch_failure<F>(&self, env: Env, deferred: Option<napi::Deferred>, f: F)
51 where
52 F: FnOnce(Option<Env>) -> Local,
53 {
54 #[allow(clippy::unnecessary_lazy_evaluations)]
56 let env = can_call_into_js(env).then(|| env);
57
58 let panic = catch_unwind(AssertUnwindSafe(move || f(env)));
61
62 let env = if let Some(env) = env {
64 env
65 } else {
66 if let Err(panic) = panic {
68 let msg = panic_msg(&panic).unwrap_or(UNKNOWN_PANIC_MESSAGE);
69
70 fatal_error(msg);
71 }
72
73 return;
75 };
76
77 let exception = catch_exception(env);
79
80 let msg = match (exception, panic.as_ref()) {
82 (Some(_), Err(_)) => self.both,
84
85 (Some(err), Ok(_)) => {
87 if let Some(deferred) = deferred {
89 reject_deferred(env, deferred, err);
90
91 return;
92 }
93
94 self.exception
95 }
96
97 (None, Err(_)) => self.panic,
99
100 (None, Ok(value)) => {
102 if let Some(deferred) = deferred {
103 resolve_deferred(env, deferred, *value);
104 }
105
106 return;
107 }
108 };
109
110 if let Some(deferred) = deferred {
112 let error = create_error(env, msg, exception, panic.err());
113
114 reject_deferred(env, deferred, error);
115
116 return;
117 }
118
119 let error = create_error(env, msg, exception, panic.err());
120
121 fatal_exception(env, error);
123 }
124}
125
126fn can_call_into_js(env: Env) -> bool {
136 !env.is_null() && unsafe { napi::throw(env, ptr::null_mut()) == Err(napi::Status::InvalidArg) }
137}
138
139unsafe fn fatal_exception(env: Env, error: Local) {
143 let mut deferred = MaybeUninit::uninit();
144 let mut promise = MaybeUninit::uninit();
145
146 let deferred = match napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()) {
147 Ok(()) => deferred.assume_init(),
148 _ => fatal_error("Failed to create a promise"),
149 };
150
151 if napi::reject_deferred(env, deferred, error) != Ok(()) {
152 fatal_error("Failed to reject a promise");
153 }
154}
155
156#[track_caller]
157unsafe fn create_error(
158 env: Env,
159 msg: &str,
160 exception: Option<Local>,
161 panic: Option<Panic>,
162) -> Local {
163 let error = error_from_message(env, msg);
165
166 if let Some(exception) = exception {
168 set_property(env, error, "cause", exception);
169 };
170
171 if let Some(panic) = panic {
173 set_property(env, error, "panic", error_from_panic(env, panic));
174 }
175
176 error
177}
178
179#[track_caller]
180unsafe fn resolve_deferred(env: Env, deferred: napi::Deferred, value: Local) {
181 if napi::resolve_deferred(env, deferred, value) != Ok(()) {
182 fatal_error("Failed to resolve promise");
183 }
184}
185
186#[track_caller]
187unsafe fn reject_deferred(env: Env, deferred: napi::Deferred, value: Local) {
188 if napi::reject_deferred(env, deferred, value) != Ok(()) {
189 fatal_error("Failed to reject promise");
190 }
191}
192
193#[track_caller]
194unsafe fn catch_exception(env: Env) -> Option<Local> {
195 if !is_exception_pending(env) {
196 return None;
197 }
198
199 let mut error = MaybeUninit::uninit();
200
201 if napi::get_and_clear_last_exception(env, error.as_mut_ptr()) != Ok(()) {
202 fatal_error("Failed to get and clear the last exception");
203 }
204
205 Some(error.assume_init())
206}
207
208#[track_caller]
209unsafe fn error_from_message(env: Env, msg: &str) -> Local {
210 let msg = create_string(env, msg);
211 let mut err = MaybeUninit::uninit();
212
213 let status = napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr());
214
215 match status {
216 Ok(()) => err.assume_init(),
217 Err(_) => fatal_error("Failed to create an Error"),
218 }
219}
220
221#[track_caller]
222unsafe fn error_from_panic(env: Env, panic: Panic) -> Local {
223 if let Some(msg) = panic_msg(&panic) {
224 error_from_message(env, msg)
225 } else {
226 let error = error_from_message(env, UNKNOWN_PANIC_MESSAGE);
227 let panic = external_from_panic(env, panic);
228
229 set_property(env, error, "cause", panic);
230 error
231 }
232}
233
234#[track_caller]
235unsafe fn set_property(env: Env, object: Local, key: &str, value: Local) {
236 let key = create_string(env, key);
237
238 if napi::set_property(env, object, key, value).is_err() {
239 fatal_error("Failed to set an object property");
240 }
241}
242
243#[track_caller]
244unsafe fn panic_msg(panic: &Panic) -> Option<&str> {
245 if let Some(msg) = panic.downcast_ref::<&str>() {
246 Some(msg)
247 } else if let Some(msg) = panic.downcast_ref::<String>() {
248 Some(msg)
249 } else {
250 None
251 }
252}
253
254unsafe fn external_from_panic(env: Env, panic: Panic) -> Local {
255 let fail = || fatal_error("Failed to create a neon::types::JsBox from a panic");
256 let mut result = MaybeUninit::uninit();
257
258 if napi::create_external(
259 env,
260 Box::into_raw(Box::new(DebugSendWrapper::new(panic))).cast(),
261 Some(finalize_panic),
262 ptr::null_mut(),
263 result.as_mut_ptr(),
264 )
265 .is_err()
266 {
267 fail();
268 }
269
270 let external = result.assume_init();
271
272 #[cfg(feature = "napi-8")]
273 if napi::type_tag_object(env, external, &*crate::MODULE_TAG).is_err() {
274 fail();
275 }
276
277 external
278}
279
280extern "C" fn finalize_panic(_env: Env, data: *mut c_void, _hint: *mut c_void) {
281 unsafe {
282 drop(Box::from_raw(data.cast::<Panic>()));
283 }
284}
285
286#[track_caller]
287unsafe fn create_string(env: Env, msg: &str) -> Local {
288 let mut string = MaybeUninit::uninit();
289
290 if napi::create_string_utf8(env, msg.as_ptr().cast(), msg.len(), string.as_mut_ptr()).is_err() {
291 fatal_error("Failed to create a String");
292 }
293
294 string.assume_init()
295}
296
297unsafe fn is_exception_pending(env: Env) -> bool {
298 let mut throwing = false;
299
300 if napi::is_exception_pending(env, &mut throwing).is_err() {
301 fatal_error("Failed to check if an exception is pending");
302 }
303
304 throwing
305}