diff options
-rw-r--r-- | src/lib.rs | 93 |
1 files changed, 57 insertions, 36 deletions
diff --git a/src/lib.rs b/src/lib.rs index a4ef9e9..3585031 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -455,6 +455,30 @@ impl<'ph> Drop for Context<'ph> { } } +/// Logs a panic message. +/// +/// # Safety +/// +/// `ph` must be a valid pointer (see `std::ptr::read`). +unsafe fn log_panic(ph: *mut RawPh, e: Box<dyn std::any::Any + Send + 'static>) { + // if it's a &str or String, just print it + if let Some(s) = e.downcast_ref::<&str>() { + hexchat_print_str(ph, s, false); + } else if let Some(s) = e.downcast_ref::<String>() { + hexchat_print_str(ph, &s, false); + } else if let Some(s) = e.downcast_ref::<Cow<'static, str>>() { + hexchat_print_str(ph, &s, false); + } else { + hexchat_print_str(ph, "couldn't log panic message", false); + if let Err(e) = catch_unwind(AssertUnwindSafe(|| drop(e))) { + // eprintln panics, hexchat_print_str doesn't. + hexchat_print_str(ph, "ERROR: panicked while trying to log panic!", false); + mem::forget(e); + std::process::abort(); + } + } +} + /// Handles a hook panic at the C-Rust ABI boundary. /// /// # Safety @@ -467,16 +491,7 @@ unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>( match catch_unwind(f) { Result::Ok(v @ _) => v, Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(s) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, s, false); - } else if let Some(s) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &s, false); - } else if let Some(s) = e.downcast_ref::<Cow<'static, str>>() { - hexchat_print_str(ph, &s, false); - } else { - mem::forget(e); - } + log_panic(ph, e); EAT_NONE } } @@ -1105,7 +1120,7 @@ impl<'ph> PluginHandle<'ph> { } out.extend_from_slice(unsafe { // take out the extra NUL at the end. - ::std::slice::from_raw_parts(stripped as *const _, out_len - 1) + std::slice::from_raw_parts(stripped as *const _, out_len - 1) }); unsafe { ph_call!(hexchat_free(self, stripped as *const _)); @@ -1231,7 +1246,7 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> { } /// Compares two nicks based on the server's case mapping. - pub fn nickcmp(&self, nick1: &str, nick2: &str) -> ::std::cmp::Ordering { + pub fn nickcmp(&self, nick1: &str, nick2: &str) -> std::cmp::Ordering { use std::cmp::Ordering; let ph = &self.ph; // need to put this in a more permanent position than temporaries @@ -1480,13 +1495,13 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> { // mutable state, std provides std::sync::Mutex which has poisoning. Other interior mutability with // poisoning could also be used. std doesn't have anything for single-threaded performance (yet), // but hexchat isn't particularly performance-critical. -// type CommandHookUd = Box<dyn Fn(Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>; +// type CommandHookUd = Box<dyn Fn(Word, WordEol) -> Eat + std::panic::RefUnwindSafe>; // /// Userdata type used by a server hook. -// type ServerHookUd = Box<dyn Fn(Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>; +// type ServerHookUd = Box<dyn Fn(Word, WordEol, EventAttrs) -> Eat + std::panic::RefUnwindSafe>; // /// Userdata type used by a print hook. -// type PrintHookUd = Box<dyn Fn(Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>; +// type PrintHookUd = Box<dyn Fn(Word, EventAttrs) -> Eat + std::panic::RefUnwindSafe>; // /// Userdata type used by a timer hook. -// type TimerHookUd = Box<dyn Fn() -> bool + ::std::panic::RefUnwindSafe>; +// type TimerHookUd = Box<dyn Fn() -> bool + std::panic::RefUnwindSafe>; /// Userdata type used by a hook type HookUd<'f> = Box<dyn Fn(*const *const c_char, *const *const c_char, *const RawAttrs) -> Eat + RefUnwindSafe + 'f>; /// Contexts @@ -1640,10 +1655,9 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>, arg: *const c_char) -> c_int where T: Plugin<'ph> + Default + 'ph { if plugin_handle.ph.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() { - // we can't really do anything here. + // we can't really do anything here. just hope this doesn't panic. eprintln!("hexchat_plugin_init called with a null pointer that shouldn't be null - broken hexchat"); - // TODO maybe call abort. - return 0; + std::process::abort(); } let ph = plugin_handle.ph as *mut RawPh; // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat @@ -1655,7 +1669,7 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>, if let Ok(fname) = CStr::from_ptr(*plugin_name).to_owned().into_string() { fname } else { - eprintln!("failed to convert filename to utf8 - broken hexchat"); + hexchat_print_str(ph, "failed to convert filename to utf8 - broken hexchat", false); return 0; } } else { @@ -1728,8 +1742,7 @@ pub unsafe fn hexchat_plugin_init<'ph, T>(plugin_handle: LtPhPtr<'ph>, }, r @ _ => { if let Err(e) = r { - // TODO try to log panic? - mem::forget(e); + log_panic(ph, e); } 0 }, @@ -1742,7 +1755,7 @@ pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_in // plugin_handle should never be null, but just in case. if !plugin_handle.ph.is_null() { let ph = plugin_handle.ph as *mut RawPh; - // userdata should also never be null - unless we already unloaded. + // userdata should also never be null. if !(*ph).userdata.is_null() { { let mut info: Option<PluginInfo> = None; @@ -1751,35 +1764,43 @@ pub unsafe fn hexchat_plugin_deinit<'ph, T>(plugin_handle: LtPhPtr<'ph>) -> c_in safe_to_unload = match catch_unwind(move || { let mut userdata = pop_userdata(plugin_handle); let pluginfo = userdata.pluginfo; - userdata.plug.as_mut().deinit(&mut PluginHandle::new(plugin_handle, pluginfo, Rc::clone(&userdata.contexts))); - drop(userdata); + if let Err(e) = catch_unwind(AssertUnwindSafe(|| { + userdata.plug.as_mut().deinit(&mut { + PluginHandle::new( + plugin_handle, + pluginfo, + Rc::clone(&userdata.contexts) + ) + }); + })) { + // panics in deinit may be retried. + // however, one may need to close hexchat if that + // happens. + put_userdata(plugin_handle, userdata); + std::panic::resume_unwind(e); + } **ausinfo = Some(pluginfo); - // we drop the plugin regardless of whether or not - // deinit panics, altho it's worth noting that a panic - // in deinit followed by a panic in drop will cause an - // abort. - // we return 0 mostly as a hint that something went - // wrong, than anything else. - // we do deliberately leak pluginfo on panic, also. + drop(userdata); }) { Ok(_) => 1, Err(e) => { - mem::forget(e); + log_panic(ph, e); 0 } }; } if let Some(mut info) = info { info.drop_info(); - } else { - eprintln!("I have no idea tbh, I didn't know `pop_userdata` could panic!"); + safe_to_unload = 1; } } } else { - // this is completely normal if we've panicked before. + hexchat_print_str(ph, "plugin userdata was null, broken hexchat-unsafe-plugin?", false); } } else { + // we are once again hoping for the best here. eprintln!("hexchat_plugin_deinit called with a null plugin_handle - broken hexchat"); + std::process::abort(); } safe_to_unload } |