use std::{
marker::PhantomData,
mem::{self, ManuallyDrop},
ops::Deref,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread::{self, Thread},
};
use closure_ffi::{
thunk_factory,
traits::{FnMutThunk, FnOnceThunk, FnPtr, FnThunk},
};
use diversion_abi::sync::Mutex;
use crate::{
Result,
hook::{Handle, Weak, temp::TemporaryHookExt},
installer::{HookInstaller, MakeInstaller, make::MakeHookInstaller},
};
pub struct Scope<'scope, 'env: 'scope, I = MakeInstaller> {
scoped_hooks: Mutex<ManuallyDrop<ScopedHooks<'scope>>>,
main_thread: Thread,
scope: PhantomData<&'scope mut &'scope ()>,
env: PhantomData<&'env mut &'env ()>,
installer: I,
}
type Ctx<I, T> = <<I as MakeHookInstaller>::Installer<T> as HookInstaller>::Context;
type ScopedHooks<'scope> = Vec<Arc<Box<dyn Send + Sync + 'scope>>>;
#[inline]
pub fn scope_with_installer<'env, T, I>(
f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env, I>) -> T,
installer: I,
) -> T
where
I: MakeHookInstaller,
{
let scope = Scope {
scoped_hooks: Default::default(),
main_thread: thread::current(),
scope: PhantomData,
env: PhantomData,
installer,
};
let _guard = DropGuard::new(&scope, |scope| {
let scoped_hooks = mem::take(&mut **scope.scoped_hooks.lock());
for scoped in scoped_hooks.into_iter().rev() {
let weak = Arc::downgrade(&scoped);
if Arc::into_inner(scoped).is_none() {
while weak.strong_count() != 0 {
thread::park();
}
}
}
});
f(&scope)
}
impl<'scope, 'env, I> Scope<'scope, 'env, I>
where
I: MakeHookInstaller,
{
#[must_use = "the hook will be removed when the handle is dropped"]
pub unsafe fn hook<T, H>(
&'scope self,
target: T,
source: impl FnOnce(Weak<T, Ctx<I, T>>) -> H,
) -> Result<Handle<T, Ctx<I, T>>>
where
T: FnPtr + 'static,
for<'a> (T::CC, &'a H): FnThunk<T>,
H: Send + Sync + 'env,
Ctx<I, T>: Send + Sync + 'static,
{
unsafe { ScopedHook::hook(self, target, source) }
}
#[must_use = "the hook will be removed when the handle is dropped"]
pub unsafe fn hook_mut<T, H>(
&'scope self,
target: T,
source: impl FnOnce(Weak<T, Ctx<I, T>>) -> H,
) -> Result<Handle<T, Ctx<I, T>>>
where
T: FnPtr + 'static,
for<'a> (T::CC, &'a mut H): FnMutThunk<T>,
H: Send + 'env,
Ctx<I, T>: Send + Sync + 'static,
{
unsafe { ScopedHookMut::hook(self, target, source) }
}
#[must_use = "the hook will be removed when the handle is dropped"]
pub unsafe fn hook_once<T, H>(
&'scope self,
target: T,
source: impl FnOnce(Weak<T, Ctx<I, T>>) -> H,
) -> Result<Handle<T, Ctx<I, T>>>
where
T: FnPtr + 'static,
(T::CC, H): FnOnceThunk<T>,
H: Send + 'env,
Ctx<I, T>: Send + Sync + 'static,
{
unsafe { ScopedHookOnce::hook(self, target, source) }
}
}
trait ScopedStrategy<'scope, H, T, I>: Sized + Send + Sync + 'scope
where
T: FnPtr + 'static,
I: MakeHookInstaller,
{
unsafe fn hook<'env>(
scope: &'scope Scope<'scope, 'env, I>,
target: T,
source: impl FnOnce(Weak<T, Ctx<I, T>>) -> H,
) -> Result<Handle<T, Ctx<I, T>>> {
let main_thread = scope.main_thread.clone();
let hook = unsafe {
scope
.installer
.make(target)?
.hook_unchecked_lt(move |hook| {
let hook_fn = Self::new(source(hook.clone()), &hook);
let strong = Arc::new(Box::new(hook_fn) as Box<_>);
let weak = Arc::downgrade(&strong);
scope.scoped_hooks.lock().push(strong);
thunk_factory::make_send_sync(move |args| match weak.upgrade() {
Some(scoped) => {
let scoped = DropGuard::new(scoped, |scoped| {
if Arc::into_inner(scoped).is_some() {
main_thread.unpark();
}
});
let downcast =
<*const (dyn Send + Sync)>::cast::<Self>(&raw const scoped);
(*downcast).call(args)
}
None => hook.upgrade().unwrap().call_original(args),
})
})
};
Ok(hook)
}
fn new(hook: H, ctx: &Weak<T, Ctx<I, T>>) -> Self;
unsafe fn call<'a, 'b, 'c>(&self, args: T::Args<'a, 'b, 'c>) -> T::Ret<'a, 'b, 'c>;
}
struct ScopedHook<'scope, H, T, Ctx>
where
T: FnPtr + 'static,
{
hook: H,
context: PhantomData<Weak<T, Ctx>>,
scope: PhantomData<&'scope mut &'scope ()>,
}
struct ScopedHookMut<'scope, H, T, Ctx>
where
T: FnPtr + 'static,
{
hook: Mutex<H>,
context: PhantomData<Weak<T, Ctx>>,
scope: PhantomData<&'scope mut &'scope ()>,
}
struct ScopedHookOnce<'scope, H, T, Ctx>
where
T: FnPtr + 'static,
{
hook: Mutex<Option<H>>,
flag: AtomicBool,
context: Weak<T, Ctx>,
scope: PhantomData<&'scope mut &'scope ()>,
}
impl<'scope, H, T, I> ScopedStrategy<'scope, H, T, I> for ScopedHook<'scope, H, T, Ctx<I, T>>
where
H: Send + Sync + 'scope,
T: FnPtr + 'static,
for<'a> (T::CC, &'a H): FnThunk<T>,
I: MakeHookInstaller,
{
fn new(hook: H, _ctx: &Weak<T, Ctx<I, T>>) -> Self {
Self {
hook,
scope: PhantomData,
context: PhantomData,
}
}
#[inline]
unsafe fn call<'a, 'b, 'c>(&self, args: T::Args<'a, 'b, 'c>) -> T::Ret<'a, 'b, 'c> {
unsafe { (T::CC::default(), &self.hook).call(args) }
}
}
impl<'scope, H, T, I> ScopedStrategy<'scope, H, T, I> for ScopedHookMut<'scope, H, T, Ctx<I, T>>
where
H: Send + 'scope,
T: FnPtr + 'static,
for<'a> (T::CC, &'a mut H): FnMutThunk<T>,
I: MakeHookInstaller,
{
fn new(hook: H, _ctx: &Weak<T, Ctx<I, T>>) -> Self {
Self {
hook: Mutex::new(hook),
scope: PhantomData,
context: PhantomData,
}
}
#[inline]
unsafe fn call<'a, 'b, 'c>(&self, args: T::Args<'a, 'b, 'c>) -> T::Ret<'a, 'b, 'c> {
unsafe { (T::CC::default(), &mut *self.hook.lock()).call_mut(args) }
}
}
impl<'scope, H, T, I> ScopedStrategy<'scope, H, T, I> for ScopedHookOnce<'scope, H, T, Ctx<I, T>>
where
H: Send + 'scope,
T: FnPtr + 'static,
(T::CC, H): FnOnceThunk<T>,
I: MakeHookInstaller,
{
fn new(hook: H, ctx: &Weak<T, Ctx<I, T>>) -> Self {
Self {
hook: Mutex::new(Some(hook)),
flag: AtomicBool::new(true),
context: ctx.clone(),
scope: PhantomData,
}
}
#[inline]
unsafe fn call<'a, 'b, 'c>(&self, args: T::Args<'a, 'b, 'c>) -> T::Ret<'a, 'b, 'c> {
unsafe {
if self.flag.load(Ordering::Acquire)
&& let Some(hook) = { self.hook.lock().take() }
{
self.flag.store(false, Ordering::Release);
(T::CC::default(), hook).call_once(args)
} else {
self.context.upgrade().unwrap().call_original(args)
}
}
}
}
struct DropGuard<T, F: FnOnce(T)>(ManuallyDrop<(T, F)>);
impl<T, F: FnOnce(T)> DropGuard<T, F> {
const fn new(t: T, f: F) -> Self {
Self(ManuallyDrop::new((t, f)))
}
}
impl<T, F: FnOnce(T)> Deref for DropGuard<T, F> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0.0
}
}
impl<T, F: FnOnce(T)> Drop for DropGuard<T, F> {
fn drop(&mut self) {
let (t, f) = unsafe { ManuallyDrop::take(&mut self.0) };
f(t);
}
}