diff options
Diffstat (limited to 'ext/ffi/callback.rs')
-rw-r--r-- | ext/ffi/callback.rs | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs new file mode 100644 index 000000000..9b759a30e --- /dev/null +++ b/ext/ffi/callback.rs @@ -0,0 +1,562 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use crate::check_unstable; +use crate::symbol::NativeType; +use crate::FfiPermissions; +use crate::FfiState; +use crate::ForeignFunction; +use crate::PendingFfiAsyncWork; +use crate::LOCAL_ISOLATE_POINTER; +use crate::MAX_SAFE_INTEGER; +use crate::MIN_SAFE_INTEGER; +use deno_core::error::AnyError; +use deno_core::futures::channel::mpsc; +use deno_core::op; +use deno_core::serde_v8; +use deno_core::v8; +use deno_core::CancelFuture; +use deno_core::CancelHandle; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use libffi::middle::Cif; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::ffi::c_void; +use std::future::Future; +use std::future::IntoFuture; +use std::pin::Pin; +use std::ptr; +use std::ptr::NonNull; +use std::rc::Rc; +use std::sync::mpsc::sync_channel; +use std::task::Poll; +use std::task::Waker; +#[derive(Clone)] +pub struct PtrSymbol { + pub cif: libffi::middle::Cif, + pub ptr: libffi::middle::CodePtr, +} + +impl PtrSymbol { + pub fn new(fn_ptr: usize, def: &ForeignFunction) -> Self { + let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _); + let cif = libffi::middle::Cif::new( + def + .parameters + .clone() + .into_iter() + .map(libffi::middle::Type::from), + def.result.into(), + ); + + Self { cif, ptr } + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +// SAFETY: unsafe trait must have unsafe implementation +unsafe impl Send for PtrSymbol {} +// SAFETY: unsafe trait must have unsafe implementation +unsafe impl Sync for PtrSymbol {} + +struct UnsafeCallbackResource { + cancel: Rc<CancelHandle>, + // Closure is never directly touched, but it keeps the C callback alive + // until `close()` method is called. + #[allow(dead_code)] + closure: libffi::middle::Closure<'static>, + info: *mut CallbackInfo, +} + +impl Resource for UnsafeCallbackResource { + fn name(&self) -> Cow<str> { + "unsafecallback".into() + } + + fn close(self: Rc<Self>) { + self.cancel.cancel(); + // SAFETY: This drops the closure and the callback info associated with it. + // Any retained function pointers to the closure become dangling pointers. + // It is up to the user to know that it is safe to call the `close()` on the + // UnsafeCallback instance. + unsafe { + let info = Box::from_raw(self.info); + let isolate = info.isolate.as_mut().unwrap(); + let _ = v8::Global::from_raw(isolate, info.callback); + let _ = v8::Global::from_raw(isolate, info.context); + } + } +} + +struct CallbackInfo { + pub parameters: Vec<NativeType>, + pub result: NativeType, + pub async_work_sender: mpsc::UnboundedSender<PendingFfiAsyncWork>, + pub callback: NonNull<v8::Function>, + pub context: NonNull<v8::Context>, + pub isolate: *mut v8::Isolate, + pub waker: Option<Waker>, +} + +impl Future for CallbackInfo { + type Output = (); + fn poll( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll<Self::Output> { + // Always replace the waker to make sure it's bound to the proper Future. + self.waker.replace(cx.waker().clone()); + // The future for the CallbackInfo never resolves: It can only be canceled. + Poll::Pending + } +} +unsafe extern "C" fn deno_ffi_callback( + _cif: &libffi::low::ffi_cif, + result: &mut c_void, + args: *const *const c_void, + info: &CallbackInfo, +) { + LOCAL_ISOLATE_POINTER.with(|s| { + if ptr::eq(*s.borrow(), info.isolate) { + // Own isolate thread, okay to call directly + do_ffi_callback(info, result, args); + } else { + let async_work_sender = &info.async_work_sender; + // SAFETY: Safe as this function blocks until `do_ffi_callback` completes and a response message is received. + let result: &'static mut c_void = std::mem::transmute(result); + let info: &'static CallbackInfo = std::mem::transmute(info); + let (response_sender, response_receiver) = sync_channel::<()>(0); + let fut = Box::new(move || { + do_ffi_callback(info, result, args); + response_sender.send(()).unwrap(); + }); + async_work_sender.unbounded_send(fut).unwrap(); + if let Some(waker) = info.waker.as_ref() { + // Make sure event loop wakes up to receive our message before we start waiting for a response. + waker.wake_by_ref(); + } + response_receiver.recv().unwrap(); + } + }); +} + +unsafe fn do_ffi_callback( + info: &CallbackInfo, + result: &mut c_void, + args: *const *const c_void, +) { + let callback: NonNull<v8::Function> = info.callback; + let context: NonNull<v8::Context> = info.context; + let isolate: *mut v8::Isolate = info.isolate; + let isolate = &mut *isolate; + let callback = v8::Global::from_raw(isolate, callback); + let context = std::mem::transmute::< + NonNull<v8::Context>, + v8::Local<v8::Context>, + >(context); + // Call from main thread. If this callback is being triggered due to a + // function call coming from Deno itself, then this callback will build + // ontop of that stack. + // If this callback is being triggered outside of Deno (for example from a + // signal handler) then this will either create an empty new stack if + // Deno currently has nothing running and is waiting for promises to resolve, + // or will (very incorrectly) build ontop of whatever stack exists. + // The callback will even be called through from a `while (true)` liveloop, but + // it somehow cannot change the values that the loop sees, even if they both + // refer the same `let bool_value`. + let mut cb_scope = v8::CallbackScope::new(context); + let scope = &mut v8::HandleScope::new(&mut cb_scope); + let func = callback.open(scope); + let result = result as *mut c_void; + let vals: &[*const c_void] = + std::slice::from_raw_parts(args, info.parameters.len() as usize); + + let mut params: Vec<v8::Local<v8::Value>> = vec![]; + for (native_type, val) in info.parameters.iter().zip(vals) { + let value: v8::Local<v8::Value> = match native_type { + NativeType::Bool => { + let value = *((*val) as *const bool); + v8::Boolean::new(scope, value).into() + } + NativeType::F32 => { + let value = *((*val) as *const f32); + v8::Number::new(scope, value as f64).into() + } + NativeType::F64 => { + let value = *((*val) as *const f64); + v8::Number::new(scope, value).into() + } + NativeType::I8 => { + let value = *((*val) as *const i8); + v8::Integer::new(scope, value as i32).into() + } + NativeType::U8 => { + let value = *((*val) as *const u8); + v8::Integer::new_from_unsigned(scope, value as u32).into() + } + NativeType::I16 => { + let value = *((*val) as *const i16); + v8::Integer::new(scope, value as i32).into() + } + NativeType::U16 => { + let value = *((*val) as *const u16); + v8::Integer::new_from_unsigned(scope, value as u32).into() + } + NativeType::I32 => { + let value = *((*val) as *const i32); + v8::Integer::new(scope, value).into() + } + NativeType::U32 => { + let value = *((*val) as *const u32); + v8::Integer::new_from_unsigned(scope, value).into() + } + NativeType::I64 | NativeType::ISize => { + let result = *((*val) as *const i64); + if result > MAX_SAFE_INTEGER as i64 || result < MIN_SAFE_INTEGER as i64 + { + v8::BigInt::new_from_i64(scope, result).into() + } else { + v8::Number::new(scope, result as f64).into() + } + } + NativeType::U64 | NativeType::USize => { + let result = *((*val) as *const u64); + if result > MAX_SAFE_INTEGER as u64 { + v8::BigInt::new_from_u64(scope, result).into() + } else { + v8::Number::new(scope, result as f64).into() + } + } + NativeType::Pointer | NativeType::Buffer | NativeType::Function => { + let result = *((*val) as *const usize); + if result > MAX_SAFE_INTEGER as usize { + v8::BigInt::new_from_u64(scope, result as u64).into() + } else { + v8::Number::new(scope, result as f64).into() + } + } + NativeType::Void => unreachable!(), + }; + params.push(value); + } + + let recv = v8::undefined(scope); + let call_result = func.call(scope, recv.into(), ¶ms); + std::mem::forget(callback); + + if call_result.is_none() { + // JS function threw an exception. Set the return value to zero and return. + // The exception continue propagating up the call chain when the event loop + // resumes. + match info.result { + NativeType::Bool => { + *(result as *mut bool) = false; + } + NativeType::U32 | NativeType::I32 => { + // zero is equal for signed and unsigned alike + *(result as *mut u32) = 0; + } + NativeType::F32 => { + *(result as *mut f32) = 0.0; + } + NativeType::F64 => { + *(result as *mut f64) = 0.0; + } + NativeType::U8 | NativeType::I8 => { + // zero is equal for signed and unsigned alike + *(result as *mut u8) = 0; + } + NativeType::U16 | NativeType::I16 => { + // zero is equal for signed and unsigned alike + *(result as *mut u16) = 0; + } + NativeType::Pointer + | NativeType::Buffer + | NativeType::Function + | NativeType::U64 + | NativeType::I64 => { + *(result as *mut usize) = 0; + } + NativeType::Void => { + // nop + } + _ => { + unreachable!(); + } + }; + + return; + } + let value = call_result.unwrap(); + + match info.result { + NativeType::Bool => { + let value = if let Ok(value) = v8::Local::<v8::Boolean>::try_from(value) { + value.is_true() + } else { + value.boolean_value(scope) + }; + *(result as *mut bool) = value; + } + NativeType::I32 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as i32 + } else { + // Fallthrough, probably UB. + value + .int32_value(scope) + .expect("Unable to deserialize result parameter.") as i32 + }; + *(result as *mut i32) = value; + } + NativeType::F32 => { + let value = if let Ok(value) = v8::Local::<v8::Number>::try_from(value) { + value.value() as f32 + } else { + // Fallthrough, probably UB. + value + .number_value(scope) + .expect("Unable to deserialize result parameter.") as f32 + }; + *(result as *mut f32) = value; + } + NativeType::F64 => { + let value = if let Ok(value) = v8::Local::<v8::Number>::try_from(value) { + value.value() + } else { + // Fallthrough, probably UB. + value + .number_value(scope) + .expect("Unable to deserialize result parameter.") + }; + *(result as *mut f64) = value; + } + NativeType::Pointer | NativeType::Buffer | NativeType::Function => { + let pointer = if let Ok(value) = + v8::Local::<v8::ArrayBufferView>::try_from(value) + { + let byte_offset = value.byte_offset(); + let backing_store = value + .buffer(scope) + .expect("Unable to deserialize result parameter.") + .get_backing_store(); + &backing_store[byte_offset..] as *const _ as *const u8 + } else if let Ok(value) = v8::Local::<v8::BigInt>::try_from(value) { + value.u64_value().0 as usize as *const u8 + } else if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(value) { + let backing_store = value.get_backing_store(); + &backing_store[..] as *const _ as *const u8 + } else if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as usize as *const u8 + } else if value.is_null() { + ptr::null() + } else { + // Fallthrough: Probably someone returned a number but this could + // also be eg. a string. This is essentially UB. + value + .integer_value(scope) + .expect("Unable to deserialize result parameter.") as usize + as *const u8 + }; + *(result as *mut *const u8) = pointer; + } + NativeType::I8 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as i8 + } else { + // Fallthrough, essentially UB. + value + .int32_value(scope) + .expect("Unable to deserialize result parameter.") as i8 + }; + *(result as *mut i8) = value; + } + NativeType::U8 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as u8 + } else { + // Fallthrough, essentially UB. + value + .uint32_value(scope) + .expect("Unable to deserialize result parameter.") as u8 + }; + *(result as *mut u8) = value; + } + NativeType::I16 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as i16 + } else { + // Fallthrough, essentially UB. + value + .int32_value(scope) + .expect("Unable to deserialize result parameter.") as i16 + }; + *(result as *mut i16) = value; + } + NativeType::U16 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as u16 + } else { + // Fallthrough, essentially UB. + value + .uint32_value(scope) + .expect("Unable to deserialize result parameter.") as u16 + }; + *(result as *mut u16) = value; + } + NativeType::U32 => { + let value = if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + value.value() as u32 + } else { + // Fallthrough, essentially UB. + value + .uint32_value(scope) + .expect("Unable to deserialize result parameter.") + }; + *(result as *mut u32) = value; + } + NativeType::I64 => { + if let Ok(value) = v8::Local::<v8::BigInt>::try_from(value) { + *(result as *mut i64) = value.i64_value().0; + } else if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + *(result as *mut i64) = value.value(); + } else { + *(result as *mut i64) = value + .integer_value(scope) + .expect("Unable to deserialize result parameter.") + as i64; + } + } + NativeType::U64 => { + if let Ok(value) = v8::Local::<v8::BigInt>::try_from(value) { + *(result as *mut u64) = value.u64_value().0; + } else if let Ok(value) = v8::Local::<v8::Integer>::try_from(value) { + *(result as *mut u64) = value.value() as u64; + } else { + *(result as *mut u64) = value + .integer_value(scope) + .expect("Unable to deserialize result parameter.") + as u64; + } + } + NativeType::Void => { + // nop + } + _ => { + unreachable!(); + } + }; +} + +#[op] +pub fn op_ffi_unsafe_callback_ref( + state: Rc<RefCell<OpState>>, + rid: ResourceId, +) -> Result<impl Future<Output = Result<(), AnyError>>, AnyError> { + let state = state.borrow(); + let callback_resource = + state.resource_table.get::<UnsafeCallbackResource>(rid)?; + + Ok(async move { + let info: &mut CallbackInfo = + // SAFETY: CallbackInfo pointer stays valid as long as the resource is still alive. + unsafe { callback_resource.info.as_mut().unwrap() }; + // Ignore cancellation rejection + let _ = info + .into_future() + .or_cancel(callback_resource.cancel.clone()) + .await; + Ok(()) + }) +} + +#[op(fast)] +pub fn op_ffi_unsafe_callback_unref( + state: &mut deno_core::OpState, + rid: u32, +) -> Result<(), AnyError> { + state + .resource_table + .get::<UnsafeCallbackResource>(rid)? + .cancel + .cancel(); + Ok(()) +} + +#[derive(Deserialize)] +pub struct RegisterCallbackArgs { + parameters: Vec<NativeType>, + result: NativeType, +} + +#[op(v8)] +pub fn op_ffi_unsafe_callback_create<FP, 'scope>( + state: &mut deno_core::OpState, + scope: &mut v8::HandleScope<'scope>, + args: RegisterCallbackArgs, + cb: serde_v8::Value<'scope>, +) -> Result<serde_v8::Value<'scope>, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafeCallback"); + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + let v8_value = cb.v8_value; + let cb = v8::Local::<v8::Function>::try_from(v8_value)?; + + let isolate: *mut v8::Isolate = &mut *scope as &mut v8::Isolate; + LOCAL_ISOLATE_POINTER.with(|s| { + if s.borrow().is_null() { + s.replace(isolate); + } + }); + + let async_work_sender = + state.borrow_mut::<FfiState>().async_work_sender.clone(); + let callback = v8::Global::new(scope, cb).into_raw(); + let current_context = scope.get_current_context(); + let context = v8::Global::new(scope, current_context).into_raw(); + + let info: *mut CallbackInfo = Box::leak(Box::new(CallbackInfo { + parameters: args.parameters.clone(), + result: args.result, + async_work_sender, + callback, + context, + isolate, + waker: None, + })); + let cif = Cif::new( + args.parameters.into_iter().map(libffi::middle::Type::from), + libffi::middle::Type::from(args.result), + ); + + // SAFETY: CallbackInfo is leaked, is not null and stays valid as long as the callback exists. + let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, unsafe { + info.as_ref().unwrap() + }); + let ptr = *closure.code_ptr() as usize; + let resource = UnsafeCallbackResource { + cancel: CancelHandle::new_rc(), + closure, + info, + }; + let rid = state.resource_table.add(resource); + + let rid_local = v8::Integer::new_from_unsigned(scope, rid); + let ptr_local: v8::Local<v8::Value> = if ptr > MAX_SAFE_INTEGER as usize { + v8::BigInt::new_from_u64(scope, ptr as u64).into() + } else { + v8::Number::new(scope, ptr as f64).into() + }; + let array = v8::Array::new(scope, 2); + array.set_index(scope, 0, rid_local.into()); + array.set_index(scope, 1, ptr_local); + let array_value: v8::Local<v8::Value> = array.into(); + + Ok(array_value.into()) +} |