summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2022-12-12 06:14:20 -0800
committerGitHub <noreply@github.com>2022-12-12 14:14:20 +0000
commita2db70a8d0820722695e9094c8dbc888bde1ffa3 (patch)
tree6885e2aee424ab0264a8b01df9b0bafbcfe85792 /ext
parent890b0653104388bfef60ca7ebd0af758caf5f0be (diff)
refactor(ext/ffi): split into multiple parts (#16950)
- [x] `dlfcn.rs` - `dlopen()`-related code. - [x] `turbocall.rs` - Call trampoline JIT compiler. - [x] `repr.rs` - Pointer representation. Home of the UnsafePointerView ops. - [x] `symbol.rs` - Function symbol related code. - [x] `callback.rs` - Home of `Deno.UnsafeCallback` ops. - [x] `ir.rs` - Intermediate representation for values. Home of the `NativeValue` type. - [x] `call.rs` - Generic call ops. Home to everything related to calling FFI symbols. - [x] `static.rs` - static symbol support I find easier to work with this setup, I eventually want to expand TurboCall to unroll type conversion loop in generic calls, generate code for individual symbols (lazy function pointers), etc.
Diffstat (limited to 'ext')
-rw-r--r--ext/ffi/call.rs335
-rw-r--r--ext/ffi/callback.rs562
-rw-r--r--ext/ffi/dlfcn.rs398
-rw-r--r--ext/ffi/ir.rs527
-rw-r--r--ext/ffi/lib.rs2429
-rw-r--r--ext/ffi/repr.rs454
-rw-r--r--ext/ffi/static.rs146
-rw-r--r--ext/ffi/symbol.rs63
-rw-r--r--ext/ffi/turbocall.rs (renamed from ext/ffi/fast_call.rs)8
9 files changed, 2517 insertions, 2405 deletions
diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs
new file mode 100644
index 000000000..4a913c0e0
--- /dev/null
+++ b/ext/ffi/call.rs
@@ -0,0 +1,335 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::callback::PtrSymbol;
+use crate::check_unstable2;
+use crate::dlfcn::DynamicLibraryResource;
+use crate::ir::*;
+use crate::symbol::NativeType;
+use crate::symbol::Symbol;
+use crate::FfiPermissions;
+use crate::ForeignFunction;
+use deno_core::anyhow::anyhow;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::serde_json::Value;
+use deno_core::serde_v8;
+use deno_core::v8;
+use deno_core::ResourceId;
+use libffi::middle::Arg;
+use std::cell::RefCell;
+use std::ffi::c_void;
+use std::future::Future;
+use std::rc::Rc;
+
+// A one-off synchronous FFI call.
+pub(crate) fn ffi_call_sync<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ args: v8::FunctionCallbackArguments,
+ symbol: &Symbol,
+) -> Result<NativeValue, AnyError>
+where
+ 'scope: 'scope,
+{
+ let Symbol {
+ parameter_types,
+ result_type,
+ cif,
+ ptr: fun_ptr,
+ ..
+ } = symbol;
+ let mut ffi_args: Vec<NativeValue> =
+ Vec::with_capacity(parameter_types.len());
+
+ for (index, native_type) in parameter_types.iter().enumerate() {
+ let value = args.get(index as i32);
+ match native_type {
+ NativeType::Bool => {
+ ffi_args.push(ffi_parse_bool_arg(value)?);
+ }
+ NativeType::U8 => {
+ ffi_args.push(ffi_parse_u8_arg(value)?);
+ }
+ NativeType::I8 => {
+ ffi_args.push(ffi_parse_i8_arg(value)?);
+ }
+ NativeType::U16 => {
+ ffi_args.push(ffi_parse_u16_arg(value)?);
+ }
+ NativeType::I16 => {
+ ffi_args.push(ffi_parse_i16_arg(value)?);
+ }
+ NativeType::U32 => {
+ ffi_args.push(ffi_parse_u32_arg(value)?);
+ }
+ NativeType::I32 => {
+ ffi_args.push(ffi_parse_i32_arg(value)?);
+ }
+ NativeType::U64 => {
+ ffi_args.push(ffi_parse_u64_arg(scope, value)?);
+ }
+ NativeType::I64 => {
+ ffi_args.push(ffi_parse_i64_arg(scope, value)?);
+ }
+ NativeType::USize => {
+ ffi_args.push(ffi_parse_usize_arg(scope, value)?);
+ }
+ NativeType::ISize => {
+ ffi_args.push(ffi_parse_isize_arg(scope, value)?);
+ }
+ NativeType::F32 => {
+ ffi_args.push(ffi_parse_f32_arg(value)?);
+ }
+ NativeType::F64 => {
+ ffi_args.push(ffi_parse_f64_arg(value)?);
+ }
+ NativeType::Buffer => {
+ ffi_args.push(ffi_parse_buffer_arg(scope, value)?);
+ }
+ NativeType::Pointer => {
+ ffi_args.push(ffi_parse_pointer_arg(scope, value)?);
+ }
+ NativeType::Function => {
+ ffi_args.push(ffi_parse_function_arg(scope, value)?);
+ }
+ NativeType::Void => {
+ unreachable!();
+ }
+ }
+ }
+ let call_args: Vec<Arg> = ffi_args.iter().map(Arg::new).collect();
+ // SAFETY: types in the `Cif` match the actual calling convention and
+ // types of symbol.
+ unsafe {
+ Ok(match result_type {
+ NativeType::Void => NativeValue {
+ void_value: cif.call::<()>(*fun_ptr, &call_args),
+ },
+ NativeType::Bool => NativeValue {
+ bool_value: cif.call::<bool>(*fun_ptr, &call_args),
+ },
+ NativeType::U8 => NativeValue {
+ u8_value: cif.call::<u8>(*fun_ptr, &call_args),
+ },
+ NativeType::I8 => NativeValue {
+ i8_value: cif.call::<i8>(*fun_ptr, &call_args),
+ },
+ NativeType::U16 => NativeValue {
+ u16_value: cif.call::<u16>(*fun_ptr, &call_args),
+ },
+ NativeType::I16 => NativeValue {
+ i16_value: cif.call::<i16>(*fun_ptr, &call_args),
+ },
+ NativeType::U32 => NativeValue {
+ u32_value: cif.call::<u32>(*fun_ptr, &call_args),
+ },
+ NativeType::I32 => NativeValue {
+ i32_value: cif.call::<i32>(*fun_ptr, &call_args),
+ },
+ NativeType::U64 => NativeValue {
+ u64_value: cif.call::<u64>(*fun_ptr, &call_args),
+ },
+ NativeType::I64 => NativeValue {
+ i64_value: cif.call::<i64>(*fun_ptr, &call_args),
+ },
+ NativeType::USize => NativeValue {
+ usize_value: cif.call::<usize>(*fun_ptr, &call_args),
+ },
+ NativeType::ISize => NativeValue {
+ isize_value: cif.call::<isize>(*fun_ptr, &call_args),
+ },
+ NativeType::F32 => NativeValue {
+ f32_value: cif.call::<f32>(*fun_ptr, &call_args),
+ },
+ NativeType::F64 => NativeValue {
+ f64_value: cif.call::<f64>(*fun_ptr, &call_args),
+ },
+ NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
+ NativeValue {
+ pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args),
+ }
+ }
+ })
+ }
+}
+
+fn ffi_call(
+ call_args: Vec<NativeValue>,
+ cif: &libffi::middle::Cif,
+ fun_ptr: libffi::middle::CodePtr,
+ parameter_types: &[NativeType],
+ result_type: NativeType,
+) -> Result<NativeValue, AnyError> {
+ let call_args: Vec<Arg> = call_args
+ .iter()
+ .enumerate()
+ .map(|(index, ffi_arg)| {
+ // SAFETY: the union field is initialized
+ unsafe { ffi_arg.as_arg(*parameter_types.get(index).unwrap()) }
+ })
+ .collect();
+
+ // SAFETY: types in the `Cif` match the actual calling convention and
+ // types of symbol.
+ unsafe {
+ Ok(match result_type {
+ NativeType::Void => NativeValue {
+ void_value: cif.call::<()>(fun_ptr, &call_args),
+ },
+ NativeType::Bool => NativeValue {
+ bool_value: cif.call::<bool>(fun_ptr, &call_args),
+ },
+ NativeType::U8 => NativeValue {
+ u8_value: cif.call::<u8>(fun_ptr, &call_args),
+ },
+ NativeType::I8 => NativeValue {
+ i8_value: cif.call::<i8>(fun_ptr, &call_args),
+ },
+ NativeType::U16 => NativeValue {
+ u16_value: cif.call::<u16>(fun_ptr, &call_args),
+ },
+ NativeType::I16 => NativeValue {
+ i16_value: cif.call::<i16>(fun_ptr, &call_args),
+ },
+ NativeType::U32 => NativeValue {
+ u32_value: cif.call::<u32>(fun_ptr, &call_args),
+ },
+ NativeType::I32 => NativeValue {
+ i32_value: cif.call::<i32>(fun_ptr, &call_args),
+ },
+ NativeType::U64 => NativeValue {
+ u64_value: cif.call::<u64>(fun_ptr, &call_args),
+ },
+ NativeType::I64 => NativeValue {
+ i64_value: cif.call::<i64>(fun_ptr, &call_args),
+ },
+ NativeType::USize => NativeValue {
+ usize_value: cif.call::<usize>(fun_ptr, &call_args),
+ },
+ NativeType::ISize => NativeValue {
+ isize_value: cif.call::<isize>(fun_ptr, &call_args),
+ },
+ NativeType::F32 => NativeValue {
+ f32_value: cif.call::<f32>(fun_ptr, &call_args),
+ },
+ NativeType::F64 => NativeValue {
+ f64_value: cif.call::<f64>(fun_ptr, &call_args),
+ },
+ NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
+ NativeValue {
+ pointer: cif.call::<*mut c_void>(fun_ptr, &call_args),
+ }
+ }
+ })
+ }
+}
+
+#[op(v8)]
+pub fn op_ffi_call_ptr_nonblocking<'scope, FP>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: usize,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, AnyError>>, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable2(&state, "Deno.UnsafeFnPointer#call");
+ {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+ };
+
+ let symbol = PtrSymbol::new(pointer, &def);
+ let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
+
+ let join_handle = tokio::task::spawn_blocking(move || {
+ let PtrSymbol { cif, ptr } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &def.parameters, def.result)
+ });
+
+ Ok(async move {
+ let result = join_handle
+ .await
+ .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ Ok(unsafe { result.to_value(def.result) })
+ })
+}
+
+/// A non-blocking FFI call.
+#[op(v8)]
+pub fn op_ffi_call_nonblocking<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ rid: ResourceId,
+ symbol: String,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, AnyError>> + 'static, AnyError> {
+ let symbol = {
+ let state = state.borrow();
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
+ let symbols = &resource.symbols;
+ *symbols
+ .get(&symbol)
+ .ok_or_else(|| type_error("Invalid FFI symbol name"))?
+ .clone()
+ };
+
+ let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
+
+ let result_type = symbol.result_type;
+ let join_handle = tokio::task::spawn_blocking(move || {
+ let Symbol {
+ cif,
+ ptr,
+ parameter_types,
+ result_type,
+ ..
+ } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &parameter_types, result_type)
+ });
+
+ Ok(async move {
+ let result = join_handle
+ .await
+ .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ Ok(unsafe { result.to_value(result_type) })
+ })
+}
+
+#[op(v8)]
+pub fn op_ffi_call_ptr<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: usize,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable2(&state, "Deno.UnsafeFnPointer#call");
+ {
+ let mut state = state.borrow_mut();
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+ };
+
+ let symbol = PtrSymbol::new(pointer, &def);
+ let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
+
+ let result = ffi_call(
+ call_args,
+ &symbol.cif,
+ symbol.ptr,
+ &def.parameters,
+ def.result,
+ )?;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, def.result) };
+ Ok(result)
+}
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(), &params);
+ 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())
+}
diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs
new file mode 100644
index 000000000..da47dcd47
--- /dev/null
+++ b/ext/ffi/dlfcn.rs
@@ -0,0 +1,398 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::check_unstable;
+use crate::symbol::NativeType;
+use crate::symbol::Symbol;
+use crate::turbocall;
+use crate::FfiPermissions;
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::serde_v8;
+use deno_core::v8;
+use deno_core::Resource;
+use deno_core::ResourceId;
+use dlopen::raw::Library;
+use serde::Deserialize;
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::ffi::c_void;
+use std::path::PathBuf;
+use std::rc::Rc;
+
+pub struct DynamicLibraryResource {
+ lib: Library,
+ pub symbols: HashMap<String, Box<Symbol>>,
+}
+
+impl Resource for DynamicLibraryResource {
+ fn name(&self) -> Cow<str> {
+ "dynamicLibrary".into()
+ }
+
+ fn close(self: Rc<Self>) {
+ drop(self)
+ }
+}
+
+impl DynamicLibraryResource {
+ pub fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> {
+ // By default, Err returned by this function does not tell
+ // which symbol wasn't exported. So we'll modify the error
+ // message to include the name of symbol.
+ //
+ // SAFETY: The obtained T symbol is the size of a pointer.
+ match unsafe { self.lib.symbol::<*const c_void>(&symbol) } {
+ Ok(value) => Ok(Ok(value)),
+ Err(err) => Err(generic_error(format!(
+ "Failed to register symbol {}: {}",
+ symbol, err
+ ))),
+ }?
+ }
+}
+
+pub fn needs_unwrap(rv: NativeType) -> bool {
+ matches!(
+ rv,
+ NativeType::Function
+ | NativeType::Pointer
+ | NativeType::Buffer
+ | NativeType::I64
+ | NativeType::ISize
+ | NativeType::U64
+ | NativeType::USize
+ )
+}
+
+fn is_i64(rv: NativeType) -> bool {
+ matches!(rv, NativeType::I64 | NativeType::ISize)
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct ForeignFunction {
+ name: Option<String>,
+ pub parameters: Vec<NativeType>,
+ pub result: NativeType,
+ #[serde(rename = "nonblocking")]
+ non_blocking: Option<bool>,
+ #[serde(rename = "callback")]
+ #[serde(default = "default_callback")]
+ callback: bool,
+}
+
+fn default_callback() -> bool {
+ false
+}
+
+// ForeignStatic's name and type fields are read and used by
+// serde_v8 to determine which variant a ForeignSymbol is.
+// They are not used beyond that and are thus marked with underscores.
+#[derive(Deserialize, Debug)]
+struct ForeignStatic {
+ #[serde(rename(deserialize = "name"))]
+ _name: Option<String>,
+ #[serde(rename(deserialize = "type"))]
+ _type: String,
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(untagged)]
+enum ForeignSymbol {
+ ForeignFunction(ForeignFunction),
+ ForeignStatic(ForeignStatic),
+}
+
+#[derive(Deserialize, Debug)]
+pub struct FfiLoadArgs {
+ path: String,
+ symbols: HashMap<String, ForeignSymbol>,
+}
+
+#[op(v8)]
+pub fn op_ffi_load<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: &mut deno_core::OpState,
+ args: FfiLoadArgs,
+) -> Result<(ResourceId, serde_v8::Value<'scope>), AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ let path = args.path;
+
+ check_unstable(state, "Deno.dlopen");
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(Some(&PathBuf::from(&path)))?;
+
+ let lib = Library::open(&path).map_err(|e| {
+ dlopen::Error::OpeningLibraryError(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format_error(e, path),
+ ))
+ })?;
+ let mut resource = DynamicLibraryResource {
+ lib,
+ symbols: HashMap::new(),
+ };
+ let obj = v8::Object::new(scope);
+
+ for (symbol_key, foreign_symbol) in args.symbols {
+ match foreign_symbol {
+ ForeignSymbol::ForeignStatic(_) => {
+ // No-op: Statics will be handled separately and are not part of the Rust-side resource.
+ }
+ ForeignSymbol::ForeignFunction(foreign_fn) => {
+ let symbol = match &foreign_fn.name {
+ Some(symbol) => symbol,
+ None => &symbol_key,
+ };
+ // By default, Err returned by this function does not tell
+ // which symbol wasn't exported. So we'll modify the error
+ // message to include the name of symbol.
+ let fn_ptr =
+ // SAFETY: The obtained T symbol is the size of a pointer.
+ match unsafe { resource.lib.symbol::<*const c_void>(symbol) } {
+ Ok(value) => Ok(value),
+ Err(err) => Err(generic_error(format!(
+ "Failed to register symbol {}: {}",
+ symbol, err
+ ))),
+ }?;
+ let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
+ let cif = libffi::middle::Cif::new(
+ foreign_fn
+ .parameters
+ .clone()
+ .into_iter()
+ .map(libffi::middle::Type::from),
+ foreign_fn.result.into(),
+ );
+
+ let func_key = v8::String::new(scope, &symbol_key).unwrap();
+ let sym = Box::new(Symbol {
+ cif,
+ ptr,
+ parameter_types: foreign_fn.parameters,
+ result_type: foreign_fn.result,
+ can_callback: foreign_fn.callback,
+ });
+
+ resource.symbols.insert(symbol_key, sym.clone());
+ match foreign_fn.non_blocking {
+ // Generate functions for synchronous calls.
+ Some(false) | None => {
+ let function = make_sync_fn(scope, sym);
+ obj.set(scope, func_key.into(), function.into());
+ }
+ // This optimization is not yet supported for non-blocking calls.
+ _ => {}
+ };
+ }
+ }
+ }
+
+ let rid = state.resource_table.add(resource);
+ Ok((
+ rid,
+ serde_v8::Value {
+ v8_value: obj.into(),
+ },
+ ))
+}
+
+// Create a JavaScript function for synchronous FFI call to
+// the given symbol.
+fn make_sync_fn<'s>(
+ scope: &mut v8::HandleScope<'s>,
+ sym: Box<Symbol>,
+) -> v8::Local<'s, v8::Function> {
+ let sym = Box::leak(sym);
+ let builder = v8::FunctionTemplate::builder(
+ |scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ mut rv: v8::ReturnValue| {
+ let external: v8::Local<v8::External> = args.data().try_into().unwrap();
+ // SAFETY: The pointer will not be deallocated until the function is
+ // garbage collected.
+ let symbol = unsafe { &*(external.value() as *const Symbol) };
+ let needs_unwrap = match needs_unwrap(symbol.result_type) {
+ true => Some(args.get(symbol.parameter_types.len() as i32)),
+ false => None,
+ };
+ match crate::call::ffi_call_sync(scope, args, symbol) {
+ Ok(result) => {
+ match needs_unwrap {
+ Some(v) => {
+ let view: v8::Local<v8::ArrayBufferView> = v.try_into().unwrap();
+ let backing_store =
+ view.buffer(scope).unwrap().get_backing_store();
+
+ if is_i64(symbol.result_type) {
+ // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
+ // it points to a fixed continuous slice of bytes on the heap.
+ let bs = unsafe {
+ &mut *(&backing_store[..] as *const _ as *mut [u8]
+ as *mut i64)
+ };
+ // SAFETY: We already checked that type == I64
+ let value = unsafe { result.i64_value };
+ *bs = value;
+ } else {
+ // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
+ // it points to a fixed continuous slice of bytes on the heap.
+ let bs = unsafe {
+ &mut *(&backing_store[..] as *const _ as *mut [u8]
+ as *mut u64)
+ };
+ // SAFETY: We checked that type == U64
+ let value = unsafe { result.u64_value };
+ *bs = value;
+ }
+ }
+ None => {
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, symbol.result_type) };
+ rv.set(result.v8_value);
+ }
+ }
+ }
+ Err(err) => {
+ deno_core::_ops::throw_type_error(scope, err.to_string());
+ }
+ };
+ },
+ )
+ .data(v8::External::new(scope, sym as *mut Symbol as *mut _).into());
+
+ let mut fast_call_alloc = None;
+
+ let func = if turbocall::is_compatible(sym) {
+ let trampoline = turbocall::compile_trampoline(sym);
+ let func = builder.build_fast(
+ scope,
+ &turbocall::make_template(sym, &trampoline),
+ None,
+ );
+ fast_call_alloc = Some(Box::into_raw(Box::new(trampoline)));
+ func
+ } else {
+ builder.build(scope)
+ };
+ let func = func.get_function(scope).unwrap();
+
+ let weak = v8::Weak::with_finalizer(
+ scope,
+ func,
+ Box::new(move |_| {
+ // SAFETY: This is never called twice. pointer obtained
+ // from Box::into_raw, hence, satisfies memory layout requirements.
+ let _ = unsafe { Box::from_raw(sym) };
+ if let Some(fast_call_ptr) = fast_call_alloc {
+ // fast-call compiled trampoline is unmapped when the MMAP handle is dropped
+ // SAFETY: This is never called twice. pointer obtained
+ // from Box::into_raw, hence, satisfies memory layout requirements.
+ let _ = unsafe { Box::from_raw(fast_call_ptr) };
+ }
+ }),
+ );
+
+ weak.to_local(scope).unwrap()
+}
+
+// `path` is only used on Windows.
+#[allow(unused_variables)]
+pub(crate) fn format_error(e: dlopen::Error, path: String) -> String {
+ match e {
+ #[cfg(target_os = "windows")]
+ // This calls FormatMessageW with library path
+ // as replacement for the insert sequences.
+ // Unlike libstd which passes the FORMAT_MESSAGE_IGNORE_INSERTS
+ // flag without any arguments.
+ //
+ // https://github.com/denoland/deno/issues/11632
+ dlopen::Error::OpeningLibraryError(e) => {
+ use std::ffi::OsStr;
+ use std::os::windows::ffi::OsStrExt;
+ use winapi::shared::minwindef::DWORD;
+ use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER;
+ use winapi::um::errhandlingapi::GetLastError;
+ use winapi::um::winbase::FormatMessageW;
+ use winapi::um::winbase::FORMAT_MESSAGE_ARGUMENT_ARRAY;
+ use winapi::um::winbase::FORMAT_MESSAGE_FROM_SYSTEM;
+ use winapi::um::winnt::LANG_SYSTEM_DEFAULT;
+ use winapi::um::winnt::MAKELANGID;
+ use winapi::um::winnt::SUBLANG_SYS_DEFAULT;
+
+ let err_num = match e.raw_os_error() {
+ Some(err_num) => err_num,
+ // This should never hit unless dlopen changes its error type.
+ None => return e.to_string(),
+ };
+
+ // Language ID (0x0800)
+ let lang_id =
+ MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) as DWORD;
+
+ let mut buf = vec![0; 500];
+
+ let path = OsStr::new(&path)
+ .encode_wide()
+ .chain(Some(0).into_iter())
+ .collect::<Vec<_>>();
+
+ let arguments = [path.as_ptr()];
+
+ loop {
+ // SAFETY:
+ // winapi call to format the error message
+ let length = unsafe {
+ FormatMessageW(
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
+ std::ptr::null_mut(),
+ err_num as DWORD,
+ lang_id as DWORD,
+ buf.as_mut_ptr(),
+ buf.len() as DWORD,
+ arguments.as_ptr() as _,
+ )
+ };
+
+ if length == 0 {
+ // SAFETY:
+ // winapi call to get the last error message
+ let err_num = unsafe { GetLastError() };
+ if err_num == ERROR_INSUFFICIENT_BUFFER {
+ buf.resize(buf.len() * 2, 0);
+ continue;
+ }
+
+ // Something went wrong, just return the original error.
+ return e.to_string();
+ }
+
+ let msg = String::from_utf16_lossy(&buf[..length as usize]);
+ return msg;
+ }
+ }
+ _ => e.to_string(),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_format_error() {
+ use super::format_error;
+
+ // BAD_EXE_FORMAT
+ let err = dlopen::Error::OpeningLibraryError(
+ std::io::Error::from_raw_os_error(0x000000C1),
+ );
+ assert_eq!(
+ format_error(err, "foo.dll".to_string()),
+ "foo.dll is not a valid Win32 application.\r\n".to_string(),
+ );
+ }
+}
diff --git a/ext/ffi/ir.rs b/ext/ffi/ir.rs
new file mode 100644
index 000000000..67c65b5b5
--- /dev/null
+++ b/ext/ffi/ir.rs
@@ -0,0 +1,527 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::symbol::NativeType;
+use crate::MAX_SAFE_INTEGER;
+use crate::MIN_SAFE_INTEGER;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::serde_json::Value;
+use deno_core::serde_v8;
+use deno_core::v8;
+use libffi::middle::Arg;
+use std::ffi::c_void;
+use std::ptr;
+
+/// Intermediate format for easy translation from NativeType + V8 value
+/// to libffi argument types.
+#[repr(C)]
+pub union NativeValue {
+ pub void_value: (),
+ pub bool_value: bool,
+ pub u8_value: u8,
+ pub i8_value: i8,
+ pub u16_value: u16,
+ pub i16_value: i16,
+ pub u32_value: u32,
+ pub i32_value: i32,
+ pub u64_value: u64,
+ pub i64_value: i64,
+ pub usize_value: usize,
+ pub isize_value: isize,
+ pub f32_value: f32,
+ pub f64_value: f64,
+ pub pointer: *mut c_void,
+}
+
+impl NativeValue {
+ pub unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
+ match native_type {
+ NativeType::Void => unreachable!(),
+ NativeType::Bool => Arg::new(&self.bool_value),
+ NativeType::U8 => Arg::new(&self.u8_value),
+ NativeType::I8 => Arg::new(&self.i8_value),
+ NativeType::U16 => Arg::new(&self.u16_value),
+ NativeType::I16 => Arg::new(&self.i16_value),
+ NativeType::U32 => Arg::new(&self.u32_value),
+ NativeType::I32 => Arg::new(&self.i32_value),
+ NativeType::U64 => Arg::new(&self.u64_value),
+ NativeType::I64 => Arg::new(&self.i64_value),
+ NativeType::USize => Arg::new(&self.usize_value),
+ NativeType::ISize => Arg::new(&self.isize_value),
+ NativeType::F32 => Arg::new(&self.f32_value),
+ NativeType::F64 => Arg::new(&self.f64_value),
+ NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
+ Arg::new(&self.pointer)
+ }
+ }
+ }
+
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ pub unsafe fn to_value(&self, native_type: NativeType) -> Value {
+ match native_type {
+ NativeType::Void => Value::Null,
+ NativeType::Bool => Value::from(self.bool_value),
+ NativeType::U8 => Value::from(self.u8_value),
+ NativeType::I8 => Value::from(self.i8_value),
+ NativeType::U16 => Value::from(self.u16_value),
+ NativeType::I16 => Value::from(self.i16_value),
+ NativeType::U32 => Value::from(self.u32_value),
+ NativeType::I32 => Value::from(self.i32_value),
+ NativeType::U64 => Value::from(self.u64_value),
+ NativeType::I64 => Value::from(self.i64_value),
+ NativeType::USize => Value::from(self.usize_value),
+ NativeType::ISize => Value::from(self.isize_value),
+ NativeType::F32 => Value::from(self.f32_value),
+ NativeType::F64 => Value::from(self.f64_value),
+ NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
+ Value::from(self.pointer as usize)
+ }
+ }
+ }
+
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ #[inline]
+ pub unsafe fn to_v8<'scope>(
+ &self,
+ scope: &mut v8::HandleScope<'scope>,
+ native_type: NativeType,
+ ) -> serde_v8::Value<'scope> {
+ match native_type {
+ NativeType::Void => {
+ let local_value: v8::Local<v8::Value> = v8::undefined(scope).into();
+ local_value.into()
+ }
+ NativeType::Bool => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Boolean::new(scope, self.bool_value).into();
+ local_value.into()
+ }
+ NativeType::U8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u8_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i8_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u16_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i16_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u32_value).into();
+ local_value.into()
+ }
+ NativeType::I32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i32_value).into();
+ local_value.into()
+ }
+ NativeType::U64 => {
+ let value = self.u64_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::I64 => {
+ let value = self.i64_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as i64 || value < MIN_SAFE_INTEGER as i64
+ {
+ v8::BigInt::new_from_i64(scope, self.i64_value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::USize => {
+ let value = self.usize_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as usize {
+ v8::BigInt::new_from_u64(scope, value as u64).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::ISize => {
+ let value = self.isize_value;
+ let local_value: v8::Local<v8::Value> =
+ if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value) {
+ v8::BigInt::new_from_i64(scope, self.isize_value as i64).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::F32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f32_value as f64).into();
+ local_value.into()
+ }
+ NativeType::F64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f64_value).into();
+ local_value.into()
+ }
+ NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
+ let value = self.pointer as u64;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ }
+ }
+}
+
+// SAFETY: unsafe trait must have unsafe implementation
+unsafe impl Send for NativeValue {}
+
+#[inline]
+pub fn ffi_parse_bool_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let bool_value = v8::Local::<v8::Boolean>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u8 type, expected boolean"))?
+ .is_true();
+ Ok(NativeValue { bool_value })
+}
+
+#[inline]
+pub fn ffi_parse_u8_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u8_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u8 type, expected unsigned integer"))?
+ .value() as u8;
+ Ok(NativeValue { u8_value })
+}
+
+#[inline]
+pub fn ffi_parse_i8_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i8_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i8 type, expected integer"))?
+ .value() as i8;
+ Ok(NativeValue { i8_value })
+}
+
+#[inline]
+pub fn ffi_parse_u16_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u16_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u16 type, expected unsigned integer"))?
+ .value() as u16;
+ Ok(NativeValue { u16_value })
+}
+
+#[inline]
+pub fn ffi_parse_i16_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i16_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i16 type, expected integer"))?
+ .value() as i16;
+ Ok(NativeValue { i16_value })
+}
+
+#[inline]
+pub fn ffi_parse_u32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u32_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u32 type, expected unsigned integer"))?
+ .value() as u32;
+ Ok(NativeValue { u32_value })
+}
+
+#[inline]
+pub fn ffi_parse_i32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i32_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i32 type, expected integer"))?
+ .value() as i32;
+ Ok(NativeValue { i32_value })
+}
+
+#[inline]
+pub fn ffi_parse_u64_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let u64_value: u64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
+ {
+ value.u64_value().0
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as u64
+ } else {
+ return Err(type_error(
+ "Invalid FFI u64 type, expected unsigned integer",
+ ));
+ };
+ Ok(NativeValue { u64_value })
+}
+
+#[inline]
+pub fn ffi_parse_i64_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let i64_value: i64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
+ {
+ value.i64_value().0
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as i64
+ } else {
+ return Err(type_error("Invalid FFI i64 type, expected integer"));
+ };
+ Ok(NativeValue { i64_value })
+}
+
+#[inline]
+pub fn ffi_parse_usize_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let usize_value: usize =
+ if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize
+ } else {
+ return Err(type_error("Invalid FFI usize type, expected integer"));
+ };
+ Ok(NativeValue { usize_value })
+}
+
+#[inline]
+pub fn ffi_parse_isize_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let isize_value: isize =
+ if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.i64_value().0 as isize
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as isize
+ } else {
+ return Err(type_error("Invalid FFI isize type, expected integer"));
+ };
+ Ok(NativeValue { isize_value })
+}
+
+#[inline]
+pub fn ffi_parse_f32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let f32_value = v8::Local::<v8::Number>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI f32 type, expected number"))?
+ .value() as f32;
+ Ok(NativeValue { f32_value })
+}
+
+#[inline]
+pub fn ffi_parse_f64_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let f64_value = v8::Local::<v8::Number>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI f64 type, expected number"))?
+ .value() as f64;
+ Ok(NativeValue { f64_value })
+}
+
+#[inline]
+pub fn ffi_parse_pointer_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
+ // 2. Number: Common and supported by Fast API.
+ // 3. Null: Very uncommon / can be represented by a 0.
+ let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize as *mut c_void
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize as *mut c_void
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI pointer type, expected null, integer or BigInt",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+#[inline]
+pub fn ffi_parse_buffer_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. ArrayBuffer: Fairly common and not supported by Fast API, optimise this case.
+ // 2. ArrayBufferView: Common and supported by Fast API
+ // 5. Null: Very uncommon / can be represented by a 0.
+
+ let pointer = if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(arg) {
+ if let Some(non_null) = value.data() {
+ non_null.as_ptr()
+ } else {
+ ptr::null_mut()
+ }
+ } else if let Ok(value) = v8::Local::<v8::ArrayBufferView>::try_from(arg) {
+ let byte_offset = value.byte_offset();
+ let pointer = value
+ .buffer(scope)
+ .ok_or_else(|| {
+ type_error("Invalid FFI ArrayBufferView, expected data in the buffer")
+ })?
+ .data();
+ if let Some(non_null) = pointer {
+ // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset
+ // is within the buffer backing store.
+ unsafe { non_null.as_ptr().add(byte_offset) }
+ } else {
+ ptr::null_mut()
+ }
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+#[inline]
+pub fn ffi_parse_function_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
+ // 2. Number: Common and supported by Fast API, optimise this case as second.
+ // 3. Null: Very uncommon / can be represented by a 0.
+ let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize as *mut c_void
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize as *mut c_void
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI function type, expected null, integer, or BigInt",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+pub fn ffi_parse_args<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ args: serde_v8::Value<'scope>,
+ parameter_types: &[NativeType],
+) -> Result<Vec<NativeValue>, AnyError>
+where
+ 'scope: 'scope,
+{
+ if parameter_types.is_empty() {
+ return Ok(vec![]);
+ }
+
+ let args = v8::Local::<v8::Array>::try_from(args.v8_value)
+ .map_err(|_| type_error("Invalid FFI parameters, expected Array"))?;
+ let mut ffi_args: Vec<NativeValue> =
+ Vec::with_capacity(parameter_types.len());
+
+ for (index, native_type) in parameter_types.iter().enumerate() {
+ let value = args.get_index(scope, index as u32).unwrap();
+ match native_type {
+ NativeType::Bool => {
+ ffi_args.push(ffi_parse_bool_arg(value)?);
+ }
+ NativeType::U8 => {
+ ffi_args.push(ffi_parse_u8_arg(value)?);
+ }
+ NativeType::I8 => {
+ ffi_args.push(ffi_parse_i8_arg(value)?);
+ }
+ NativeType::U16 => {
+ ffi_args.push(ffi_parse_u16_arg(value)?);
+ }
+ NativeType::I16 => {
+ ffi_args.push(ffi_parse_i16_arg(value)?);
+ }
+ NativeType::U32 => {
+ ffi_args.push(ffi_parse_u32_arg(value)?);
+ }
+ NativeType::I32 => {
+ ffi_args.push(ffi_parse_i32_arg(value)?);
+ }
+ NativeType::U64 => {
+ ffi_args.push(ffi_parse_u64_arg(scope, value)?);
+ }
+ NativeType::I64 => {
+ ffi_args.push(ffi_parse_i64_arg(scope, value)?);
+ }
+ NativeType::USize => {
+ ffi_args.push(ffi_parse_usize_arg(scope, value)?);
+ }
+ NativeType::ISize => {
+ ffi_args.push(ffi_parse_isize_arg(scope, value)?);
+ }
+ NativeType::F32 => {
+ ffi_args.push(ffi_parse_f32_arg(value)?);
+ }
+ NativeType::F64 => {
+ ffi_args.push(ffi_parse_f64_arg(value)?);
+ }
+ NativeType::Buffer => {
+ ffi_args.push(ffi_parse_buffer_arg(scope, value)?);
+ }
+ NativeType::Pointer => {
+ ffi_args.push(ffi_parse_pointer_arg(scope, value)?);
+ }
+ NativeType::Function => {
+ ffi_args.push(ffi_parse_function_arg(scope, value)?);
+ }
+ NativeType::Void => {
+ unreachable!();
+ }
+ }
+ }
+
+ Ok(ffi_args)
+}
diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs
index 7e7756c93..f3a906505 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -1,47 +1,41 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use core::ptr::NonNull;
-use deno_core::anyhow::anyhow;
-use deno_core::error::generic_error;
-use deno_core::error::range_error;
-use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::channel::mpsc;
-use deno_core::futures::Future;
use deno_core::include_js_files;
-use deno_core::op;
-use deno_core::serde_json::Value;
-use deno_core::serde_v8;
use deno_core::v8;
-use deno_core::CancelFuture;
-use deno_core::CancelHandle;
use deno_core::Extension;
use deno_core::OpState;
-use deno_core::Resource;
-use deno_core::ResourceId;
-use dlopen::raw::Library;
-use libffi::middle::Arg;
-use libffi::middle::Cif;
-use serde::Deserialize;
-use std::borrow::Cow;
+
use std::cell::RefCell;
-use std::collections::HashMap;
-use std::ffi::c_void;
-use std::ffi::CStr;
-use std::future::IntoFuture;
use std::mem::size_of;
use std::os::raw::c_char;
use std::os::raw::c_short;
use std::path::Path;
-use std::path::PathBuf;
-use std::pin::Pin;
use std::ptr;
use std::rc::Rc;
-use std::sync::mpsc::sync_channel;
-use std::task::Poll;
-use std::task::Waker;
-mod fast_call;
+mod call;
+mod callback;
+mod dlfcn;
+mod ir;
+mod repr;
+mod r#static;
+mod symbol;
+mod turbocall;
+
+use call::op_ffi_call_nonblocking;
+use call::op_ffi_call_ptr;
+use call::op_ffi_call_ptr_nonblocking;
+use callback::op_ffi_unsafe_callback_create;
+use callback::op_ffi_unsafe_callback_ref;
+use callback::op_ffi_unsafe_callback_unref;
+use dlfcn::op_ffi_load;
+use dlfcn::ForeignFunction;
+use r#static::op_ffi_get_static;
+use repr::*;
+use symbol::NativeType;
+use symbol::Symbol;
#[cfg(not(target_pointer_width = "64"))]
compile_error!("platform not supported");
@@ -56,8 +50,8 @@ thread_local! {
static LOCAL_ISOLATE_POINTER: RefCell<*const v8::Isolate> = RefCell::new(ptr::null());
}
-const MAX_SAFE_INTEGER: isize = 9007199254740991;
-const MIN_SAFE_INTEGER: isize = -9007199254740991;
+pub(crate) const MAX_SAFE_INTEGER: isize = 9007199254740991;
+pub(crate) const MIN_SAFE_INTEGER: isize = -9007199254740991;
pub struct Unstable(pub bool);
@@ -82,86 +76,11 @@ pub trait FfiPermissions {
fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError>;
}
-#[derive(Clone)]
-struct Symbol {
- cif: libffi::middle::Cif,
- ptr: libffi::middle::CodePtr,
- parameter_types: Vec<NativeType>,
- result_type: NativeType,
- can_callback: bool,
-}
-
-#[allow(clippy::non_send_fields_in_send_ty)]
-// SAFETY: unsafe trait must have unsafe implementation
-unsafe impl Send for Symbol {}
-// SAFETY: unsafe trait must have unsafe implementation
-unsafe impl Sync for Symbol {}
-
-#[derive(Clone)]
-struct PtrSymbol {
- cif: libffi::middle::Cif,
- ptr: libffi::middle::CodePtr,
-}
-
-impl PtrSymbol {
- 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 DynamicLibraryResource {
- lib: Library,
- symbols: HashMap<String, Box<Symbol>>,
-}
-
-impl Resource for DynamicLibraryResource {
- fn name(&self) -> Cow<str> {
- "dynamicLibrary".into()
- }
+pub(crate) type PendingFfiAsyncWork = Box<dyn FnOnce()>;
- fn close(self: Rc<Self>) {
- drop(self)
- }
-}
-
-impl DynamicLibraryResource {
- fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> {
- // By default, Err returned by this function does not tell
- // which symbol wasn't exported. So we'll modify the error
- // message to include the name of symbol.
- //
- // SAFETY: The obtained T symbol is the size of a pointer.
- match unsafe { self.lib.symbol::<*const c_void>(&symbol) } {
- Ok(value) => Ok(Ok(value)),
- Err(err) => Err(generic_error(format!(
- "Failed to register symbol {}: {}",
- symbol, err
- ))),
- }?
- }
-}
-
-type PendingFfiAsyncWork = Box<dyn FnOnce()>;
-
-struct FfiState {
- async_work_sender: mpsc::UnboundedSender<PendingFfiAsyncWork>,
- async_work_receiver: mpsc::UnboundedReceiver<PendingFfiAsyncWork>,
+pub(crate) struct FfiState {
+ pub(crate) async_work_sender: mpsc::UnboundedSender<PendingFfiAsyncWork>,
+ pub(crate) async_work_receiver: mpsc::UnboundedReceiver<PendingFfiAsyncWork>,
}
pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
@@ -237,2295 +156,3 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
})
.build()
}
-
-/// Defines the accepted types that can be used as
-/// parameters and return values in FFI.
-#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-enum NativeType {
- Void,
- Bool,
- U8,
- I8,
- U16,
- I16,
- U32,
- I32,
- U64,
- I64,
- USize,
- ISize,
- F32,
- F64,
- Pointer,
- Buffer,
- Function,
-}
-
-impl From<NativeType> for libffi::middle::Type {
- fn from(native_type: NativeType) -> Self {
- match native_type {
- NativeType::Void => libffi::middle::Type::void(),
- NativeType::U8 | NativeType::Bool => libffi::middle::Type::u8(),
- NativeType::I8 => libffi::middle::Type::i8(),
- NativeType::U16 => libffi::middle::Type::u16(),
- NativeType::I16 => libffi::middle::Type::i16(),
- NativeType::U32 => libffi::middle::Type::u32(),
- NativeType::I32 => libffi::middle::Type::i32(),
- NativeType::U64 => libffi::middle::Type::u64(),
- NativeType::I64 => libffi::middle::Type::i64(),
- NativeType::USize => libffi::middle::Type::usize(),
- NativeType::ISize => libffi::middle::Type::isize(),
- NativeType::F32 => libffi::middle::Type::f32(),
- NativeType::F64 => libffi::middle::Type::f64(),
- NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
- libffi::middle::Type::pointer()
- }
- }
- }
-}
-
-/// Intermediate format for easy translation from NativeType + V8 value
-/// to libffi argument types.
-#[repr(C)]
-union NativeValue {
- void_value: (),
- bool_value: bool,
- u8_value: u8,
- i8_value: i8,
- u16_value: u16,
- i16_value: i16,
- u32_value: u32,
- i32_value: i32,
- u64_value: u64,
- i64_value: i64,
- usize_value: usize,
- isize_value: isize,
- f32_value: f32,
- f64_value: f64,
- pointer: *mut c_void,
-}
-
-impl NativeValue {
- unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
- match native_type {
- NativeType::Void => unreachable!(),
- NativeType::Bool => Arg::new(&self.bool_value),
- NativeType::U8 => Arg::new(&self.u8_value),
- NativeType::I8 => Arg::new(&self.i8_value),
- NativeType::U16 => Arg::new(&self.u16_value),
- NativeType::I16 => Arg::new(&self.i16_value),
- NativeType::U32 => Arg::new(&self.u32_value),
- NativeType::I32 => Arg::new(&self.i32_value),
- NativeType::U64 => Arg::new(&self.u64_value),
- NativeType::I64 => Arg::new(&self.i64_value),
- NativeType::USize => Arg::new(&self.usize_value),
- NativeType::ISize => Arg::new(&self.isize_value),
- NativeType::F32 => Arg::new(&self.f32_value),
- NativeType::F64 => Arg::new(&self.f64_value),
- NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
- Arg::new(&self.pointer)
- }
- }
- }
-
- // SAFETY: native_type must correspond to the type of value represented by the union field
- unsafe fn to_value(&self, native_type: NativeType) -> Value {
- match native_type {
- NativeType::Void => Value::Null,
- NativeType::Bool => Value::from(self.bool_value),
- NativeType::U8 => Value::from(self.u8_value),
- NativeType::I8 => Value::from(self.i8_value),
- NativeType::U16 => Value::from(self.u16_value),
- NativeType::I16 => Value::from(self.i16_value),
- NativeType::U32 => Value::from(self.u32_value),
- NativeType::I32 => Value::from(self.i32_value),
- NativeType::U64 => Value::from(self.u64_value),
- NativeType::I64 => Value::from(self.i64_value),
- NativeType::USize => Value::from(self.usize_value),
- NativeType::ISize => Value::from(self.isize_value),
- NativeType::F32 => Value::from(self.f32_value),
- NativeType::F64 => Value::from(self.f64_value),
- NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
- Value::from(self.pointer as usize)
- }
- }
- }
-
- // SAFETY: native_type must correspond to the type of value represented by the union field
- #[inline]
- unsafe fn to_v8<'scope>(
- &self,
- scope: &mut v8::HandleScope<'scope>,
- native_type: NativeType,
- ) -> serde_v8::Value<'scope> {
- match native_type {
- NativeType::Void => {
- let local_value: v8::Local<v8::Value> = v8::undefined(scope).into();
- local_value.into()
- }
- NativeType::Bool => {
- let local_value: v8::Local<v8::Value> =
- v8::Boolean::new(scope, self.bool_value).into();
- local_value.into()
- }
- NativeType::U8 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, self.u8_value as u32).into();
- local_value.into()
- }
- NativeType::I8 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new(scope, self.i8_value as i32).into();
- local_value.into()
- }
- NativeType::U16 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, self.u16_value as u32).into();
- local_value.into()
- }
- NativeType::I16 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new(scope, self.i16_value as i32).into();
- local_value.into()
- }
- NativeType::U32 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, self.u32_value).into();
- local_value.into()
- }
- NativeType::I32 => {
- let local_value: v8::Local<v8::Value> =
- v8::Integer::new(scope, self.i32_value).into();
- local_value.into()
- }
- NativeType::U64 => {
- let value = self.u64_value;
- let local_value: v8::Local<v8::Value> =
- if value > MAX_SAFE_INTEGER as u64 {
- v8::BigInt::new_from_u64(scope, value).into()
- } else {
- v8::Number::new(scope, value as f64).into()
- };
- local_value.into()
- }
- NativeType::I64 => {
- let value = self.i64_value;
- let local_value: v8::Local<v8::Value> =
- if value > MAX_SAFE_INTEGER as i64 || value < MIN_SAFE_INTEGER as i64
- {
- v8::BigInt::new_from_i64(scope, self.i64_value).into()
- } else {
- v8::Number::new(scope, value as f64).into()
- };
- local_value.into()
- }
- NativeType::USize => {
- let value = self.usize_value;
- let local_value: v8::Local<v8::Value> =
- if value > MAX_SAFE_INTEGER as usize {
- v8::BigInt::new_from_u64(scope, value as u64).into()
- } else {
- v8::Number::new(scope, value as f64).into()
- };
- local_value.into()
- }
- NativeType::ISize => {
- let value = self.isize_value;
- let local_value: v8::Local<v8::Value> =
- if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value) {
- v8::BigInt::new_from_i64(scope, self.isize_value as i64).into()
- } else {
- v8::Number::new(scope, value as f64).into()
- };
- local_value.into()
- }
- NativeType::F32 => {
- let local_value: v8::Local<v8::Value> =
- v8::Number::new(scope, self.f32_value as f64).into();
- local_value.into()
- }
- NativeType::F64 => {
- let local_value: v8::Local<v8::Value> =
- v8::Number::new(scope, self.f64_value).into();
- local_value.into()
- }
- NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
- let value = self.pointer as u64;
- let local_value: v8::Local<v8::Value> =
- if value > MAX_SAFE_INTEGER as u64 {
- v8::BigInt::new_from_u64(scope, value).into()
- } else {
- v8::Number::new(scope, value as f64).into()
- };
- local_value.into()
- }
- }
- }
-}
-
-// SAFETY: unsafe trait must have unsafe implementation
-unsafe impl Send for NativeValue {}
-
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-struct ForeignFunction {
- name: Option<String>,
- parameters: Vec<NativeType>,
- result: NativeType,
- #[serde(rename = "nonblocking")]
- non_blocking: Option<bool>,
- #[serde(rename = "callback")]
- #[serde(default = "default_callback")]
- callback: bool,
-}
-
-fn default_callback() -> bool {
- false
-}
-
-// ForeignStatic's name and type fields are read and used by
-// serde_v8 to determine which variant a ForeignSymbol is.
-// They are not used beyond that and are thus marked with underscores.
-#[derive(Deserialize, Debug)]
-struct ForeignStatic {
- #[serde(rename(deserialize = "name"))]
- _name: Option<String>,
- #[serde(rename(deserialize = "type"))]
- _type: String,
-}
-
-#[derive(Deserialize, Debug)]
-#[serde(untagged)]
-enum ForeignSymbol {
- ForeignFunction(ForeignFunction),
- ForeignStatic(ForeignStatic),
-}
-
-#[derive(Deserialize, Debug)]
-struct FfiLoadArgs {
- path: String,
- symbols: HashMap<String, ForeignSymbol>,
-}
-
-// `path` is only used on Windows.
-#[allow(unused_variables)]
-pub(crate) fn format_error(e: dlopen::Error, path: String) -> String {
- match e {
- #[cfg(target_os = "windows")]
- // This calls FormatMessageW with library path
- // as replacement for the insert sequences.
- // Unlike libstd which passes the FORMAT_MESSAGE_IGNORE_INSERTS
- // flag without any arguments.
- //
- // https://github.com/denoland/deno/issues/11632
- dlopen::Error::OpeningLibraryError(e) => {
- use std::ffi::OsStr;
- use std::os::windows::ffi::OsStrExt;
- use winapi::shared::minwindef::DWORD;
- use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER;
- use winapi::um::errhandlingapi::GetLastError;
- use winapi::um::winbase::FormatMessageW;
- use winapi::um::winbase::FORMAT_MESSAGE_ARGUMENT_ARRAY;
- use winapi::um::winbase::FORMAT_MESSAGE_FROM_SYSTEM;
- use winapi::um::winnt::LANG_SYSTEM_DEFAULT;
- use winapi::um::winnt::MAKELANGID;
- use winapi::um::winnt::SUBLANG_SYS_DEFAULT;
-
- let err_num = match e.raw_os_error() {
- Some(err_num) => err_num,
- // This should never hit unless dlopen changes its error type.
- None => return e.to_string(),
- };
-
- // Language ID (0x0800)
- let lang_id =
- MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) as DWORD;
-
- let mut buf = vec![0; 500];
-
- let path = OsStr::new(&path)
- .encode_wide()
- .chain(Some(0).into_iter())
- .collect::<Vec<_>>();
-
- let arguments = [path.as_ptr()];
-
- loop {
- // SAFETY:
- // winapi call to format the error message
- let length = unsafe {
- FormatMessageW(
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
- std::ptr::null_mut(),
- err_num as DWORD,
- lang_id as DWORD,
- buf.as_mut_ptr(),
- buf.len() as DWORD,
- arguments.as_ptr() as _,
- )
- };
-
- if length == 0 {
- // SAFETY:
- // winapi call to get the last error message
- let err_num = unsafe { GetLastError() };
- if err_num == ERROR_INSUFFICIENT_BUFFER {
- buf.resize(buf.len() * 2, 0);
- continue;
- }
-
- // Something went wrong, just return the original error.
- return e.to_string();
- }
-
- let msg = String::from_utf16_lossy(&buf[..length as usize]);
- return msg;
- }
- }
- _ => e.to_string(),
- }
-}
-
-#[op(v8)]
-fn op_ffi_load<FP, 'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: &mut deno_core::OpState,
- args: FfiLoadArgs,
-) -> Result<(ResourceId, serde_v8::Value<'scope>), AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- let path = args.path;
-
- check_unstable(state, "Deno.dlopen");
- let permissions = state.borrow_mut::<FP>();
- permissions.check(Some(&PathBuf::from(&path)))?;
-
- let lib = Library::open(&path).map_err(|e| {
- dlopen::Error::OpeningLibraryError(std::io::Error::new(
- std::io::ErrorKind::Other,
- format_error(e, path),
- ))
- })?;
- let mut resource = DynamicLibraryResource {
- lib,
- symbols: HashMap::new(),
- };
- let obj = v8::Object::new(scope);
-
- for (symbol_key, foreign_symbol) in args.symbols {
- match foreign_symbol {
- ForeignSymbol::ForeignStatic(_) => {
- // No-op: Statics will be handled separately and are not part of the Rust-side resource.
- }
- ForeignSymbol::ForeignFunction(foreign_fn) => {
- let symbol = match &foreign_fn.name {
- Some(symbol) => symbol,
- None => &symbol_key,
- };
- // By default, Err returned by this function does not tell
- // which symbol wasn't exported. So we'll modify the error
- // message to include the name of symbol.
- let fn_ptr =
- // SAFETY: The obtained T symbol is the size of a pointer.
- match unsafe { resource.lib.symbol::<*const c_void>(symbol) } {
- Ok(value) => Ok(value),
- Err(err) => Err(generic_error(format!(
- "Failed to register symbol {}: {}",
- symbol, err
- ))),
- }?;
- let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
- let cif = libffi::middle::Cif::new(
- foreign_fn
- .parameters
- .clone()
- .into_iter()
- .map(libffi::middle::Type::from),
- foreign_fn.result.into(),
- );
-
- let func_key = v8::String::new(scope, &symbol_key).unwrap();
- let sym = Box::new(Symbol {
- cif,
- ptr,
- parameter_types: foreign_fn.parameters,
- result_type: foreign_fn.result,
- can_callback: foreign_fn.callback,
- });
-
- resource.symbols.insert(symbol_key, sym.clone());
- match foreign_fn.non_blocking {
- // Generate functions for synchronous calls.
- Some(false) | None => {
- let function = make_sync_fn(scope, sym);
- obj.set(scope, func_key.into(), function.into());
- }
- // This optimization is not yet supported for non-blocking calls.
- _ => {}
- };
- }
- }
- }
-
- let rid = state.resource_table.add(resource);
- Ok((
- rid,
- serde_v8::Value {
- v8_value: obj.into(),
- },
- ))
-}
-
-fn needs_unwrap(rv: NativeType) -> bool {
- matches!(
- rv,
- NativeType::Function
- | NativeType::Pointer
- | NativeType::Buffer
- | NativeType::I64
- | NativeType::ISize
- | NativeType::U64
- | NativeType::USize
- )
-}
-
-fn is_i64(rv: NativeType) -> bool {
- matches!(rv, NativeType::I64 | NativeType::ISize)
-}
-
-// Create a JavaScript function for synchronous FFI call to
-// the given symbol.
-fn make_sync_fn<'s>(
- scope: &mut v8::HandleScope<'s>,
- sym: Box<Symbol>,
-) -> v8::Local<'s, v8::Function> {
- let sym = Box::leak(sym);
- let builder = v8::FunctionTemplate::builder(
- |scope: &mut v8::HandleScope,
- args: v8::FunctionCallbackArguments,
- mut rv: v8::ReturnValue| {
- let external: v8::Local<v8::External> = args.data().try_into().unwrap();
- // SAFETY: The pointer will not be deallocated until the function is
- // garbage collected.
- let symbol = unsafe { &*(external.value() as *const Symbol) };
- let needs_unwrap = match needs_unwrap(symbol.result_type) {
- true => Some(args.get(symbol.parameter_types.len() as i32)),
- false => None,
- };
- match ffi_call_sync(scope, args, symbol) {
- Ok(result) => {
- match needs_unwrap {
- Some(v) => {
- let view: v8::Local<v8::ArrayBufferView> = v.try_into().unwrap();
- let backing_store =
- view.buffer(scope).unwrap().get_backing_store();
-
- if is_i64(symbol.result_type) {
- // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
- // it points to a fixed continuous slice of bytes on the heap.
- let bs = unsafe {
- &mut *(&backing_store[..] as *const _ as *mut [u8]
- as *mut i64)
- };
- // SAFETY: We already checked that type == I64
- let value = unsafe { result.i64_value };
- *bs = value;
- } else {
- // SAFETY: v8::SharedRef<v8::BackingStore> is similar to Arc<[u8]>,
- // it points to a fixed continuous slice of bytes on the heap.
- let bs = unsafe {
- &mut *(&backing_store[..] as *const _ as *mut [u8]
- as *mut u64)
- };
- // SAFETY: We checked that type == U64
- let value = unsafe { result.u64_value };
- *bs = value;
- }
- }
- None => {
- // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
- let result = unsafe { result.to_v8(scope, symbol.result_type) };
- rv.set(result.v8_value);
- }
- }
- }
- Err(err) => {
- deno_core::_ops::throw_type_error(scope, err.to_string());
- }
- };
- },
- )
- .data(v8::External::new(scope, sym as *mut Symbol as *mut _).into());
-
- let mut fast_call_alloc = None;
-
- let func = if fast_call::is_compatible(sym) {
- let trampoline = fast_call::compile_trampoline(sym);
- let func = builder.build_fast(
- scope,
- &fast_call::make_template(sym, &trampoline),
- None,
- );
- fast_call_alloc = Some(Box::into_raw(Box::new(trampoline)));
- func
- } else {
- builder.build(scope)
- };
- let func = func.get_function(scope).unwrap();
-
- let weak = v8::Weak::with_finalizer(
- scope,
- func,
- Box::new(move |_| {
- // SAFETY: This is never called twice. pointer obtained
- // from Box::into_raw, hence, satisfies memory layout requirements.
- let _ = unsafe { Box::from_raw(sym) };
- if let Some(fast_call_ptr) = fast_call_alloc {
- // fast-call compiled trampoline is unmapped when the MMAP handle is dropped
- // SAFETY: This is never called twice. pointer obtained
- // from Box::into_raw, hence, satisfies memory layout requirements.
- let _ = unsafe { Box::from_raw(fast_call_ptr) };
- }
- }),
- );
-
- weak.to_local(scope).unwrap()
-}
-
-#[inline]
-fn ffi_parse_bool_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let bool_value = v8::Local::<v8::Boolean>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI u8 type, expected boolean"))?
- .is_true();
- Ok(NativeValue { bool_value })
-}
-
-#[inline]
-fn ffi_parse_u8_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let u8_value = v8::Local::<v8::Uint32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI u8 type, expected unsigned integer"))?
- .value() as u8;
- Ok(NativeValue { u8_value })
-}
-
-#[inline]
-fn ffi_parse_i8_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let i8_value = v8::Local::<v8::Int32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI i8 type, expected integer"))?
- .value() as i8;
- Ok(NativeValue { i8_value })
-}
-
-#[inline]
-fn ffi_parse_u16_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let u16_value = v8::Local::<v8::Uint32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI u16 type, expected unsigned integer"))?
- .value() as u16;
- Ok(NativeValue { u16_value })
-}
-
-#[inline]
-fn ffi_parse_i16_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let i16_value = v8::Local::<v8::Int32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI i16 type, expected integer"))?
- .value() as i16;
- Ok(NativeValue { i16_value })
-}
-
-#[inline]
-fn ffi_parse_u32_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let u32_value = v8::Local::<v8::Uint32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI u32 type, expected unsigned integer"))?
- .value() as u32;
- Ok(NativeValue { u32_value })
-}
-
-#[inline]
-fn ffi_parse_i32_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let i32_value = v8::Local::<v8::Int32>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI i32 type, expected integer"))?
- .value() as i32;
- Ok(NativeValue { i32_value })
-}
-
-#[inline]
-fn ffi_parse_u64_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
- // 2. Number: Common, supported by Fast API, so let that be the optimal case.
- let u64_value: u64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
- {
- value.u64_value().0
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as u64
- } else {
- return Err(type_error(
- "Invalid FFI u64 type, expected unsigned integer",
- ));
- };
- Ok(NativeValue { u64_value })
-}
-
-#[inline]
-fn ffi_parse_i64_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
- // 2. Number: Common, supported by Fast API, so let that be the optimal case.
- let i64_value: i64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
- {
- value.i64_value().0
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as i64
- } else {
- return Err(type_error("Invalid FFI i64 type, expected integer"));
- };
- Ok(NativeValue { i64_value })
-}
-
-#[inline]
-fn ffi_parse_usize_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
- // 2. Number: Common, supported by Fast API, so let that be the optimal case.
- let usize_value: usize =
- if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
- value.u64_value().0 as usize
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as usize
- } else {
- return Err(type_error("Invalid FFI usize type, expected integer"));
- };
- Ok(NativeValue { usize_value })
-}
-
-#[inline]
-fn ffi_parse_isize_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
- // 2. Number: Common, supported by Fast API, so let that be the optimal case.
- let isize_value: isize =
- if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
- value.i64_value().0 as isize
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as isize
- } else {
- return Err(type_error("Invalid FFI isize type, expected integer"));
- };
- Ok(NativeValue { isize_value })
-}
-
-#[inline]
-fn ffi_parse_f32_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let f32_value = v8::Local::<v8::Number>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI f32 type, expected number"))?
- .value() as f32;
- Ok(NativeValue { f32_value })
-}
-
-#[inline]
-fn ffi_parse_f64_arg(
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- let f64_value = v8::Local::<v8::Number>::try_from(arg)
- .map_err(|_| type_error("Invalid FFI f64 type, expected number"))?
- .value() as f64;
- Ok(NativeValue { f64_value })
-}
-
-#[inline]
-fn ffi_parse_pointer_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
- // 2. Number: Common and supported by Fast API.
- // 3. Null: Very uncommon / can be represented by a 0.
- let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
- value.u64_value().0 as usize as *mut c_void
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as usize as *mut c_void
- } else if arg.is_null() {
- ptr::null_mut()
- } else {
- return Err(type_error(
- "Invalid FFI pointer type, expected null, integer or BigInt",
- ));
- };
- Ok(NativeValue { pointer })
-}
-
-#[inline]
-fn ffi_parse_buffer_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. ArrayBuffer: Fairly common and not supported by Fast API, optimise this case.
- // 2. ArrayBufferView: Common and supported by Fast API
- // 5. Null: Very uncommon / can be represented by a 0.
-
- let pointer = if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(arg) {
- if let Some(non_null) = value.data() {
- non_null.as_ptr()
- } else {
- ptr::null_mut()
- }
- } else if let Ok(value) = v8::Local::<v8::ArrayBufferView>::try_from(arg) {
- let byte_offset = value.byte_offset();
- let pointer = value
- .buffer(scope)
- .ok_or_else(|| {
- type_error("Invalid FFI ArrayBufferView, expected data in the buffer")
- })?
- .data();
- if let Some(non_null) = pointer {
- // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset
- // is within the buffer backing store.
- unsafe { non_null.as_ptr().add(byte_offset) }
- } else {
- ptr::null_mut()
- }
- } else if arg.is_null() {
- ptr::null_mut()
- } else {
- return Err(type_error(
- "Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView",
- ));
- };
- Ok(NativeValue { pointer })
-}
-
-#[inline]
-fn ffi_parse_function_arg(
- scope: &mut v8::HandleScope,
- arg: v8::Local<v8::Value>,
-) -> Result<NativeValue, AnyError> {
- // Order of checking:
- // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
- // 2. Number: Common and supported by Fast API, optimise this case as second.
- // 3. Null: Very uncommon / can be represented by a 0.
- let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
- value.u64_value().0 as usize as *mut c_void
- } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
- value.integer_value(scope).unwrap() as usize as *mut c_void
- } else if arg.is_null() {
- ptr::null_mut()
- } else {
- return Err(type_error(
- "Invalid FFI function type, expected null, integer, or BigInt",
- ));
- };
- Ok(NativeValue { pointer })
-}
-
-fn ffi_parse_args<'scope>(
- scope: &mut v8::HandleScope<'scope>,
- args: serde_v8::Value<'scope>,
- parameter_types: &[NativeType],
-) -> Result<Vec<NativeValue>, AnyError>
-where
- 'scope: 'scope,
-{
- if parameter_types.is_empty() {
- return Ok(vec![]);
- }
-
- let args = v8::Local::<v8::Array>::try_from(args.v8_value)
- .map_err(|_| type_error("Invalid FFI parameters, expected Array"))?;
- let mut ffi_args: Vec<NativeValue> =
- Vec::with_capacity(parameter_types.len());
-
- for (index, native_type) in parameter_types.iter().enumerate() {
- let value = args.get_index(scope, index as u32).unwrap();
- match native_type {
- NativeType::Bool => {
- ffi_args.push(ffi_parse_bool_arg(value)?);
- }
- NativeType::U8 => {
- ffi_args.push(ffi_parse_u8_arg(value)?);
- }
- NativeType::I8 => {
- ffi_args.push(ffi_parse_i8_arg(value)?);
- }
- NativeType::U16 => {
- ffi_args.push(ffi_parse_u16_arg(value)?);
- }
- NativeType::I16 => {
- ffi_args.push(ffi_parse_i16_arg(value)?);
- }
- NativeType::U32 => {
- ffi_args.push(ffi_parse_u32_arg(value)?);
- }
- NativeType::I32 => {
- ffi_args.push(ffi_parse_i32_arg(value)?);
- }
- NativeType::U64 => {
- ffi_args.push(ffi_parse_u64_arg(scope, value)?);
- }
- NativeType::I64 => {
- ffi_args.push(ffi_parse_i64_arg(scope, value)?);
- }
- NativeType::USize => {
- ffi_args.push(ffi_parse_usize_arg(scope, value)?);
- }
- NativeType::ISize => {
- ffi_args.push(ffi_parse_isize_arg(scope, value)?);
- }
- NativeType::F32 => {
- ffi_args.push(ffi_parse_f32_arg(value)?);
- }
- NativeType::F64 => {
- ffi_args.push(ffi_parse_f64_arg(value)?);
- }
- NativeType::Buffer => {
- ffi_args.push(ffi_parse_buffer_arg(scope, value)?);
- }
- NativeType::Pointer => {
- ffi_args.push(ffi_parse_pointer_arg(scope, value)?);
- }
- NativeType::Function => {
- ffi_args.push(ffi_parse_function_arg(scope, value)?);
- }
- NativeType::Void => {
- unreachable!();
- }
- }
- }
-
- Ok(ffi_args)
-}
-
-// A one-off synchronous FFI call.
-fn ffi_call_sync<'scope>(
- scope: &mut v8::HandleScope<'scope>,
- args: v8::FunctionCallbackArguments,
- symbol: &Symbol,
-) -> Result<NativeValue, AnyError>
-where
- 'scope: 'scope,
-{
- let Symbol {
- parameter_types,
- result_type,
- cif,
- ptr: fun_ptr,
- ..
- } = symbol;
- let mut ffi_args: Vec<NativeValue> =
- Vec::with_capacity(parameter_types.len());
-
- for (index, native_type) in parameter_types.iter().enumerate() {
- let value = args.get(index as i32);
- match native_type {
- NativeType::Bool => {
- ffi_args.push(ffi_parse_bool_arg(value)?);
- }
- NativeType::U8 => {
- ffi_args.push(ffi_parse_u8_arg(value)?);
- }
- NativeType::I8 => {
- ffi_args.push(ffi_parse_i8_arg(value)?);
- }
- NativeType::U16 => {
- ffi_args.push(ffi_parse_u16_arg(value)?);
- }
- NativeType::I16 => {
- ffi_args.push(ffi_parse_i16_arg(value)?);
- }
- NativeType::U32 => {
- ffi_args.push(ffi_parse_u32_arg(value)?);
- }
- NativeType::I32 => {
- ffi_args.push(ffi_parse_i32_arg(value)?);
- }
- NativeType::U64 => {
- ffi_args.push(ffi_parse_u64_arg(scope, value)?);
- }
- NativeType::I64 => {
- ffi_args.push(ffi_parse_i64_arg(scope, value)?);
- }
- NativeType::USize => {
- ffi_args.push(ffi_parse_usize_arg(scope, value)?);
- }
- NativeType::ISize => {
- ffi_args.push(ffi_parse_isize_arg(scope, value)?);
- }
- NativeType::F32 => {
- ffi_args.push(ffi_parse_f32_arg(value)?);
- }
- NativeType::F64 => {
- ffi_args.push(ffi_parse_f64_arg(value)?);
- }
- NativeType::Buffer => {
- ffi_args.push(ffi_parse_buffer_arg(scope, value)?);
- }
- NativeType::Pointer => {
- ffi_args.push(ffi_parse_pointer_arg(scope, value)?);
- }
- NativeType::Function => {
- ffi_args.push(ffi_parse_function_arg(scope, value)?);
- }
- NativeType::Void => {
- unreachable!();
- }
- }
- }
- let call_args: Vec<Arg> = ffi_args.iter().map(Arg::new).collect();
- // SAFETY: types in the `Cif` match the actual calling convention and
- // types of symbol.
- unsafe {
- Ok(match result_type {
- NativeType::Void => NativeValue {
- void_value: cif.call::<()>(*fun_ptr, &call_args),
- },
- NativeType::Bool => NativeValue {
- bool_value: cif.call::<bool>(*fun_ptr, &call_args),
- },
- NativeType::U8 => NativeValue {
- u8_value: cif.call::<u8>(*fun_ptr, &call_args),
- },
- NativeType::I8 => NativeValue {
- i8_value: cif.call::<i8>(*fun_ptr, &call_args),
- },
- NativeType::U16 => NativeValue {
- u16_value: cif.call::<u16>(*fun_ptr, &call_args),
- },
- NativeType::I16 => NativeValue {
- i16_value: cif.call::<i16>(*fun_ptr, &call_args),
- },
- NativeType::U32 => NativeValue {
- u32_value: cif.call::<u32>(*fun_ptr, &call_args),
- },
- NativeType::I32 => NativeValue {
- i32_value: cif.call::<i32>(*fun_ptr, &call_args),
- },
- NativeType::U64 => NativeValue {
- u64_value: cif.call::<u64>(*fun_ptr, &call_args),
- },
- NativeType::I64 => NativeValue {
- i64_value: cif.call::<i64>(*fun_ptr, &call_args),
- },
- NativeType::USize => NativeValue {
- usize_value: cif.call::<usize>(*fun_ptr, &call_args),
- },
- NativeType::ISize => NativeValue {
- isize_value: cif.call::<isize>(*fun_ptr, &call_args),
- },
- NativeType::F32 => NativeValue {
- f32_value: cif.call::<f32>(*fun_ptr, &call_args),
- },
- NativeType::F64 => NativeValue {
- f64_value: cif.call::<f64>(*fun_ptr, &call_args),
- },
- NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
- NativeValue {
- pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args),
- }
- }
- })
- }
-}
-
-fn ffi_call(
- call_args: Vec<NativeValue>,
- cif: &libffi::middle::Cif,
- fun_ptr: libffi::middle::CodePtr,
- parameter_types: &[NativeType],
- result_type: NativeType,
-) -> Result<NativeValue, AnyError> {
- let call_args: Vec<Arg> = call_args
- .iter()
- .enumerate()
- .map(|(index, ffi_arg)| {
- // SAFETY: the union field is initialized
- unsafe { ffi_arg.as_arg(*parameter_types.get(index).unwrap()) }
- })
- .collect();
-
- // SAFETY: types in the `Cif` match the actual calling convention and
- // types of symbol.
- unsafe {
- Ok(match result_type {
- NativeType::Void => NativeValue {
- void_value: cif.call::<()>(fun_ptr, &call_args),
- },
- NativeType::Bool => NativeValue {
- bool_value: cif.call::<bool>(fun_ptr, &call_args),
- },
- NativeType::U8 => NativeValue {
- u8_value: cif.call::<u8>(fun_ptr, &call_args),
- },
- NativeType::I8 => NativeValue {
- i8_value: cif.call::<i8>(fun_ptr, &call_args),
- },
- NativeType::U16 => NativeValue {
- u16_value: cif.call::<u16>(fun_ptr, &call_args),
- },
- NativeType::I16 => NativeValue {
- i16_value: cif.call::<i16>(fun_ptr, &call_args),
- },
- NativeType::U32 => NativeValue {
- u32_value: cif.call::<u32>(fun_ptr, &call_args),
- },
- NativeType::I32 => NativeValue {
- i32_value: cif.call::<i32>(fun_ptr, &call_args),
- },
- NativeType::U64 => NativeValue {
- u64_value: cif.call::<u64>(fun_ptr, &call_args),
- },
- NativeType::I64 => NativeValue {
- i64_value: cif.call::<i64>(fun_ptr, &call_args),
- },
- NativeType::USize => NativeValue {
- usize_value: cif.call::<usize>(fun_ptr, &call_args),
- },
- NativeType::ISize => NativeValue {
- isize_value: cif.call::<isize>(fun_ptr, &call_args),
- },
- NativeType::F32 => NativeValue {
- f32_value: cif.call::<f32>(fun_ptr, &call_args),
- },
- NativeType::F64 => NativeValue {
- f64_value: cif.call::<f64>(fun_ptr, &call_args),
- },
- NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
- NativeValue {
- pointer: cif.call::<*mut c_void>(fun_ptr, &call_args),
- }
- }
- })
- }
-}
-
-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>,
-}
-
-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(), &params);
- 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!();
- }
- };
-}
-
-#[derive(Deserialize)]
-struct RegisterCallbackArgs {
- parameters: Vec<NativeType>,
- result: NativeType,
-}
-
-#[op(v8)]
-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())
-}
-
-#[op(v8)]
-fn op_ffi_call_ptr<FP, 'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: Rc<RefCell<deno_core::OpState>>,
- pointer: usize,
- def: ForeignFunction,
- parameters: serde_v8::Value<'scope>,
-) -> Result<serde_v8::Value<'scope>, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable2(&state, "Deno.UnsafeFnPointer#call");
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
- };
-
- let symbol = PtrSymbol::new(pointer, &def);
- let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
-
- let result = ffi_call(
- call_args,
- &symbol.cif,
- symbol.ptr,
- &def.parameters,
- def.result,
- )?;
- // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
- let result = unsafe { result.to_v8(scope, def.result) };
- Ok(result)
-}
-
-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
- }
-}
-
-#[op]
-fn op_ffi_unsafe_callback_ref(
- state: Rc<RefCell<deno_core::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)]
-fn op_ffi_unsafe_callback_unref(
- state: &mut deno_core::OpState,
- rid: u32,
-) -> Result<(), AnyError> {
- state
- .resource_table
- .get::<UnsafeCallbackResource>(rid)?
- .cancel
- .cancel();
- Ok(())
-}
-
-#[op(v8)]
-fn op_ffi_call_ptr_nonblocking<'scope, FP>(
- scope: &mut v8::HandleScope<'scope>,
- state: Rc<RefCell<deno_core::OpState>>,
- pointer: usize,
- def: ForeignFunction,
- parameters: serde_v8::Value<'scope>,
-) -> Result<impl Future<Output = Result<Value, AnyError>>, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable2(&state, "Deno.UnsafeFnPointer#call");
- {
- let mut state = state.borrow_mut();
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
- };
-
- let symbol = PtrSymbol::new(pointer, &def);
- let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
-
- let join_handle = tokio::task::spawn_blocking(move || {
- let PtrSymbol { cif, ptr } = symbol.clone();
- ffi_call(call_args, &cif, ptr, &def.parameters, def.result)
- });
-
- Ok(async move {
- let result = join_handle
- .await
- .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
- // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
- Ok(unsafe { result.to_value(def.result) })
- })
-}
-
-#[op(v8)]
-fn op_ffi_get_static<'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: &mut deno_core::OpState,
- rid: ResourceId,
- name: String,
- static_type: NativeType,
-) -> Result<serde_v8::Value<'scope>, AnyError> {
- let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
-
- let data_ptr = resource.get_static(name)?;
-
- Ok(match static_type {
- NativeType::Void => {
- return Err(type_error("Invalid FFI static type 'void'"));
- }
- NativeType::Bool => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const bool) };
- let boolean: v8::Local<v8::Value> =
- v8::Boolean::new(scope, result).into();
- boolean.into()
- }
- NativeType::U8 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const u8) };
- let number: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, result as u32).into();
- number.into()
- }
- NativeType::I8 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const i8) };
- let number: v8::Local<v8::Value> =
- v8::Integer::new(scope, result as i32).into();
- number.into()
- }
- NativeType::U16 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const u16) };
- let number: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, result as u32).into();
- number.into()
- }
- NativeType::I16 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const i16) };
- let number: v8::Local<v8::Value> =
- v8::Integer::new(scope, result as i32).into();
- number.into()
- }
- NativeType::U32 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const u32) };
- let number: v8::Local<v8::Value> =
- v8::Integer::new_from_unsigned(scope, result).into();
- number.into()
- }
- NativeType::I32 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const i32) };
- let number: v8::Local<v8::Value> = v8::Integer::new(scope, result).into();
- number.into()
- }
- NativeType::U64 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const u64) };
- let integer: v8::Local<v8::Value> = if result > MAX_SAFE_INTEGER as u64 {
- v8::BigInt::new_from_u64(scope, result).into()
- } else {
- v8::Number::new(scope, result as f64).into()
- };
- integer.into()
- }
- NativeType::I64 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const i64) };
- let integer: v8::Local<v8::Value> = 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()
- };
- integer.into()
- }
- NativeType::USize => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const usize) };
- let integer: v8::Local<v8::Value> = 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()
- };
- integer.into()
- }
- NativeType::ISize => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const isize) };
- let integer: v8::Local<v8::Value> =
- if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&result) {
- v8::BigInt::new_from_i64(scope, result as i64).into()
- } else {
- v8::Number::new(scope, result as f64).into()
- };
- integer.into()
- }
- NativeType::F32 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const f32) };
- let number: v8::Local<v8::Value> =
- v8::Number::new(scope, result as f64).into();
- number.into()
- }
- NativeType::F64 => {
- // SAFETY: ptr is user provided
- let result = unsafe { ptr::read_unaligned(data_ptr as *const f64) };
- let number: v8::Local<v8::Value> = v8::Number::new(scope, result).into();
- number.into()
- }
- NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
- let result = data_ptr as u64;
- let integer: v8::Local<v8::Value> = if result > MAX_SAFE_INTEGER as u64 {
- v8::BigInt::new_from_u64(scope, result).into()
- } else {
- v8::Number::new(scope, result as f64).into()
- };
- integer.into()
- }
- })
-}
-
-/// A non-blocking FFI call.
-#[op(v8)]
-fn op_ffi_call_nonblocking<'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: Rc<RefCell<deno_core::OpState>>,
- rid: ResourceId,
- symbol: String,
- parameters: serde_v8::Value<'scope>,
-) -> Result<impl Future<Output = Result<Value, AnyError>> + 'static, AnyError> {
- let symbol = {
- let state = state.borrow();
- let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
- let symbols = &resource.symbols;
- *symbols
- .get(&symbol)
- .ok_or_else(|| type_error("Invalid FFI symbol name"))?
- .clone()
- };
-
- let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
-
- let result_type = symbol.result_type;
- let join_handle = tokio::task::spawn_blocking(move || {
- let Symbol {
- cif,
- ptr,
- parameter_types,
- result_type,
- ..
- } = symbol.clone();
- ffi_call(call_args, &cif, ptr, &parameter_types, result_type)
- });
-
- Ok(async move {
- let result = join_handle
- .await
- .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
- // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
- Ok(unsafe { result.to_value(result_type) })
- })
-}
-
-#[op(fast)]
-fn op_ffi_ptr_of<FP>(
- state: &mut deno_core::OpState,
- buf: *const u8,
- out: &mut [u32],
-) -> Result<(), AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointer#of");
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let outptr = out.as_ptr() as *mut usize;
- let length = out.len();
- assert!(
- length >= (std::mem::size_of::<usize>() / std::mem::size_of::<u32>())
- );
- assert_eq!(outptr as usize % std::mem::size_of::<usize>(), 0);
-
- // SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned.
- let out = unsafe { &mut *outptr };
- *out = buf as usize;
-
- Ok(())
-}
-
-unsafe extern "C" fn noop_deleter_callback(
- _data: *mut c_void,
- _byte_length: usize,
- _deleter_data: *mut c_void,
-) {
-}
-
-#[op(v8)]
-fn op_ffi_get_buf<FP, 'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
- len: usize,
-) -> Result<serde_v8::Value<'scope>, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#arrayBuffer");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *mut c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid FFI pointer value, got nullptr"));
- }
-
- // SAFETY: Offset is user defined.
- let ptr = unsafe { ptr.add(offset) };
-
- // SAFETY: Trust the user to have provided a real pointer, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion.
- let backing_store = unsafe {
- v8::ArrayBuffer::new_backing_store_from_ptr(
- ptr,
- len,
- noop_deleter_callback,
- std::ptr::null_mut(),
- )
- }
- .make_shared();
- let array_buffer: v8::Local<v8::Value> =
- v8::ArrayBuffer::with_backing_store(scope, &backing_store).into();
- Ok(array_buffer.into())
-}
-
-#[op(fast)]
-fn op_ffi_buf_copy_into<FP>(
- state: &mut deno_core::OpState,
- src: usize,
- offset: usize,
- dst: &mut [u8],
- len: usize,
-) -> Result<(), AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#copyInto");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- if dst.len() < len {
- Err(range_error(
- "Destination length is smaller than source length",
- ))
- } else {
- let src = src as *const c_void;
-
- // SAFETY: Offset is user defined.
- let src = unsafe { src.add(offset) as *const u8 };
-
- // SAFETY: src is user defined.
- // dest is properly aligned and is valid for writes of len * size_of::<T>() bytes.
- unsafe { ptr::copy::<u8>(src, dst.as_mut_ptr(), len) };
- Ok(())
- }
-}
-
-#[op(v8)]
-fn op_ffi_cstr_read<FP, 'scope>(
- scope: &mut v8::HandleScope<'scope>,
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<serde_v8::Value<'scope>, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getCString");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid CString pointer, pointer is null"));
- }
-
- // SAFETY: Offset is user defined.
- let ptr = unsafe { ptr.add(offset) };
-
- // SAFETY: Pointer is user provided.
- let cstr = unsafe { CStr::from_ptr(ptr as *const c_char) }
- .to_str()
- .map_err(|_| type_error("Invalid CString pointer, not valid UTF-8"))?;
- let value: v8::Local<v8::Value> = v8::String::new(scope, cstr)
- .ok_or_else(|| {
- type_error("Invalid CString pointer, string exceeds max length")
- })?
- .into();
- Ok(value.into())
-}
-
-#[op(fast)]
-fn op_ffi_read_bool<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<bool, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getBool");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid bool pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe { ptr::read_unaligned::<bool>(ptr.add(offset) as *const bool) })
-}
-
-#[op(fast)]
-fn op_ffi_read_u8<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<u32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getUint8");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid u8 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe { ptr::read_unaligned::<u8>(ptr.add(offset) as *const u8) as u32 })
-}
-
-#[op(fast)]
-fn op_ffi_read_i8<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<i32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getInt8");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid i8 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe { ptr::read_unaligned::<i8>(ptr.add(offset) as *const i8) as i32 })
-}
-
-#[op(fast)]
-fn op_ffi_read_u16<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<u32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getUint16");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid u16 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe {
- ptr::read_unaligned::<u16>(ptr.add(offset) as *const u16) as u32
- })
-}
-
-#[op(fast)]
-fn op_ffi_read_i16<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<i32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getInt16");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid i16 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe {
- ptr::read_unaligned::<i16>(ptr.add(offset) as *const i16) as i32
- })
-}
-
-#[op(fast)]
-fn op_ffi_read_u32<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<u32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getUint32");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid u32 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe {
- ptr::read_unaligned::<u32>(ptr.add(offset) as *const u32) as u32
- })
-}
-
-#[op(fast)]
-fn op_ffi_read_i32<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<i32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getInt32");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid i32 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe {
- ptr::read_unaligned::<i32>(ptr.add(offset) as *const i32) as i32
- })
-}
-
-#[op]
-fn op_ffi_read_u64<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
- out: &mut [u32],
-) -> Result<(), AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let outptr = out.as_mut_ptr() as *mut u64;
-
- assert!(
- out.len() >= (std::mem::size_of::<u64>() / std::mem::size_of::<u32>())
- );
- assert_eq!((outptr as usize % std::mem::size_of::<u64>()), 0);
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid u64 pointer, pointer is null"));
- }
-
- let value =
- // SAFETY: ptr and offset are user provided.
- unsafe { ptr::read_unaligned::<u64>(ptr.add(offset) as *const u64) };
-
- // SAFETY: Length and alignment of out slice were asserted to be correct.
- unsafe { *outptr = value };
- Ok(())
-}
-
-#[op(fast)]
-fn op_ffi_read_i64<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
- out: &mut [u32],
-) -> Result<(), AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let outptr = out.as_mut_ptr() as *mut i64;
-
- assert!(
- out.len() >= (std::mem::size_of::<i64>() / std::mem::size_of::<u32>())
- );
- assert_eq!((outptr as usize % std::mem::size_of::<i64>()), 0);
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid i64 pointer, pointer is null"));
- }
-
- let value =
- // SAFETY: ptr and offset are user provided.
- unsafe { ptr::read_unaligned::<i64>(ptr.add(offset) as *const i64) };
- // SAFETY: Length and alignment of out slice were asserted to be correct.
- unsafe { *outptr = value };
- Ok(())
-}
-
-#[op(fast)]
-fn op_ffi_read_f32<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<f32, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getFloat32");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid f32 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe { ptr::read_unaligned::<f32>(ptr.add(offset) as *const f32) })
-}
-
-#[op(fast)]
-fn op_ffi_read_f64<FP>(
- state: &mut deno_core::OpState,
- ptr: usize,
- offset: usize,
-) -> Result<f64, AnyError>
-where
- FP: FfiPermissions + 'static,
-{
- check_unstable(state, "Deno.UnsafePointerView#getFloat64");
-
- let permissions = state.borrow_mut::<FP>();
- permissions.check(None)?;
-
- let ptr = ptr as *const c_void;
-
- if ptr.is_null() {
- return Err(type_error("Invalid f64 pointer, pointer is null"));
- }
-
- // SAFETY: ptr and offset are user provided.
- Ok(unsafe { ptr::read_unaligned::<f64>(ptr.add(offset) as *const f64) })
-}
-
-#[cfg(test)]
-mod tests {
- #[cfg(target_os = "windows")]
- #[test]
- fn test_format_error() {
- use super::format_error;
-
- // BAD_EXE_FORMAT
- let err = dlopen::Error::OpeningLibraryError(
- std::io::Error::from_raw_os_error(0x000000C1),
- );
- assert_eq!(
- format_error(err, "foo.dll".to_string()),
- "foo.dll is not a valid Win32 application.\r\n".to_string(),
- );
- }
-}
diff --git a/ext/ffi/repr.rs b/ext/ffi/repr.rs
new file mode 100644
index 000000000..22cf03a6b
--- /dev/null
+++ b/ext/ffi/repr.rs
@@ -0,0 +1,454 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::check_unstable;
+use crate::FfiPermissions;
+use deno_core::error::range_error;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::serde_v8;
+use deno_core::v8;
+use std::ffi::c_char;
+use std::ffi::c_void;
+use std::ffi::CStr;
+use std::ptr;
+
+#[op(fast)]
+pub fn op_ffi_ptr_of<FP>(
+ state: &mut deno_core::OpState,
+ buf: *const u8,
+ out: &mut [u32],
+) -> Result<(), AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointer#of");
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let outptr = out.as_ptr() as *mut usize;
+ let length = out.len();
+ assert!(
+ length >= (std::mem::size_of::<usize>() / std::mem::size_of::<u32>())
+ );
+ assert_eq!(outptr as usize % std::mem::size_of::<usize>(), 0);
+
+ // SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned.
+ let out = unsafe { &mut *outptr };
+ *out = buf as usize;
+
+ Ok(())
+}
+
+unsafe extern "C" fn noop_deleter_callback(
+ _data: *mut c_void,
+ _byte_length: usize,
+ _deleter_data: *mut c_void,
+) {
+}
+
+#[op(v8)]
+pub fn op_ffi_get_buf<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+ len: usize,
+) -> Result<serde_v8::Value<'scope>, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#arrayBuffer");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *mut c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid FFI pointer value, got nullptr"));
+ }
+
+ // SAFETY: Offset is user defined.
+ let ptr = unsafe { ptr.add(offset) };
+
+ // SAFETY: Trust the user to have provided a real pointer, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion.
+ let backing_store = unsafe {
+ v8::ArrayBuffer::new_backing_store_from_ptr(
+ ptr,
+ len,
+ noop_deleter_callback,
+ std::ptr::null_mut(),
+ )
+ }
+ .make_shared();
+ let array_buffer: v8::Local<v8::Value> =
+ v8::ArrayBuffer::with_backing_store(scope, &backing_store).into();
+ Ok(array_buffer.into())
+}
+
+#[op(fast)]
+pub fn op_ffi_buf_copy_into<FP>(
+ state: &mut deno_core::OpState,
+ src: usize,
+ offset: usize,
+ dst: &mut [u8],
+ len: usize,
+) -> Result<(), AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#copyInto");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ if dst.len() < len {
+ Err(range_error(
+ "Destination length is smaller than source length",
+ ))
+ } else {
+ let src = src as *const c_void;
+
+ // SAFETY: Offset is user defined.
+ let src = unsafe { src.add(offset) as *const u8 };
+
+ // SAFETY: src is user defined.
+ // dest is properly aligned and is valid for writes of len * size_of::<T>() bytes.
+ unsafe { ptr::copy::<u8>(src, dst.as_mut_ptr(), len) };
+ Ok(())
+ }
+}
+
+#[op(v8)]
+pub fn op_ffi_cstr_read<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<serde_v8::Value<'scope>, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getCString");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid CString pointer, pointer is null"));
+ }
+
+ // SAFETY: Offset is user defined.
+ let ptr = unsafe { ptr.add(offset) };
+
+ // SAFETY: Pointer is user provided.
+ let cstr = unsafe { CStr::from_ptr(ptr as *const c_char) }
+ .to_str()
+ .map_err(|_| type_error("Invalid CString pointer, not valid UTF-8"))?;
+ let value: v8::Local<v8::Value> = v8::String::new(scope, cstr)
+ .ok_or_else(|| {
+ type_error("Invalid CString pointer, string exceeds max length")
+ })?
+ .into();
+ Ok(value.into())
+}
+
+#[op(fast)]
+pub fn op_ffi_read_bool<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<bool, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getBool");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid bool pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe { ptr::read_unaligned::<bool>(ptr.add(offset) as *const bool) })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_u8<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<u32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getUint8");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid u8 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe { ptr::read_unaligned::<u8>(ptr.add(offset) as *const u8) as u32 })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_i8<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<i32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getInt8");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid i8 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe { ptr::read_unaligned::<i8>(ptr.add(offset) as *const i8) as i32 })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_u16<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<u32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getUint16");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid u16 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe {
+ ptr::read_unaligned::<u16>(ptr.add(offset) as *const u16) as u32
+ })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_i16<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<i32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getInt16");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid i16 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe {
+ ptr::read_unaligned::<i16>(ptr.add(offset) as *const i16) as i32
+ })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_u32<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<u32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getUint32");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid u32 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe {
+ ptr::read_unaligned::<u32>(ptr.add(offset) as *const u32) as u32
+ })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_i32<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<i32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getInt32");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid i32 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe {
+ ptr::read_unaligned::<i32>(ptr.add(offset) as *const i32) as i32
+ })
+}
+
+#[op]
+pub fn op_ffi_read_u64<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+ out: &mut [u32],
+) -> Result<(), AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let outptr = out.as_mut_ptr() as *mut u64;
+
+ assert!(
+ out.len() >= (std::mem::size_of::<u64>() / std::mem::size_of::<u32>())
+ );
+ assert_eq!((outptr as usize % std::mem::size_of::<u64>()), 0);
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid u64 pointer, pointer is null"));
+ }
+
+ let value =
+ // SAFETY: ptr and offset are user provided.
+ unsafe { ptr::read_unaligned::<u64>(ptr.add(offset) as *const u64) };
+
+ // SAFETY: Length and alignment of out slice were asserted to be correct.
+ unsafe { *outptr = value };
+ Ok(())
+}
+
+#[op(fast)]
+pub fn op_ffi_read_i64<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+ out: &mut [u32],
+) -> Result<(), AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let outptr = out.as_mut_ptr() as *mut i64;
+
+ assert!(
+ out.len() >= (std::mem::size_of::<i64>() / std::mem::size_of::<u32>())
+ );
+ assert_eq!((outptr as usize % std::mem::size_of::<i64>()), 0);
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid i64 pointer, pointer is null"));
+ }
+
+ let value =
+ // SAFETY: ptr and offset are user provided.
+ unsafe { ptr::read_unaligned::<i64>(ptr.add(offset) as *const i64) };
+ // SAFETY: Length and alignment of out slice were asserted to be correct.
+ unsafe { *outptr = value };
+ Ok(())
+}
+
+#[op(fast)]
+pub fn op_ffi_read_f32<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<f32, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getFloat32");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid f32 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe { ptr::read_unaligned::<f32>(ptr.add(offset) as *const f32) })
+}
+
+#[op(fast)]
+pub fn op_ffi_read_f64<FP>(
+ state: &mut deno_core::OpState,
+ ptr: usize,
+ offset: usize,
+) -> Result<f64, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.UnsafePointerView#getFloat64");
+
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(None)?;
+
+ let ptr = ptr as *const c_void;
+
+ if ptr.is_null() {
+ return Err(type_error("Invalid f64 pointer, pointer is null"));
+ }
+
+ // SAFETY: ptr and offset are user provided.
+ Ok(unsafe { ptr::read_unaligned::<f64>(ptr.add(offset) as *const f64) })
+}
diff --git a/ext/ffi/static.rs b/ext/ffi/static.rs
new file mode 100644
index 000000000..aa0bb325c
--- /dev/null
+++ b/ext/ffi/static.rs
@@ -0,0 +1,146 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::dlfcn::DynamicLibraryResource;
+use crate::symbol::NativeType;
+use crate::MAX_SAFE_INTEGER;
+use crate::MIN_SAFE_INTEGER;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::serde_v8;
+use deno_core::v8;
+use deno_core::ResourceId;
+use std::ptr;
+
+#[op(v8)]
+pub fn op_ffi_get_static<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: &mut deno_core::OpState,
+ rid: ResourceId,
+ name: String,
+ static_type: NativeType,
+) -> Result<serde_v8::Value<'scope>, AnyError> {
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
+
+ let data_ptr = resource.get_static(name)?;
+
+ Ok(match static_type {
+ NativeType::Void => {
+ return Err(type_error("Invalid FFI static type 'void'"));
+ }
+ NativeType::Bool => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const bool) };
+ let boolean: v8::Local<v8::Value> =
+ v8::Boolean::new(scope, result).into();
+ boolean.into()
+ }
+ NativeType::U8 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u8) };
+ let number: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, result as u32).into();
+ number.into()
+ }
+ NativeType::I8 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i8) };
+ let number: v8::Local<v8::Value> =
+ v8::Integer::new(scope, result as i32).into();
+ number.into()
+ }
+ NativeType::U16 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u16) };
+ let number: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, result as u32).into();
+ number.into()
+ }
+ NativeType::I16 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i16) };
+ let number: v8::Local<v8::Value> =
+ v8::Integer::new(scope, result as i32).into();
+ number.into()
+ }
+ NativeType::U32 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u32) };
+ let number: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, result).into();
+ number.into()
+ }
+ NativeType::I32 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i32) };
+ let number: v8::Local<v8::Value> = v8::Integer::new(scope, result).into();
+ number.into()
+ }
+ NativeType::U64 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u64) };
+ let integer: v8::Local<v8::Value> = if result > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, result).into()
+ } else {
+ v8::Number::new(scope, result as f64).into()
+ };
+ integer.into()
+ }
+ NativeType::I64 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i64) };
+ let integer: v8::Local<v8::Value> = 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()
+ };
+ integer.into()
+ }
+ NativeType::USize => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const usize) };
+ let integer: v8::Local<v8::Value> = 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()
+ };
+ integer.into()
+ }
+ NativeType::ISize => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const isize) };
+ let integer: v8::Local<v8::Value> =
+ if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&result) {
+ v8::BigInt::new_from_i64(scope, result as i64).into()
+ } else {
+ v8::Number::new(scope, result as f64).into()
+ };
+ integer.into()
+ }
+ NativeType::F32 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f32) };
+ let number: v8::Local<v8::Value> =
+ v8::Number::new(scope, result as f64).into();
+ number.into()
+ }
+ NativeType::F64 => {
+ // SAFETY: ptr is user provided
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f64) };
+ let number: v8::Local<v8::Value> = v8::Number::new(scope, result).into();
+ number.into()
+ }
+ NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
+ let result = data_ptr as u64;
+ let integer: v8::Local<v8::Value> = if result > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, result).into()
+ } else {
+ v8::Number::new(scope, result as f64).into()
+ };
+ integer.into()
+ }
+ })
+}
diff --git a/ext/ffi/symbol.rs b/ext/ffi/symbol.rs
new file mode 100644
index 000000000..0248c1fff
--- /dev/null
+++ b/ext/ffi/symbol.rs
@@ -0,0 +1,63 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+/// Defines the accepted types that can be used as
+/// parameters and return values in FFI.
+#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum NativeType {
+ Void,
+ Bool,
+ U8,
+ I8,
+ U16,
+ I16,
+ U32,
+ I32,
+ U64,
+ I64,
+ USize,
+ ISize,
+ F32,
+ F64,
+ Pointer,
+ Buffer,
+ Function,
+}
+
+impl From<NativeType> for libffi::middle::Type {
+ fn from(native_type: NativeType) -> Self {
+ match native_type {
+ NativeType::Void => libffi::middle::Type::void(),
+ NativeType::U8 | NativeType::Bool => libffi::middle::Type::u8(),
+ NativeType::I8 => libffi::middle::Type::i8(),
+ NativeType::U16 => libffi::middle::Type::u16(),
+ NativeType::I16 => libffi::middle::Type::i16(),
+ NativeType::U32 => libffi::middle::Type::u32(),
+ NativeType::I32 => libffi::middle::Type::i32(),
+ NativeType::U64 => libffi::middle::Type::u64(),
+ NativeType::I64 => libffi::middle::Type::i64(),
+ NativeType::USize => libffi::middle::Type::usize(),
+ NativeType::ISize => libffi::middle::Type::isize(),
+ NativeType::F32 => libffi::middle::Type::f32(),
+ NativeType::F64 => libffi::middle::Type::f64(),
+ NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
+ libffi::middle::Type::pointer()
+ }
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Symbol {
+ pub cif: libffi::middle::Cif,
+ pub ptr: libffi::middle::CodePtr,
+ pub parameter_types: Vec<NativeType>,
+ pub result_type: NativeType,
+ pub can_callback: bool,
+}
+
+#[allow(clippy::non_send_fields_in_send_ty)]
+// SAFETY: unsafe trait must have unsafe implementation
+unsafe impl Send for Symbol {}
+// SAFETY: unsafe trait must have unsafe implementation
+unsafe impl Sync for Symbol {}
diff --git a/ext/ffi/fast_call.rs b/ext/ffi/turbocall.rs
index dc098a69a..79ec814b4 100644
--- a/ext/ffi/fast_call.rs
+++ b/ext/ffi/turbocall.rs
@@ -9,7 +9,7 @@ use dynasmrt::dynasm;
use dynasmrt::DynasmApi;
use dynasmrt::ExecutableBuffer;
-use crate::needs_unwrap;
+use crate::dlfcn::needs_unwrap;
use crate::NativeType;
use crate::Symbol;
@@ -562,7 +562,7 @@ impl SysVAmd64 {
fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool {
// V8 only supports i32 and u32 return types for integers
// We support 64 bit integers by wrapping them in a TypedArray out parameter
- crate::needs_unwrap(rv)
+ crate::dlfcn::needs_unwrap(rv)
}
fn finalize(self) -> ExecutableBuffer {
@@ -1073,7 +1073,7 @@ impl Aarch64Apple {
fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool {
// V8 only supports i32 and u32 return types for integers
// We support 64 bit integers by wrapping them in a TypedArray out parameter
- crate::needs_unwrap(rv)
+ crate::dlfcn::needs_unwrap(rv)
}
fn finalize(self) -> ExecutableBuffer {
@@ -1429,7 +1429,7 @@ impl Win64 {
fn must_wrap_return_value_in_typed_array(&self, rv: NativeType) -> bool {
// V8 only supports i32 and u32 return types for integers
// We support 64 bit integers by wrapping them in a TypedArray out parameter
- crate::needs_unwrap(rv)
+ crate::dlfcn::needs_unwrap(rv)
}
fn finalize(self) -> ExecutableBuffer {