mirror of
https://github.com/denoland/deno.git
synced 2025-03-04 09:57:11 -05:00

Now that ArrayBuffer/ArrayBufferView is a generic Value type, we have to handle it being passed any value. To do this, thread FastApiCallbackOptions through the function, and add error raising logic. If we run conversion and the value is not valid, we return `isize::MAX`, and then in cranelift we use this value to know that we should branch to the error logic. An example compilation looks like this: ```rust extern "C" fn print_buffer(ptr: *const u8, len: usize); ``` ```clif function %print_buffer_wrapper(i64, i64, i64, i64) system_v { sig0 = (i64, i64) system_v sig1 = (i64) -> i64 system_v sig2 = (i64) system_v block0(v0: i64, v1: i64, v2: i64, v3: i64): v4 = iconst.i64 0x6525_9198_2d00 ; turbocall_ab_contents v5 = call_indirect sig1, v4(v1) v6 = iconst.i64 0x7fff_ffff_ffff_ffff v7 = icmp eq v5, v6 brif v7, block1, block2 block2: v8 = iconst.i64 0x7558_4c0c_0700 ; sym.ptr call_indirect sig0, v8(v5, v2) return block1 cold: v9 = iconst.i64 0x6525_9198_2d70 ; turbocall_raise call_indirect sig2, v9(v3) return } ``` Also cleaned up all the `unwrap`s and added some logging.
427 lines
12 KiB
Rust
427 lines
12 KiB
Rust
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
|
|
use std::cell::RefCell;
|
|
use std::ffi::c_void;
|
|
use std::future::Future;
|
|
use std::rc::Rc;
|
|
|
|
use deno_core::op2;
|
|
use deno_core::serde_json::Value;
|
|
use deno_core::serde_v8::BigInt as V8BigInt;
|
|
use deno_core::serde_v8::ExternalPointer;
|
|
use deno_core::unsync::spawn_blocking;
|
|
use deno_core::v8;
|
|
use deno_core::OpState;
|
|
use deno_core::ResourceId;
|
|
use libffi::middle::Arg;
|
|
use num_bigint::BigInt;
|
|
use serde::Serialize;
|
|
|
|
use crate::callback::PtrSymbol;
|
|
use crate::dlfcn::DynamicLibraryResource;
|
|
use crate::ir::*;
|
|
use crate::symbol::NativeType;
|
|
use crate::symbol::Symbol;
|
|
use crate::FfiPermissions;
|
|
use crate::ForeignFunction;
|
|
|
|
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
|
pub enum CallError {
|
|
#[class(type)]
|
|
#[error(transparent)]
|
|
IR(#[from] IRError),
|
|
#[class(generic)]
|
|
#[error("Nonblocking FFI call failed: {0}")]
|
|
NonblockingCallFailure(#[source] tokio::task::JoinError),
|
|
#[class(type)]
|
|
#[error("Invalid FFI symbol name: '{0}'")]
|
|
InvalidSymbol(String),
|
|
#[class(inherit)]
|
|
#[error(transparent)]
|
|
Permission(#[from] deno_permissions::PermissionCheckError),
|
|
#[class(inherit)]
|
|
#[error(transparent)]
|
|
Resource(#[from] deno_core::error::ResourceError),
|
|
#[class(inherit)]
|
|
#[error(transparent)]
|
|
Callback(#[from] super::CallbackError),
|
|
}
|
|
|
|
// SAFETY: Makes an FFI call
|
|
unsafe fn ffi_call_rtype_struct(
|
|
cif: &libffi::middle::Cif,
|
|
fn_ptr: &libffi::middle::CodePtr,
|
|
call_args: Vec<Arg>,
|
|
out_buffer: *mut u8,
|
|
) {
|
|
libffi::raw::ffi_call(
|
|
cif.as_raw_ptr(),
|
|
Some(*fn_ptr.as_safe_fun()),
|
|
out_buffer as *mut c_void,
|
|
call_args.as_ptr() as *mut *mut c_void,
|
|
);
|
|
}
|
|
|
|
// A one-off synchronous FFI call.
|
|
pub(crate) fn ffi_call_sync<'scope>(
|
|
scope: &mut v8::HandleScope<'scope>,
|
|
args: v8::FunctionCallbackArguments,
|
|
symbol: &Symbol,
|
|
out_buffer: Option<OutBuffer>,
|
|
) -> Result<NativeValue, CallError>
|
|
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(value)?);
|
|
}
|
|
NativeType::Struct(_) => {
|
|
ffi_args.push(ffi_parse_struct_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()
|
|
.enumerate()
|
|
// SAFETY: Creating a `Arg` from a `NativeValue` is pretty safe.
|
|
.map(|(i, v)| unsafe { v.as_arg(parameter_types.get(i).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),
|
|
}
|
|
}
|
|
NativeType::Struct(_) => NativeValue {
|
|
void_value: ffi_call_rtype_struct(
|
|
&symbol.cif,
|
|
&symbol.ptr,
|
|
call_args,
|
|
out_buffer.unwrap().0,
|
|
),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum FfiValue {
|
|
Value(Value),
|
|
BigInt(V8BigInt),
|
|
External(ExternalPointer),
|
|
}
|
|
|
|
fn ffi_call(
|
|
call_args: Vec<NativeValue>,
|
|
cif: &libffi::middle::Cif,
|
|
fun_ptr: libffi::middle::CodePtr,
|
|
parameter_types: &[NativeType],
|
|
result_type: NativeType,
|
|
out_buffer: Option<OutBuffer>,
|
|
) -> FfiValue {
|
|
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 {
|
|
match result_type {
|
|
NativeType::Void => {
|
|
cif.call::<()>(fun_ptr, &call_args);
|
|
FfiValue::Value(Value::from(()))
|
|
}
|
|
NativeType::Bool => {
|
|
FfiValue::Value(Value::from(cif.call::<bool>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U8 => {
|
|
FfiValue::Value(Value::from(cif.call::<u8>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I8 => {
|
|
FfiValue::Value(Value::from(cif.call::<i8>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U16 => {
|
|
FfiValue::Value(Value::from(cif.call::<u16>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I16 => {
|
|
FfiValue::Value(Value::from(cif.call::<i16>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U32 => {
|
|
FfiValue::Value(Value::from(cif.call::<u32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I32 => {
|
|
FfiValue::Value(Value::from(cif.call::<i32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U64 => FfiValue::BigInt(V8BigInt::from(BigInt::from(
|
|
cif.call::<u64>(fun_ptr, &call_args),
|
|
))),
|
|
NativeType::I64 => FfiValue::BigInt(V8BigInt::from(BigInt::from(
|
|
cif.call::<i64>(fun_ptr, &call_args),
|
|
))),
|
|
NativeType::USize => FfiValue::BigInt(V8BigInt::from(BigInt::from(
|
|
cif.call::<usize>(fun_ptr, &call_args),
|
|
))),
|
|
NativeType::ISize => FfiValue::BigInt(V8BigInt::from(BigInt::from(
|
|
cif.call::<isize>(fun_ptr, &call_args),
|
|
))),
|
|
NativeType::F32 => {
|
|
FfiValue::Value(Value::from(cif.call::<f32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::F64 => {
|
|
FfiValue::Value(Value::from(cif.call::<f64>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
|
|
FfiValue::External(ExternalPointer::from(
|
|
cif.call::<*mut c_void>(fun_ptr, &call_args),
|
|
))
|
|
}
|
|
NativeType::Struct(_) => {
|
|
ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0);
|
|
FfiValue::Value(Value::Null)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[op2(async, stack_trace)]
|
|
#[serde]
|
|
pub fn op_ffi_call_ptr_nonblocking<FP>(
|
|
scope: &mut v8::HandleScope,
|
|
state: Rc<RefCell<OpState>>,
|
|
pointer: *mut c_void,
|
|
#[serde] def: ForeignFunction,
|
|
parameters: v8::Local<v8::Array>,
|
|
out_buffer: Option<v8::Local<v8::TypedArray>>,
|
|
) -> Result<impl Future<Output = Result<FfiValue, CallError>>, CallError>
|
|
where
|
|
FP: FfiPermissions + 'static,
|
|
{
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<FP>();
|
|
permissions.check_partial_no_path()?;
|
|
};
|
|
|
|
let symbol = PtrSymbol::new(pointer, &def)?;
|
|
let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let join_handle = spawn_blocking(move || {
|
|
let PtrSymbol { cif, ptr } = symbol.clone();
|
|
ffi_call(
|
|
call_args,
|
|
&cif,
|
|
ptr,
|
|
&def.parameters,
|
|
def.result,
|
|
out_buffer_ptr,
|
|
)
|
|
});
|
|
|
|
Ok(async move {
|
|
let result = join_handle
|
|
.await
|
|
.map_err(CallError::NonblockingCallFailure)?;
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
})
|
|
}
|
|
|
|
/// A non-blocking FFI call.
|
|
#[op2(async)]
|
|
#[serde]
|
|
pub fn op_ffi_call_nonblocking(
|
|
scope: &mut v8::HandleScope,
|
|
state: Rc<RefCell<OpState>>,
|
|
#[smi] rid: ResourceId,
|
|
#[string] symbol: String,
|
|
parameters: v8::Local<v8::Array>,
|
|
out_buffer: Option<v8::Local<v8::TypedArray>>,
|
|
) -> Result<impl Future<Output = Result<FfiValue, CallError>>, CallError> {
|
|
let symbol = {
|
|
let state = state.borrow();
|
|
let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
|
|
let symbols = &resource.symbols;
|
|
*symbols
|
|
.get(&symbol)
|
|
.ok_or_else(|| CallError::InvalidSymbol(symbol))?
|
|
.clone()
|
|
};
|
|
|
|
let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let join_handle = spawn_blocking(move || {
|
|
let Symbol {
|
|
cif,
|
|
ptr,
|
|
parameter_types,
|
|
result_type,
|
|
..
|
|
} = symbol.clone();
|
|
ffi_call(
|
|
call_args,
|
|
&cif,
|
|
ptr,
|
|
¶meter_types,
|
|
result_type,
|
|
out_buffer_ptr,
|
|
)
|
|
});
|
|
|
|
Ok(async move {
|
|
let result = join_handle
|
|
.await
|
|
.map_err(CallError::NonblockingCallFailure)?;
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
})
|
|
}
|
|
|
|
#[op2(reentrant, stack_trace)]
|
|
#[serde]
|
|
pub fn op_ffi_call_ptr<FP>(
|
|
scope: &mut v8::HandleScope,
|
|
state: Rc<RefCell<OpState>>,
|
|
pointer: *mut c_void,
|
|
#[serde] def: ForeignFunction,
|
|
parameters: v8::Local<v8::Array>,
|
|
out_buffer: Option<v8::Local<v8::TypedArray>>,
|
|
) -> Result<FfiValue, CallError>
|
|
where
|
|
FP: FfiPermissions + 'static,
|
|
{
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<FP>();
|
|
permissions.check_partial_no_path()?;
|
|
};
|
|
|
|
let symbol = PtrSymbol::new(pointer, &def)?;
|
|
let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
|
|
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let result = ffi_call(
|
|
call_args,
|
|
&symbol.cif,
|
|
symbol.ptr,
|
|
&def.parameters,
|
|
def.result.clone(),
|
|
out_buffer_ptr,
|
|
);
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
}
|