diff options
Diffstat (limited to 'ext/ffi/call.rs')
-rw-r--r-- | ext/ffi/call.rs | 335 |
1 files changed, 335 insertions, 0 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, ¶meter_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) +} |