use std::any::Any;
use std::cell::UnsafeCell;
use std::ops::Deref;
use std::os::raw::c_int;
use std::panic::{catch_unwind, UnwindSafe};
use std::process;
use std::ptr;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::usize;
use crate::ffi::{hexchat_plugin, result_to_int, RawPluginHandle};
use crate::plugin::{Plugin, PluginHandle};
static LAST_RESORT_PLUGIN_HANDLE: AtomicPtr<hexchat_plugin> = AtomicPtr::new(ptr::null_mut());
pub(crate) fn catch_and_log_unwind<R>(
ctxt_msg: &str,
f: impl FnOnce() -> R + UnwindSafe,
) -> Result<R, ()> {
#[cold]
#[inline(never)]
fn abort_process_due_to_panic_in_panic_logger() -> ! {
process::abort()
}
#[cold]
#[inline(never)]
fn abort_process_due_to_panic_without_plugin_handle() -> ! {
process::abort()
}
#[cold]
#[inline(never)]
fn handle_plugin_panic(ctxt_msg: &str, e: Box<dyn Any + Send>) {
let panic_msg = if let Some(s) = e.downcast_ref::<String>() {
s.as_str()
} else if let Some(s) = e.downcast_ref::<&'static str>() {
s
} else {
&"<unknown>"
};
eprintln!(
"WARNING: `hexavalent` caught panic (in `{}`): {}",
ctxt_msg, panic_msg
);
let plugin_handle = LAST_RESORT_PLUGIN_HANDLE.load(Ordering::Relaxed);
if plugin_handle.is_null() {
eprintln!("FATAL: `hexavalent` cannot find a plugin context");
abort_process_due_to_panic_without_plugin_handle()
} else {
let message = format!(
"WARNING: `hexavalent` caught panic (in `{}`): {}\0",
ctxt_msg, panic_msg
);
unsafe { ((*plugin_handle).hexchat_print)(plugin_handle, message.as_ptr().cast()) }
}
}
catch_unwind(|| match catch_unwind(f) {
Ok(x) => Ok(x),
Err(e) => {
handle_plugin_panic(ctxt_msg, e);
Err(())
}
})
.unwrap_or_else(|_| abort_process_due_to_panic_in_panic_logger())
}
const NO_READERS: usize = 0;
const LOCKED: usize = usize::MAX;
static STATE: AtomicUsize = AtomicUsize::new(NO_READERS);
struct ExtSync<T>(UnsafeCell<T>);
unsafe impl<T> Sync for ExtSync<T> {}
impl<T> Deref for ExtSync<T> {
type Target = UnsafeCell<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct GlobalPlugin {
#[cfg(debug_assertions)]
thread_id: std::thread::ThreadId,
plugin: Box<dyn Any>,
plugin_handle: NonNull<hexchat_plugin>,
}
static PLUGIN: ExtSync<Option<GlobalPlugin>> = ExtSync(UnsafeCell::new(None));
pub(crate) unsafe fn hexchat_plugin_init<P: Plugin>(plugin_handle: *mut hexchat_plugin) -> c_int {
result_to_int(catch_and_log_unwind("init", || {
LAST_RESORT_PLUGIN_HANDLE.store(plugin_handle, Ordering::Relaxed);
let plugin_handle = match NonNull::new(plugin_handle) {
Some(ph) => ph,
None => panic!("Plugin initialized with null handle"),
};
{
STATE
.compare_exchange(NO_READERS, LOCKED, Ordering::Relaxed, Ordering::Relaxed)
.unwrap_or_else(|e| panic!("Plugin initialized while running, state: {}", e));
defer! { STATE.store(NO_READERS, Ordering::Relaxed) };
unsafe {
*PLUGIN.get() = Some(GlobalPlugin {
#[cfg(debug_assertions)]
thread_id: std::thread::current().id(),
plugin: Box::<P>::default(),
plugin_handle,
});
}
}
with_plugin_state(|plugin: &P, ph| plugin.init(ph));
}))
}
pub(crate) unsafe fn hexchat_plugin_deinit<P: Plugin>(plugin_handle: *mut hexchat_plugin) -> c_int {
let _ = plugin_handle;
result_to_int(catch_and_log_unwind("deinit", || {
with_plugin_state(|plugin: &P, ph| plugin.deinit(ph));
{
STATE
.compare_exchange(NO_READERS, LOCKED, Ordering::Relaxed, Ordering::Relaxed)
.unwrap_or_else(|e| panic!("Plugin deinitialized while running, state: {}", e));
defer! { STATE.store(NO_READERS, Ordering::Relaxed) };
unsafe {
*PLUGIN.get() = None;
}
}
LAST_RESORT_PLUGIN_HANDLE.store(ptr::null_mut(), Ordering::Relaxed);
}))
}
pub(crate) fn with_plugin_state<P: 'static, R>(f: impl FnOnce(&P, PluginHandle<'_, P>) -> R) -> R {
#[cold]
#[inline(never)]
fn panic_on_bad_initial_state(state: usize) -> ! {
assert_ne!(state, LOCKED, "plugin invoked while (un)loading");
assert_ne!(state + 1, LOCKED, "too many references to plugin state");
unreachable!();
}
#[cold]
#[inline(never)]
fn panic_on_concurrent_invoke<T>(state: usize) -> T {
panic!("Plugin invoked concurrently (?), state: {}", state);
}
#[cold]
#[inline(never)]
fn panic_on_uninitialized_plugin<T>() -> T {
panic!("Plugin invoked while uninitialized");
}
#[cold]
#[inline(never)]
fn panic_on_wrong_type<T>() -> T {
panic!("Plugin is an unexpected type");
}
let state = STATE.load(Ordering::Relaxed);
if state == LOCKED || state + 1 == LOCKED {
panic_on_bad_initial_state(state);
}
STATE
.compare_exchange(state, state + 1, Ordering::Relaxed, Ordering::Relaxed)
.unwrap_or_else(panic_on_concurrent_invoke);
defer! {{
STATE
.compare_exchange(state + 1, state, Ordering::Relaxed, Ordering::Relaxed)
.unwrap_or_else(panic_on_concurrent_invoke);
}}
let global_plugin = unsafe {
(*PLUGIN.get())
.as_ref()
.unwrap_or_else(panic_on_uninitialized_plugin)
};
#[cfg(debug_assertions)]
debug_assert_eq!(
global_plugin.thread_id,
std::thread::current().id(),
"plugin invoked from different thread"
);
let plugin = global_plugin
.plugin
.downcast_ref()
.unwrap_or_else(panic_on_wrong_type);
let raw = unsafe { RawPluginHandle::new(global_plugin.plugin_handle) };
let ph = PluginHandle::new(raw);
f(plugin, ph)
}