summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs93
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
 }