#![feature(unboxed_closures)]
#![feature(fn_traits)]
#![feature(const_fn)]
#![feature(const_fn_fn_ptr_basics)]
#![feature(unsized_fn_params)]
#![feature(unsize)]
#![feature(coerce_unsized)]
use std::marker::PhantomData;
use simple_error::bail;
use std::sync::RwLock;
pub use hotpatch_macros::*;
#[doc(hidden)]
pub use once_cell::sync::Lazy;
use variadic_generics::*;
mod export;
pub use export::*;
mod docs;
pub use docs::*;
use std::mem::{transmute, transmute_copy};
type FnVoid = dyn Fn() -> () + Send + Sync + 'static;
macro_rules! va_largesig {
($va_len:tt, $va_idents:tt, $va_indices:tt, $($tt:tt)+) => {
#[cfg(not(feature = "large-signatures"))]
va_expand_with_nil! { $va_len $va_idents $va_indices $($tt)* }
#[cfg(feature = "large-signatures")]
va_expand_more_with_nil! { $va_len $va_idents $va_indices $($tt)* }
}
}
pub struct Patchable<RealType: ?Sized + Send + Sync + 'static> {
lazy: Lazy<Option<RwLock<HotpatchImportInternal<RealType>>>>,
}
#[doc(hidden)]
pub struct HotpatchImportInternal<RealType: ?Sized + Send + Sync + 'static> {
current_ptr: Box<FnVoid>,
default_ptr: Box<FnVoid>,
phantom: PhantomData<RealType>,
sig: &'static str,
lib: Option<libloading::Library>,
mpath: &'static str,
}
impl<RealType: ?Sized + Send + Sync + 'static> HotpatchImportInternal<RealType> {
fn new<T>(ptr: T, mpath: &'static str, sig: &'static str) -> Self {
let r = &ptr;
unsafe {
Self {
current_ptr: transmute_copy(r),
default_ptr: transmute_copy(r),
phantom: PhantomData,
lib: None,
sig,
mpath: mpath.trim_start_matches(|c| c != ':'),
}
}
}
fn clean(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if self.lib.is_some() {
self.lib.take().unwrap().close()?;
}
Ok(())
}
fn restore_default(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.current_ptr = unsafe { transmute_copy(&self.default_ptr) };
self.clean()
}
fn upcast_self(&self) -> &Box<RealType> {
let p: &Box<FnVoid> = &self.current_ptr;
let casted: &Box<RealType> = unsafe { transmute(p) };
casted
}
}
impl<RealType: ?Sized + Send + Sync + 'static> Patchable<RealType> {
#[doc(hidden)]
pub const fn __new(ptr: fn() -> Option<RwLock<HotpatchImportInternal<RealType>>>) -> Self {
Self {
lazy: Lazy::new(ptr),
}
}
#[doc(hidden)]
pub fn __new_internal<T>(
ptr: T,
mpath: &'static str,
sig: &'static str,
) -> Option<RwLock<HotpatchImportInternal<RealType>>> {
Some(RwLock::new(HotpatchImportInternal::new(ptr, mpath, sig)))
}
pub fn restore_default(&self) -> Result<(), Box<dyn std::error::Error + '_>> {
self.lazy.as_ref().unwrap().write()?.restore_default()
}
pub fn try_restore_default(&self) -> Result<(), Box<dyn std::error::Error + '_>> {
self.lazy.as_ref().unwrap().try_write()?.restore_default()
}
pub unsafe fn force_restore_default(&self) -> Result<(), Box<dyn std::error::Error + '_>> {
let sref = self as *const Self as *mut Self;
let mut rref = (*sref).lazy.take().unwrap();
let reslt = rref.get_mut().unwrap().restore_default();
*(*sref).lazy = Some(rref);
reslt
}
}
trait HotpatchFnInternal<T, Dummy> {
unsafe fn hotpatch_fn(&mut self, c: T) -> Result<(), Box<dyn std::error::Error>>;
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + 'static, T, Ret, $($va_idents,)*> HotpatchFnInternal<T, (Ret, $($va_idents,)*)>
for HotpatchImportInternal<RealType>
where
T: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
unsafe fn hotpatch_fn(&mut self, c: T) -> Result<(), Box<dyn std::error::Error>> {
let boxed: Box<T> = Box::new(c);
let reboxed: Box<dyn Fn($($va_idents,)*) -> Ret> = boxed;
let dbox: Box<FnVoid> = std::mem::transmute(reboxed);
self.current_ptr = dbox;
self.clean()
}
}
}
trait HotpatchLibInternal<Dummy> {
fn hotpatch_lib(&mut self, lib_name: &str) -> Result<(), Box<dyn std::error::Error>>;
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + Send + Sync + 'static, Ret: 'static, $($va_idents: 'static,)*> HotpatchLibInternal<(Ret, $($va_idents,)*)>
for HotpatchImportInternal<RealType>
where
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
fn hotpatch_lib(&mut self, lib_name: &str) -> Result<(), Box<dyn std::error::Error>> {
unsafe {
let lib = libloading::Library::new(lib_name)?;
let mut i: usize = 0;
loop {
let symbol_name = format!("{}{}", "__HOTPATCH_EXPORT_", i);
let exports: libloading::Symbol<*mut HotpatchExport<fn($($va_idents,)*) -> Ret>> =
lib.get(symbol_name.as_bytes()).map_err(|_| {
format!(
"Hotpatch for {} failed: symbol not found in library {}",
self.mpath, lib_name
)
})?;
let export_obj = &**exports;
if export_obj.symbol.trim_start_matches(|c| c != ':') == self.mpath {
if self.sig != export_obj.sig {
bail!("Hotpatch for {} failed: symbol found but of wrong type. Expected {} but found {}", self.mpath, self.sig, export_obj.sig);
}
let d: Box<fn($($va_idents,)*) -> Ret> = Box::new(export_obj.ptr);
let t: Box<dyn Fn($($va_idents,)*) -> Ret + Send + Sync + 'static> = d;
self.current_ptr = transmute(t);
self.clean()?;
self.lib = Some(lib);
break;
}
i += 1;
}
}
Ok(())
}
}
}
pub trait HotpatchLib<Dummy> {
fn hotpatch_lib(&self, lib_name: &str) -> Result<(), Box<dyn std::error::Error + '_>>;
fn try_hotpatch_lib(&self, lib_name: &str) -> Result<(), Box<dyn std::error::Error + '_>>;
unsafe fn force_hotpatch_lib(
&self,
lib_name: &str,
) -> Result<(), Box<dyn std::error::Error + '_>>;
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + Send + Sync + 'static, Ret: 'static, $($va_idents: 'static,)*> HotpatchLib<(Ret, $($va_idents,)*)>
for Patchable<RealType>
where
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
fn hotpatch_lib(&self, lib_name: &str) -> Result<(), Box<dyn std::error::Error + '_>> {
self.lazy.as_ref().unwrap().write()?.hotpatch_lib(lib_name)
}
fn try_hotpatch_lib(&self, lib_name: &str) -> Result<(), Box<dyn std::error::Error + '_>> {
self.lazy
.as_ref()
.unwrap()
.try_write()?
.hotpatch_lib(lib_name)
}
unsafe fn force_hotpatch_lib(
&self,
lib_name: &str,
) -> Result<(), Box<dyn std::error::Error + '_>> {
let sref = self as *const Self as *mut Self;
let mut rref = (*sref).lazy.take().unwrap();
let reslt = rref.get_mut().unwrap().hotpatch_lib(lib_name);
*(*sref).lazy = Some(rref);
reslt
}
}
}
pub trait HotpatchFn<T, Dummy> {
fn hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>>;
fn try_hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>>;
unsafe fn force_hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>>;
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + Send + Sync + 'static, T, Ret, $($va_idents,)*> HotpatchFn<T, (Ret, $($va_idents,)*)>
for Patchable<RealType>
where
T: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
fn hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>> {
unsafe { self.lazy.as_ref().unwrap().write()?.hotpatch_fn(c) }
}
fn try_hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>> {
unsafe { self.lazy.as_ref().unwrap().try_write()?.hotpatch_fn(c) }
}
unsafe fn force_hotpatch_fn(&self, c: T) -> Result<(), Box<dyn std::error::Error + '_>> {
let sref = self as *const Self as *mut Self;
let mut rref = (*sref).lazy.take().unwrap();
let reslt = rref.get_mut().unwrap().hotpatch_fn(c);
*(*sref).lazy = Some(rref);
reslt
}
}
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + 'static, Ret, $($va_idents,)*> FnOnce<($($va_idents,)*)> for Patchable<RealType>
where
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
type Output = Ret;
extern "rust-call" fn call_once(self, args: ($($va_idents,)*)) -> Ret {
let inner = self.lazy.as_ref().unwrap().read().unwrap();
inner.upcast_self().call(args)
}
}
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + 'static, Ret, $($va_idents,)*> FnMut<($($va_idents,)*)> for Patchable<RealType>
where
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
extern "rust-call" fn call_mut(&mut self, args: ($($va_idents,)*)) -> Ret {
let inner = self.lazy.as_ref().unwrap().read().unwrap();
inner.upcast_self().call(args)
}
}
}
#[cfg(not(doc))]
va_largesig! { ($va_len:tt), ($($va_idents:ident),*), ($($va_indices:tt),*),
impl<RealType: ?Sized + 'static, Ret, $($va_idents,)*> Fn<($($va_idents,)*)> for Patchable<RealType>
where
RealType: Fn($($va_idents,)*) -> Ret + Send + Sync + 'static,
{
extern "rust-call" fn call(&self, args: ($($va_idents,)*)) -> Ret {
let inner = self.lazy.as_ref().unwrap().read().unwrap();
inner.upcast_self().call(args)
}
}
}
pub struct MutConst<T: 'static> {
f: fn() -> &'static T,
}
impl<T> MutConst<T> {
pub const fn new(f: fn() -> &'static T) -> Self {
Self { f }
}
}
impl<T> std::ops::Deref for MutConst<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
(self.f)()
}
}