// Copyright 2018-2023 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; // SAFETY: Makes an FFI call unsafe fn ffi_call_rtype_struct( cif: &libffi::middle::Cif, fn_ptr: &libffi::middle::CodePtr, call_args: Vec, out_buffer: *mut u8, ) -> NativeValue { 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, ); NativeValue { void_value: () } } // 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, ) -> Result where 'scope: 'scope, { let Symbol { parameter_types, result_type, cif, ptr: fun_ptr, .. } = symbol; let mut ffi_args: Vec = 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::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 = 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::(*fun_ptr, &call_args), }, NativeType::U8 => NativeValue { u8_value: cif.call::(*fun_ptr, &call_args), }, NativeType::I8 => NativeValue { i8_value: cif.call::(*fun_ptr, &call_args), }, NativeType::U16 => NativeValue { u16_value: cif.call::(*fun_ptr, &call_args), }, NativeType::I16 => NativeValue { i16_value: cif.call::(*fun_ptr, &call_args), }, NativeType::U32 => NativeValue { u32_value: cif.call::(*fun_ptr, &call_args), }, NativeType::I32 => NativeValue { i32_value: cif.call::(*fun_ptr, &call_args), }, NativeType::U64 => NativeValue { u64_value: cif.call::(*fun_ptr, &call_args), }, NativeType::I64 => NativeValue { i64_value: cif.call::(*fun_ptr, &call_args), }, NativeType::USize => NativeValue { usize_value: cif.call::(*fun_ptr, &call_args), }, NativeType::ISize => NativeValue { isize_value: cif.call::(*fun_ptr, &call_args), }, NativeType::F32 => NativeValue { f32_value: cif.call::(*fun_ptr, &call_args), }, NativeType::F64 => NativeValue { f64_value: cif.call::(*fun_ptr, &call_args), }, NativeType::Pointer | NativeType::Function | NativeType::Buffer => { NativeValue { pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args), } } NativeType::Struct(_) => ffi_call_rtype_struct( &symbol.cif, &symbol.ptr, call_args, out_buffer.unwrap().0, ), }) } } fn ffi_call( call_args: Vec, cif: &libffi::middle::Cif, fun_ptr: libffi::middle::CodePtr, parameter_types: &[NativeType], result_type: NativeType, out_buffer: Option, ) -> Result { let call_args: Vec = 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::(fun_ptr, &call_args), }, NativeType::U8 => NativeValue { u8_value: cif.call::(fun_ptr, &call_args), }, NativeType::I8 => NativeValue { i8_value: cif.call::(fun_ptr, &call_args), }, NativeType::U16 => NativeValue { u16_value: cif.call::(fun_ptr, &call_args), }, NativeType::I16 => NativeValue { i16_value: cif.call::(fun_ptr, &call_args), }, NativeType::U32 => NativeValue { u32_value: cif.call::(fun_ptr, &call_args), }, NativeType::I32 => NativeValue { i32_value: cif.call::(fun_ptr, &call_args), }, NativeType::U64 => NativeValue { u64_value: cif.call::(fun_ptr, &call_args), }, NativeType::I64 => NativeValue { i64_value: cif.call::(fun_ptr, &call_args), }, NativeType::USize => NativeValue { usize_value: cif.call::(fun_ptr, &call_args), }, NativeType::ISize => NativeValue { isize_value: cif.call::(fun_ptr, &call_args), }, NativeType::F32 => NativeValue { f32_value: cif.call::(fun_ptr, &call_args), }, NativeType::F64 => NativeValue { f64_value: cif.call::(fun_ptr, &call_args), }, NativeType::Pointer | NativeType::Function | NativeType::Buffer => { NativeValue { pointer: cif.call::<*mut c_void>(fun_ptr, &call_args), } } NativeType::Struct(_) => { ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0) } }) } } #[op(v8)] pub fn op_ffi_call_ptr_nonblocking<'scope, FP>( scope: &mut v8::HandleScope<'scope>, state: Rc>, pointer: usize, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, ) -> Result>, AnyError> where FP: FfiPermissions + 'static, { check_unstable2(&state, "Deno.UnsafeFnPointer#call"); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.check(None)?; }; let symbol = PtrSymbol::new(pointer, &def); let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; let def_result = def.result.clone(); let out_buffer = out_buffer .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); let join_handle = tokio::task::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(|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>, rid: ResourceId, symbol: String, parameters: serde_v8::Value<'scope>, out_buffer: Option>, ) -> Result> + 'static, AnyError> { let symbol = { let state = state.borrow(); let resource = state.resource_table.get::(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 out_buffer = out_buffer .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); let result_type = symbol.result_type.clone(); 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, out_buffer_ptr, ) }); 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( scope: &mut v8::HandleScope<'scope>, state: Rc>, pointer: usize, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, ) -> Result, AnyError> where FP: FfiPermissions + 'static, { check_unstable2(&state, "Deno.UnsafeFnPointer#call"); { let mut state = state.borrow_mut(); let permissions = state.borrow_mut::(); permissions.check(None)?; }; let symbol = PtrSymbol::new(pointer, &def); let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; let out_buffer = out_buffer .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); 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. let result = unsafe { result.to_v8(scope, def.result) }; Ok(result) }