diff options
-rw-r--r-- | src/lib.rs | 569 |
1 files changed, 279 insertions, 290 deletions
diff --git a/src/lib.rs b/src/lib.rs index c8b0d80..a4faec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,16 +30,16 @@ //! //! # Examples //! -//! ``` +//! ```no_run //! #[macro_use] //! extern crate hexchat_plugin; //! //! use std::sync::Mutex; -//! use hexchat_plugin::{Plugin, PluginHandle, CommandHookHandle}; +//! use hexchat_plugin::{Plugin, PluginHandle, HookHandle}; //! //! #[derive(Default)] //! struct MyPlugin { -//! cmd: Mutex<Option<CommandHookHandle>> +//! cmd: Mutex<Option<HookHandle>> //! } //! //! impl Plugin for MyPlugin { @@ -169,13 +169,16 @@ pub use infoid::InfoId; pub use word::*; use pluginfo::PluginInfo; +use internals::Ph as RawPh; +use internals::HexchatEventAttrs as RawAttrs; +use std::borrow::Cow; use std::cell::Cell; use std::ffi::{CString, CStr}; use std::marker::PhantomData; use std::mem; use std::mem::ManuallyDrop; -use std::panic::catch_unwind; +use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind}; use std::ptr; use std::rc::Rc; use std::rc::Weak as RcWeak; @@ -183,6 +186,9 @@ use std::str::FromStr; use std::thread; use std::time::{SystemTime, UNIX_EPOCH, Duration}; +#[doc(hidden)] +pub use libc::{c_char, c_int, c_void, time_t}; + // ****** // // PUBLIC // // ****** // @@ -222,7 +228,7 @@ pub trait Plugin { /// /// # Examples /// -/// ``` +/// ```no_run /// use hexchat_plugin::{PluginHandle}; /// /// fn init(ph: &mut PluginHandle) { @@ -230,7 +236,7 @@ pub trait Plugin { /// } /// ``` pub struct PluginHandle { - ph: *mut internals::Ph, + ph: *mut RawPh, skip_pri_ck: bool, // Used for registration info: PluginInfo, @@ -251,49 +257,21 @@ pub struct EventAttrs<'a> { _dummy: PhantomData<&'a ()>, } -/// A command hook handle. -#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"] -pub struct CommandHookHandle { - ph: *mut internals::Ph, - hh: *const internals::HexchatHook, - // this does actually store an Rc<...>, but on the other side of the FFI. - _f: PhantomData<Rc<CommandHookUd>>, -} - -/// A server hook handle. -#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"] -pub struct ServerHookHandle { - ph: *mut internals::Ph, - hh: *const internals::HexchatHook, - // this does actually store an Rc<...>, but on the other side of the FFI. - _f: PhantomData<Rc<ServerHookUd>>, -} - -/// A print hook handle. +/// A hook handle. #[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"] -pub struct PrintHookHandle { - ph: *mut internals::Ph, +pub struct HookHandle { + ph: *mut RawPh, hh: *const internals::HexchatHook, + freed: Rc<Cell<bool>>, // this does actually store an Rc<...>, but on the other side of the FFI. - _f: PhantomData<Rc<PrintHookUd>>, -} - -/// A timer hook handle. -#[must_use = "Hooks must be stored somewhere and are automatically unhooked on Drop"] -pub struct TimerHookHandle { - ph: *mut internals::Ph, - hh: *const internals::HexchatHook, - // avoids issues - alive: Rc<Cell<bool>>, - // this does actually store an Rc<...>, but on the other side of the FFI. - _f: PhantomData<Rc<TimerHookUd>>, + _f: PhantomData<Rc<HookUd>>, } /// A context. #[derive(Clone)] pub struct Context { ctx: RcWeak<*const internals::HexchatContext>, // may be null - closure: Rc<Cell<Option<PrintHookHandle>>>, + closure: Rc<Cell<Option<HookHandle>>>, } // #[derive(Debug)] // doesn't work @@ -310,55 +288,54 @@ impl<F: FnOnce(EnsureValidContext) -> R, R> InvalidContextError<F, R> { self.0 } } - -impl Drop for CommandHookHandle { - fn drop(&mut self) { - unsafe { - let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut CommandHookUd; - // we assume b is not null. this should be safe. - // just drop it - drop(Rc::from_raw(b)); - } - } -} -impl Drop for ServerHookHandle { +impl Drop for HookHandle { fn drop(&mut self) { - unsafe { - let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut ServerHookUd; - // we assume b is not null. this should be safe. - // just drop it - drop(Rc::from_raw(b)); + if self.freed.get() { + // already free'd. + return; } - } -} -impl Drop for PrintHookHandle { - fn drop(&mut self) { + self.freed.set(true); unsafe { - let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut PrintHookUd; + let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut HookUd; // we assume b is not null. this should be safe. // just drop it drop(Rc::from_raw(b)); } } } -impl Drop for TimerHookHandle { - fn drop(&mut self) { - if !self.alive.get() { - // already free'd. - return; - } - self.alive.set(false); - unsafe { - let b = ((*self.ph).hexchat_unhook)(self.ph, self.hh) as *mut TimerHookUd; - // we assume b is not null. this should be safe. - // just drop it - drop(Rc::from_raw(b)); + +/// Handles a hook panic at the C-Rust ABI boundary. +/// +/// # Safety +/// +/// `ph` must be a valid pointer (see `std::ptr::read`). +unsafe fn call_hook_protected<F: FnOnce() -> Eat + UnwindSafe>( + ph: *mut RawPh, + f: F +) -> Eat { + 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); + } + EAT_NONE } } } impl PluginHandle { - fn new(ph: *mut internals::Ph, info: PluginInfo) -> PluginHandle { + /// Wraps the raw handle. + /// + /// # Safety + /// + /// `ph` must be a valid pointer (see `std::ptr::read`). + unsafe fn new(ph: *mut RawPh, info: PluginInfo) -> PluginHandle { PluginHandle { ph, info, skip_pri_ck: false, } @@ -372,7 +349,7 @@ impl PluginHandle { /// /// # Examples /// - /// ``` + /// ```no_run /// use hexchat_plugin::PluginHandle; /// /// fn init(ph: &mut PluginHandle) { @@ -515,10 +492,10 @@ impl PluginHandle { /// /// # Examples /// - /// ``` - /// use hexchat_plugin::{PluginHandle, CommandHookHandle}; + /// ```no_run + /// use hexchat_plugin::{PluginHandle, HookHandle}; /// - /// fn register_commands(ph: &mut PluginHandle) -> Vec<CommandHookHandle> { + /// fn register_commands(ph: &mut PluginHandle) -> Vec<HookHandle> { /// vec![ /// ph.hook_command("hello-world", |ph, arg, arg_eol| { /// ph.print("Hello, World!"); @@ -527,47 +504,43 @@ impl PluginHandle { /// ] /// } /// ``` - pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe { - unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int { - // hook may unhook itself. - // however, we don't wanna free it until it has returned. - let f: Rc<CommandHookUd> = rc_clone_from_raw(ud as *const CommandHookUd); - let ph = f.1; - match catch_unwind(move || { - let word = Word::new(word); - let word_eol = WordEol::new(word_eol); - (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol).do_eat as libc::c_int - }) { - Result::Ok(v @ _) => v, - Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(estr) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, estr, false); - } else if let Some(estring) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &estring, false); - } - 0 // EAT_NONE - } - } + pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe { + unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, ud: *mut c_void) -> c_int { + let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd); + (f)(word, word_eol, ptr::null()).do_eat as c_int } - let b: Rc<CommandHookUd> = Rc::new((Box::new(cb), self.ph, self.info)); + let b: Rc<HookUd> = { + let ph = self.ph; + let info = self.info; + Rc::new(Box::new(move |word, word_eol, _| { + let cb = &cb; + unsafe { + call_hook_protected(ph, move || { + let mut ph = PluginHandle::new(ph, info); + let word = Word::new(word); + let word_eol = WordEol::new(word_eol); + cb(&mut ph, word, word_eol) + }) + } + })) + }; let name = CString::new(cmd).unwrap(); let help_text = help.map(CString::new).map(Result::unwrap); let bp = Rc::into_raw(b); unsafe { - let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as libc::c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _); + let res = ((*self.ph).hexchat_hook_command)(self.ph, name.as_ptr(), pri as c_int, callback, help_text.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), bp as *mut _); assert!(!res.is_null()); - CommandHookHandle { ph: self.ph, hh: res, _f: PhantomData } + HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData } } } /// Sets a server hook. /// /// # Examples /// - /// ``` - /// use hexchat_plugin::{PluginHandle, ServerHookHandle}; + /// ```no_run + /// use hexchat_plugin::{PluginHandle, HookHandle}; /// - /// fn register_server_hooks(ph: &mut PluginHandle) -> Vec<ServerHookHandle> { + /// fn register_server_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> { /// vec![ /// ph.hook_server("PRIVMSG", |ph, word, word_eol| { /// if word.len() > 0 && word[0].starts_with('@') { @@ -578,50 +551,47 @@ impl PluginHandle { /// ] /// } /// ``` - pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe { self.hook_server_attrs(cmd, move |ph, w, we, _| cb(ph, w, we), pri) } /// Sets a server hook, with attributes. - pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe { - unsafe extern "C" fn callback(word: *const *const libc::c_char, word_eol: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int { - // hook may unhook itself. - // however, we don't wanna free it until it has returned. - let f: Rc<ServerHookUd> = rc_clone_from_raw(ud as *const ServerHookUd); - let ph = f.1; - match catch_unwind(move || { - let word = Word::new(word); - let word_eol = WordEol::new(word_eol); - (f.0)(&mut PluginHandle::new(f.1, f.2), word, word_eol, (&*attrs).into()).do_eat as libc::c_int - }) { - Result::Ok(v @ _) => v, - Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(estr) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, estr, false); - } else if let Some(estring) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &estring, false); - } - 0 // EAT_NONE - } - } + pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + RefUnwindSafe { + unsafe extern "C" fn callback(word: *const *const c_char, word_eol: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int { + let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd); + (f)(word, word_eol, attrs).do_eat as c_int } - let b: Rc<ServerHookUd> = Rc::new((Box::new(cb), self.ph, self.info)); + let b: Rc<HookUd> = { + let ph = self.ph; + let info = self.info; + Rc::new(Box::new(move |word, word_eol, attrs| { + let cb = &cb; + unsafe { + call_hook_protected(ph, move || { + let mut ph = PluginHandle::new(ph, info); + let word = Word::new(word); + let word_eol = WordEol::new(word_eol); + let attrs = (&*attrs).into(); + cb(&mut ph, word, word_eol, attrs) + }) + } + })) + }; let name = CString::new(cmd).unwrap(); let bp = Rc::into_raw(b); unsafe { - let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _); + let res = ((*self.ph).hexchat_hook_server_attrs)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _); assert!(!res.is_null()); - ServerHookHandle { ph: self.ph, hh: res, _f: PhantomData } + HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData } } } /// Sets a print hook. /// /// # Examples /// - /// ``` - /// use hexchat_plugin::{PluginHandle, PrintHookHandle}; + /// ```no_run + /// use hexchat_plugin::{PluginHandle, HookHandle}; /// - /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<PrintHookHandle> { + /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> { /// vec![ /// ph.hook_print("Channel Message", |ph, arg| { /// if let Some(nick) = arg.get(0) { @@ -634,84 +604,96 @@ impl PluginHandle { /// ] /// } /// ``` - pub fn hook_print<F>(&mut self, name: &str, cb: F, mut pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_print<F>(&mut self, name: &str, cb: F, mut pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + RefUnwindSafe { // hmm, is there any way to avoid this code duplication? // hook_print is special because dummy prints (keypresses, Close Context) are handled // through here, but never through hook_print_attrs. :/ - unsafe extern "C" fn callback(word: *const *const libc::c_char, ud: *mut libc::c_void) -> libc::c_int { - // hook may unhook itself. - // however, we don't wanna free it until it has returned. - let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd); - let ph = f.1; - match catch_unwind(move || { - let word = Word::new(word); - (f.0)(&mut PluginHandle::new(f.1, f.2), word, EventAttrs::new()).do_eat as libc::c_int - }) { - Result::Ok(v @ _) => v, - Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(estr) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, estr, false); - } else if let Some(estring) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &estring, false); - } - 0 // EAT_NONE - } - } + unsafe extern "C" fn callback(word: *const *const c_char, ud: *mut c_void) -> c_int { + let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd); + (f)(word, ptr::null(), ptr::null()).do_eat as c_int } - if name == "Close Context" && (pri as libc::c_int == libc::c_int::min_value()) && !self.skip_pri_ck { + if name == "Close Context" && (pri as c_int == c_int::min_value()) && !self.skip_pri_ck { self.print("Warning: Attempted to hook Close Context with priority INT_MIN. Adjusting to INT_MIN+1."); - pri = (libc::c_int::min_value() + 1) as i32; + pri = (c_int::min_value() + 1) as i32; } - let b: Rc<PrintHookUd> = Rc::new((Box::new(move |ph, w, _| cb(ph, w)), self.ph, self.info)); + let b: Rc<HookUd> = { + let ph = self.ph; + let info = self.info; + Rc::new(Box::new(move |word, _, _| { + let cb = &cb; + unsafe { + call_hook_protected(ph, move || { + let mut ph = PluginHandle::new(ph, info); + let word = Word::new(word); + cb(&mut ph, word) + }) + } + })) + }; let name = CString::new(name).unwrap(); let bp = Rc::into_raw(b); unsafe { - let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _); + let res = ((*self.ph).hexchat_hook_print)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _); assert!(!res.is_null()); - PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData } + HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData } } } /// Sets a print hook, with attributes. - pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + ::std::panic::RefUnwindSafe { - unsafe extern "C" fn callback(word: *const *const libc::c_char, attrs: *const internals::HexchatEventAttrs, ud: *mut libc::c_void) -> libc::c_int { - // hook may unhook itself. - // however, we don't wanna free it until it has returned. - let f: Rc<PrintHookUd> = rc_clone_from_raw(ud as *const PrintHookUd); - let ph = f.1; - match catch_unwind(move || { - let word = Word::new(word); - (f.0)(&mut PluginHandle::new(f.1, f.2), word, (&*attrs).into()).do_eat as libc::c_int - }) { - Result::Ok(v @ _) => v, - Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(estr) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, estr, false); - } else if let Some(estring) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &estring, false); - } - 0 // EAT_NONE - } - } + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_plugin::{PluginHandle, HookHandle}; + /// + /// fn register_print_hooks(ph: &mut PluginHandle) -> Vec<HookHandle> { + /// vec![ + /// ph.hook_print_attrs("Channel Message", |ph, arg, attrs| { + /// if let Some(nick) = arg.get(0) { + /// if *nick == "KnOwN_SpAmMeR" { + /// return hexchat_plugin::EAT_ALL + /// } + /// } + /// hexchat_plugin::EAT_NONE + /// }, hexchat_plugin::PRI_NORM), + /// ] + /// } + /// ``` + pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + RefUnwindSafe { + unsafe extern "C" fn callback(word: *const *const c_char, attrs: *const RawAttrs, ud: *mut c_void) -> c_int { + let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd); + (f)(word, ptr::null(), attrs).do_eat as c_int } - let b: Rc<PrintHookUd> = Rc::new((Box::new(cb), self.ph, self.info)); + let b: Rc<HookUd> = { + let ph = self.ph; + let info = self.info; + Rc::new(Box::new(move |word, _, attrs| { + let cb = &cb; + unsafe { + call_hook_protected(ph, move || { + let mut ph = PluginHandle::new(ph, info); + let word = Word::new(word); + let attrs = (&*attrs).into(); + cb(&mut ph, word, attrs) + }) + } + })) + }; let name = CString::new(name).unwrap(); let bp = Rc::into_raw(b); unsafe { - let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as libc::c_int, callback, bp as *mut _); + let res = ((*self.ph).hexchat_hook_print_attrs)(self.ph, name.as_ptr(), pri as c_int, callback, bp as *mut _); assert!(!res.is_null()); - PrintHookHandle { ph: self.ph, hh: res, _f: PhantomData } + HookHandle { ph: self.ph, hh: res, freed: Default::default(), _f: PhantomData } } } /// Sets a timer hook /// /// # Examples /// - /// ``` - /// use hexchat_plugin::{PluginHandle, TimerHookHandle}; + /// ```no_run + /// use hexchat_plugin::{PluginHandle, HookHandle}; /// - /// fn register_timers(ph: &mut PluginHandle) -> Vec<TimerHookHandle> { + /// fn register_timers(ph: &mut PluginHandle) -> Vec<HookHandle> { /// vec![ /// ph.hook_timer(2000, |ph| { /// ph.print("timer up!"); @@ -720,63 +702,60 @@ impl PluginHandle { /// ] /// } /// ``` - pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe { - unsafe extern "C" fn callback(ud: *mut libc::c_void) -> libc::c_int { - // hook may unhook itself. - // however, we don't wanna free it until it has returned. - let f: Rc<TimerHookUd> = rc_clone_from_raw(ud as *const TimerHookUd); - let alive = f.1.clone(); // clone the alive because why not - let f = f.0.clone(); - let ph = f.1; - // we could technically free the Rc<TimerHookUd> here, I guess - match catch_unwind(move || { - (f.0)(&mut PluginHandle::new(f.1, f.2)) - }) { - Result::Ok(true) => 1, - Result::Ok(false) => { - // avoid double-free - if !alive.get() { - return 0; - } - // mark it no longer alive - alive.set(false); - // HexChat will automatically free the hook. - // we just need to free the userdata. - drop(Rc::from_raw(ud as *const TimerHookUd)); - 0 - }, - Result::Err(e @ _) => { - // if it's a &str or String, just print it - if let Some(estr) = e.downcast_ref::<&str>() { - hexchat_print_str(ph, estr, false); - } else if let Some(estring) = e.downcast_ref::<String>() { - hexchat_print_str(ph, &estring, false); - } - // avoid double-free - if !alive.get() { - return 0; + pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + RefUnwindSafe { + unsafe extern "C" fn callback(ud: *mut c_void) -> c_int { + let f: Rc<HookUd> = rc_clone_from_raw(ud as *const HookUd); + (f)(ptr::null(), ptr::null(), ptr::null()).do_eat as c_int + } + let freed = Rc::new(Cell::new(false)); + // helps us clean up the thing when returning false + let dropper = Rc::new(Cell::new(None)); + let b: Rc<HookUd> = { + let ph = self.ph; + let info = self.info; + let freed = AssertUnwindSafe(Rc::clone(&freed)); + let dropper = AssertUnwindSafe(Rc::clone(&dropper)); + Rc::new(Box::new(move |_, _, _| { + let cb = &cb; + let res = unsafe { + call_hook_protected(ph, move || { + let mut ph = PluginHandle::new(ph, info); + if cb(&mut ph) { + EAT_HEXCHAT + } else { + EAT_NONE + } + }) + }; + if res == EAT_NONE && !freed.get() { + freed.set(true); + unsafe { + // drop may panic + // and so may unwrap + // but we must not panic + // (kinda silly to abuse call_hook_protected here + // but hey, it works and it helps with stuff) + call_hook_protected(ph, || { + drop(Rc::from_raw(dropper.take().unwrap())); + EAT_NONE + }); } - // mark it no longer alive - alive.set(false); - // HexChat will automatically free the hook. - // we just need to free the userdata. - drop(Rc::from_raw(ud as *const TimerHookUd)); - 0 } - } - } - let alive = Rc::new(Cell::new(true)); - let b: Rc<TimerHookUd> = Rc::new((Rc::new((Box::new(cb), self.ph, self.info)), alive.clone())); + res + })) + }; let bp = Rc::into_raw(b); + dropper.set(Some(bp)); unsafe { - let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as libc::c_int, callback, bp as *mut _); + let res = ((*self.ph).hexchat_hook_timer)(self.ph, timeout as c_int, callback, bp as *mut _); assert!(!res.is_null()); - TimerHookHandle { ph: self.ph, hh: res, alive, _f: PhantomData } + HookHandle { ph: self.ph, hh: res, freed: freed, _f: PhantomData } } } /// Prints to the hexchat buffer. // this checks the context internally. if it didn't, it wouldn't be safe to have here. + // TODO write_fmt instead. pub fn print<T: ToString>(&mut self, s: T) { let s = s.to_string(); unsafe { @@ -785,7 +764,7 @@ impl PluginHandle { } /// Returns information on the current context. - pub fn get_info(&mut self, id: InfoId) -> Option<String> { + pub fn get_info<'a>(&'a mut self, id: InfoId) -> Option<&'a str> { let ph = self.ph; let id_cstring = CString::new(&*id.name()).unwrap(); unsafe { @@ -793,7 +772,7 @@ impl PluginHandle { if res.is_null() { None } else { - let s = CStr::from_ptr(res).to_owned().into_string(); + let s = CStr::from_ptr(res).to_str(); Some(s.expect("non-utf8 word - broken hexchat")) } } @@ -834,8 +813,8 @@ impl<'a> EventAttrs<'a> { } } -impl<'a> From<&'a internals::HexchatEventAttrs> for EventAttrs<'a> { - fn from(other: &'a internals::HexchatEventAttrs) -> EventAttrs<'a> { +impl<'a> From<&'a RawAttrs> for EventAttrs<'a> { + fn from(other: &'a RawAttrs) -> EventAttrs<'a> { EventAttrs { server_time: if other.server_time_utc > 0 { Some(UNIX_EPOCH + Duration::from_secs(other.server_time_utc as u64)) } else { None }, _dummy: PhantomData, @@ -912,11 +891,11 @@ impl<'a> EnsureValidContext<'a> { assert!(mode.is_ascii(), "mode must be ascii"); assert!(mpl >= 0, "mpl must be non-negative"); let v: Vec<CString> = iter.into_iter().map(|s| CString::new(s).unwrap()).collect(); - let mut v2: Vec<*const libc::c_char> = (&v).iter().map(|x| x.as_ptr()).collect(); - let arr: &mut [*const libc::c_char] = &mut *v2; + let mut v2: Vec<*const c_char> = (&v).iter().map(|x| x.as_ptr()).collect(); + let arr: &mut [*const c_char] = &mut *v2; unsafe { - ((*ph).hexchat_send_modes)(ph, arr.as_mut_ptr(), arr.len() as libc::c_int, - mpl as libc::c_int, sign as libc::c_char, mode as libc::c_char) + ((*ph).hexchat_send_modes)(ph, arr.as_mut_ptr(), arr.len() as c_int, + mpl as c_int, sign as c_char, mode as c_char) } } @@ -948,7 +927,7 @@ impl<'a> EnsureValidContext<'a> { panic!("too many arguments to emit_print (max 4), or iterator not fused"); } } - let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5]; + let mut argv: [*const c_char; 5] = [ptr::null(); 5]; for i in 0..4 { argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr()); } @@ -974,7 +953,7 @@ impl<'a> EnsureValidContext<'a> { panic!("too many arguments to emit_print_attrs (max 4), or iterator not fused"); } } - let mut argv: [*const libc::c_char; 5] = [ptr::null(); 5]; + let mut argv: [*const c_char; 5] = [ptr::null(); 5]; for i in 0..4 { argv[i] = args_cs[i].as_ref().map_or(ptr::null(), |s| s.as_ptr()); } @@ -1008,22 +987,30 @@ impl<'a> EnsureValidContext<'a> { } /// Sets a command hook - pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> CommandHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_command<F>(&mut self, cmd: &str, cb: F, pri: i32, help: Option<&str>) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe { self.ph.hook_command(cmd, cb, pri, help) } /// Sets a server hook - pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> ServerHookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_server<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol) -> Eat + 'static + RefUnwindSafe { self.ph.hook_server(cmd, cb, pri) } + /// Sets a server hook with attributes + pub fn hook_server_attrs<F>(&mut self, cmd: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + 'static + RefUnwindSafe { + self.ph.hook_server_attrs(cmd, cb, pri) + } /// Sets a print hook - pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> PrintHookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_print<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word) -> Eat + 'static + RefUnwindSafe { self.ph.hook_print(name, cb, pri) } + /// Sets a print hook with attributes + pub fn hook_print_attrs<F>(&mut self, name: &str, cb: F, pri: i32) -> HookHandle where F: Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + 'static + RefUnwindSafe { + self.ph.hook_print_attrs(name, cb, pri) + } /// Sets a timer hook - pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> TimerHookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + ::std::panic::RefUnwindSafe { + pub fn hook_timer<F>(&mut self, timeout: i32, cb: F) -> HookHandle where F: Fn(&mut PluginHandle) -> bool + 'static + RefUnwindSafe { self.ph.hook_timer(timeout, cb) } - pub fn get_info(&mut self, id: InfoId) -> Option<String> { + pub fn get_info<'b>(&'b mut self, id: InfoId) -> Option<&'b str> { self.ph.get_info(id) } } @@ -1033,19 +1020,21 @@ impl<'a> EnsureValidContext<'a> { // ******* // // Type aliases, used for safety type checking. -/// Userdata type used by a command hook. +// /// Userdata type used by a command hook. // We actually do want RefUnwindSafe. This function may be called multiple times, and it's not // automatically invalidated if it panics, so it may be called again after it panics. If you need // 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(&mut PluginHandle, Word, WordEol) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo); -/// Userdata type used by a server hook. -type ServerHookUd = (Box<dyn Fn(&mut PluginHandle, Word, WordEol, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo); -/// Userdata type used by a print hook. -type PrintHookUd = (Box<dyn Fn(&mut PluginHandle, Word, EventAttrs) -> Eat + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo); -/// Userdata type used by a timer hook. -type TimerHookUd = (Rc<(Box<dyn Fn(&mut PluginHandle) -> bool + ::std::panic::RefUnwindSafe>, *mut internals::Ph, PluginInfo)>, Rc<Cell<bool>>); +// 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>; +// /// Userdata type used by a print hook. +// 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>; +/// Userdata type used by a hook +type HookUd = Box<dyn Fn(*const *const c_char, *const *const c_char, *const RawAttrs) -> Eat + RefUnwindSafe>; /// The contents of an empty CStr. /// @@ -1059,7 +1048,7 @@ const EMPTY_CSTRING_DATA: &[u8] = b"\0"; /// /// Panics if b contains embedded nuls. // TODO const fn, once that's possible -fn cstr(b: &'static [u8]) -> *const libc::c_char { +fn cstr(b: &'static [u8]) -> *const c_char { CStr::from_bytes_with_nul(b).unwrap().as_ptr() } @@ -1079,20 +1068,20 @@ unsafe fn rc_clone_from_raw<T>(ptr: *const T) -> Rc<T> { /// /// This function doesn't validate the context. unsafe fn wrap_context(ph: &mut PluginHandle, ctx: *const internals::HexchatContext) -> Context { - let ctxp = std::panic::AssertUnwindSafe(Rc::new(ctx)); + let ctxp = AssertUnwindSafe(Rc::new(ctx)); let weak_ctxp = Rc::downgrade(&ctxp); // calling the Closure should drop the Context (sort of) - let closure: Rc<Cell<Option<PrintHookHandle>>> = Rc::new(Cell::new(None)); - let hook = std::panic::AssertUnwindSafe(Rc::downgrade(&closure)); // dropping the Context should drop the Closure + let closure: Rc<Cell<Option<HookHandle>>> = Rc::new(Cell::new(None)); + let hook = AssertUnwindSafe(Rc::downgrade(&closure)); // dropping the Context should drop the Closure ph.skip_pri_ck = true; closure.set(Some(ph.hook_print("Close Context", move |ph, _| { // need to be careful not to recurse or leak memory let ph = ph.ph; let ctx = ((*ph).hexchat_get_context)(ph); if **ctxp == ctx { - let _: Option<PrintHookHandle> = hook.upgrade().unwrap().replace(None); + let _: Option<HookHandle> = hook.upgrade().unwrap().replace(None); } EAT_NONE - }, libc::c_int::min_value()))); + }, c_int::min_value()))); ph.skip_pri_ck = false; Context { ctx: weak_ctxp, closure } } @@ -1106,7 +1095,7 @@ unsafe fn wrap_context(ph: &mut PluginHandle, ctx: *const internals::HexchatCont /// # Panics /// /// Panics if panic_on_nul is true and the string contains embedded nuls. -unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool) { +unsafe fn hexchat_print_str(ph: *mut RawPh, s: &str, panic_on_nul: bool) { match CString::new(s) { Result::Ok(cs @ _) => { let csr: &CStr = &cs; @@ -1116,20 +1105,20 @@ unsafe fn hexchat_print_str(ph: *mut internals::Ph, s: &str, panic_on_nul: bool) } } -/// Helper to manage owned internals::HexchatEventAttrs -struct HexchatEventAttrsHelper(*mut internals::HexchatEventAttrs, *mut internals::Ph); +/// Helper to manage owned RawAttrs +struct HexchatEventAttrsHelper(*mut RawAttrs, *mut RawPh); impl HexchatEventAttrsHelper { - fn new(ph: *mut internals::Ph) -> Self { + fn new(ph: *mut RawPh) -> Self { HexchatEventAttrsHelper(unsafe { ((*ph).hexchat_event_attrs_create)(ph) }, ph) } - fn new_with(ph: *mut internals::Ph, attrs: EventAttrs) -> Self { + fn new_with(ph: *mut RawPh, attrs: EventAttrs) -> Self { let helper = Self::new(ph); let v = attrs.server_time.or(Some(UNIX_EPOCH)).map(|st| match st.duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs(), Err(_) => 0 - }).filter(|&st| st < (libc::time_t::max_value() as u64)).unwrap() as libc::time_t; + }).filter(|&st| st < (time_t::max_value() as u64)).unwrap() as time_t; unsafe { (*helper.0).server_time_utc = v; } helper } @@ -1156,8 +1145,8 @@ struct PhUserdata { /// This function is unsafe because it doesn't check if the pointer is valid. /// /// Improper use of this function can leak memory. -unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) { - (*ph).userdata = Rc::into_raw(ud) as *mut libc::c_void; +unsafe fn put_userdata(ph: *mut RawPh, ud: Rc<PhUserdata>) { + (*ph).userdata = Rc::into_raw(ud) as *mut c_void; } // /// Clones the userdata from the plugin handle. @@ -1165,7 +1154,7 @@ unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) { // /// # Safety // /// // /// This function is unsafe because it doesn't check if the pointer is valid. -// unsafe fn clone_userdata(ph: *mut internals::Ph) -> Option<Rc<PhUserdata>> { +// unsafe fn clone_userdata(ph: *mut RawPh) -> Option<Rc<PhUserdata>> { // let ptr = (*ph).userdata as *const PhUserdata; // if ptr.is_null() { // None @@ -1179,7 +1168,7 @@ unsafe fn put_userdata(ph: *mut internals::Ph, ud: Rc<PhUserdata>) { /// # Safety /// /// This function is unsafe because it doesn't check if the pointer is valid. -unsafe fn pop_userdata(ph: *mut internals::Ph) -> Rc<PhUserdata> { +unsafe fn pop_userdata(ph: *mut RawPh) -> Rc<PhUserdata> { Rc::from_raw(mem::replace(&mut (*ph).userdata, ptr::null_mut()) as *mut PhUserdata) } @@ -1188,11 +1177,11 @@ unsafe fn pop_userdata(ph: *mut internals::Ph) -> Rc<PhUserdata> { // *********************** // #[doc(hidden)] -pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void, - plugin_name: *mut *const libc::c_char, - plugin_desc: *mut *const libc::c_char, - plugin_version: *mut *const libc::c_char, - arg: *const libc::c_char) -> libc::c_int +pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut c_void, + plugin_name: *mut *const c_char, + plugin_desc: *mut *const c_char, + plugin_version: *mut *const c_char, + arg: *const c_char) -> c_int where T: Plugin + Default + 'static { if plugin_handle.is_null() || plugin_name.is_null() || plugin_desc.is_null() || plugin_version.is_null() { // we can't really do anything here. @@ -1200,7 +1189,7 @@ pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void, // TODO maybe call abort. return 0; } - let ph = plugin_handle as *mut internals::Ph; + let ph = plugin_handle as *mut RawPh; // clear the "userdata" field first thing - if the deinit function gets called (wrong hexchat // version, other issues), we don't wanna try to drop the hexchat_dummy or hexchat_read_fd // function as if it were a Box! @@ -1290,17 +1279,17 @@ pub unsafe fn hexchat_plugin_init<T>(plugin_handle: *mut libc::c_void, } #[doc(hidden)] -pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) -> libc::c_int where T: Plugin { +pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut c_void) -> c_int where T: Plugin { let mut safe_to_unload = 1; // plugin_handle should never be null, but just in case. if !plugin_handle.is_null() { - let ph = plugin_handle as *mut internals::Ph; + let ph = plugin_handle as *mut RawPh; // userdata should also never be null. if !(*ph).userdata.is_null() { { let mut info: Option<PluginInfo> = None; { - let mut ausinfo = ::std::panic::AssertUnwindSafe(&mut info); + let mut ausinfo = AssertUnwindSafe(&mut info); safe_to_unload = if catch_unwind(move || { let userdata = pop_userdata(ph); **ausinfo = Some(userdata.pluginfo); @@ -1327,15 +1316,15 @@ pub unsafe fn hexchat_plugin_deinit<T>(plugin_handle: *mut libc::c_void) -> libc macro_rules! hexchat_plugin { ($t:ty) => { #[no_mangle] - pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::libc::c_void, - plugin_name: *mut *const $crate::libc::c_char, - plugin_desc: *mut *const $crate::libc::c_char, - plugin_version: *mut *const $crate::libc::c_char, - arg: *const $crate::libc::c_char) -> $crate::libc::c_int { + pub unsafe extern "C" fn hexchat_plugin_init(plugin_handle: *mut $crate::c_void, + plugin_name: *mut *const $crate::c_char, + plugin_desc: *mut *const $crate::c_char, + plugin_version: *mut *const $crate::c_char, + arg: *const $crate::c_char) -> $crate::c_int { $crate::hexchat_plugin_init::<$t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg) } #[no_mangle] - pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::libc::c_void) -> $crate::libc::c_int { + pub unsafe extern "C" fn hexchat_plugin_deinit(plugin_handle: *mut $crate::c_void) -> $crate::c_int { $crate::hexchat_plugin_deinit::<$t>(plugin_handle) } // unlike what the documentation states, there's no need to define hexchat_plugin_get_info. |