use crate::ffi::callback::{Callable, Sendable};
use serde::Serialize;
use crate::ffi::value::{Type, Primitive};
use std::sync::atomic::Ordering;
use std::sync::atomic::AtomicU64;
use std::cell::RefCell;
use std::path::PathBuf;
use crate::pathResolver::PathResolver;
use std::cell::UnsafeCell;
use crate::ffi::allocatedMemory::AllocatedMemory;
use crate::ffi::errors::FFIError;
use crate::ffi::library::sendRawRequest;
use crate::ffi::value::Value;
use crate::zygote::FFIRequest;
struct HeavyStack
{
pathResolver: Option<PathResolver>
}
#[doc(hidden)]
pub struct ScopeGuard
{
inner: UnsafeCell<Option<HeavyStack>>
}
impl ScopeGuard
{
#[doc(hidden)]
#[inline(always)]
pub const fn new() -> Self
{
Self {
inner: UnsafeCell::new(None)
}
}
}
thread_local!{
static ScopeStack: RefCell<Vec<*const ScopeGuard>> = const { RefCell::new(Vec::new()) };
}
pub(super) fn resolveViaScope(name: &str) -> Option<String>
{
ScopeStack.with(|s| {
let ptr: *const ScopeGuard = *s.borrow().last()?;
let slot: &Option<HeavyStack> = unsafe{ &*(*ptr).inner.get() };
slot.as_ref()?.pathResolver.as_ref()?.resolve(name)
})
}
pub struct Scope<'g>
{
guard: &'g ScopeGuard,
}
impl<'g> Scope<'g>
{
#[doc(hidden)]
#[inline(always)]
pub fn new(guard: &'g ScopeGuard) -> Self
{
ScopeStack.with(|s| s.borrow_mut().push(guard as *const ScopeGuard));
Self { guard }
}
pub fn addSearchPath(&self, path: impl Into<PathBuf>) -> ()
{
let slot: &mut Option<HeavyStack> = unsafe{ &mut *self.guard.inner.get() };
slot.get_or_insert_with(|| HeavyStack{ pathResolver: None })
.pathResolver.get_or_insert_with(PathResolver::default)
.addPath(path);
}
pub fn alloc(&self, length: usize) -> Result<AllocatedMemory<'g>, FFIError>
{
let stack: &mut Option<HeavyStack> = unsafe{ &mut *self.guard.inner.get() };
if stack.is_none() {
*stack = Some(HeavyStack{
pathResolver: None
});
}
match sendRawRequest(FFIRequest::Alloc { length })? {
Value::Pointer(address) => Ok(AllocatedMemory::new(address, length)),
_ => Err(FFIError::Other("Alloc did not return a pointer".to_string())),
}
}
#[inline]
pub fn free(pointer: impl Into<usize>) -> Result<(), FFIError>
{
sendRawRequest(FFIRequest::Free { pointer: pointer.into() })?;
Ok(())
}
#[inline]
pub fn readMemory(pointer: impl Into<usize>, length: usize) -> Result<Value, FFIError>
{
sendRawRequest(FFIRequest::ReadMemory { pointer: pointer.into(), length })
}
#[inline]
pub fn writeMemory(pointer: impl Into<usize>, value: Value) -> Result<(), FFIError>
{
sendRawRequest(FFIRequest::WriteMemory { pointer: pointer.into(), value })?;
Ok(())
}
pub fn readDynamicStruct(pointer: impl Into<usize>, fields: &[Type]) -> Result<Vec<Value>, FFIError>
{
match sendRawRequest(FFIRequest::ReadDynamicStruct { pointer: pointer.into(), fields: fields.to_vec() })? {
Value::Struct(values) => Ok(values),
other => Err(FFIError::Other(format!("ReadDynamicStruct: expected Value::Struct, got {:?}", other))),
}
}
pub fn writeDynamicStruct(pointer: impl Into<usize>, fields: &[Type], values: &[Value]) -> Result<(), FFIError>
{
sendRawRequest(FFIRequest::WriteDynamicStruct {
pointer: pointer.into(), fields: fields.to_vec(), values: values.to_vec()
})?;
Ok(())
}
pub fn callPointer<T: Primitive>(&self, pointer: impl Into<usize>, args: Vec<Value>) -> Result<T, FFIError>
{
let raw: Value = sendRawRequest(FFIRequest::CallPointer { pointer: pointer.into(), args, resultType: T::TypeTag })?;
T::fromValue(raw)
}
#[inline]
pub fn callvPointer(&self, pointer: impl Into<usize>, args: Vec<Value>) -> Result<(), FFIError>
{
self.callPointer::<()>(pointer, args)
}
pub fn callback<T>(&self, argTypes: Vec<Type>, returnType: Type, f: Sendable<Vec<Value>, Value, T>) -> Value
where
T: Callable<Vec<Value>, Value> + Serialize,
{
static nextID: AtomicU64 = AtomicU64::new(1);
let id: u64 = nextID.fetch_add(1, Ordering::SeqCst);
let bytes: Vec<u8> = f.encode().expect("encode callback");
sendRawRequest(FFIRequest::RegisterCallback {
id,
bytes,
argTypes,
returnType,
}).expect("register callback failed");
Value::Function(id)
}
}
impl<'g> Drop for Scope<'g>
{
fn drop(&mut self) -> () { ScopeStack.with(|s| { s.borrow_mut().pop(); }); }
}
#[macro_export]
macro_rules! callPointer
{
($scope:expr, $pointer:expr $(, $args:expr)* $(,)?) => {
$scope.callPointer($pointer, vec![$($args.into()),*])
};
}
#[macro_export]
macro_rules! callvPointer
{
($scope:expr, $pointer:expr $(, $args:expr)* $(,)?) => {
$scope.callvPointer($pointer, vec![$($args.into()),*])
};
}
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::call;
use crate::callv;
use crate::ffi::errors::FFIError;
use crate::ffi::scope::Scope;
use crate::ffi::value::{Pointer, Value};
#[test]
fn free() -> ()
{
ffi!{
let libc: Library = Library::load("libc.so.6")?;
let ptr: Pointer = call!(libc, "malloc", 16 as usize)?;
Scope::free(ptr)?;
Ok(())
}.expect("Scope::free failed");
}
#[test]
fn readMemory() -> ()
{
let bytes: Vec<u8> = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let ptr: Pointer = call!(libc, "malloc", 8 as usize)?;
callv!(libc, "memset", ptr, 0xAB as i32, 8 as usize)?;
let Value::RawString(readBytes) = Scope::readMemory(ptr, 8)? else {
return Err(FFIError::Other("expected bytes".into()))
};
Scope::free(ptr)?;
Ok(readBytes)
}.expect("Scope::readMemory failed");
assert_eq!(bytes, vec![0xABu8; 8]);
}
#[test]
fn writeMemory() -> ()
{
let len: usize = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let ptr: Pointer = call!(libc, "malloc", 32 as usize)?;
Scope::writeMemory(ptr, Value::CString(b"hello".to_vec()))?;
let result: usize = call!(libc, "strlen", ptr)?;
Scope::free(ptr)?;
Ok(result)
}.expect("Scope::writeMemory failed");
assert!(matches!(len, 5));
}
}