// Copyright 2021 the Deno authors. All rights reserved. MIT license. use deno_core::error::bad_resource_id; use deno_core::error::AnyError; use deno_core::include_js_files; use deno_core::op_sync; use deno_core::serde_json::json; use deno_core::serde_json::Value; 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 serde::Deserialize; use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; use std::ffi::c_void; use std::rc::Rc; pub struct Unstable(pub bool); fn check_unstable(state: &OpState, api_name: &str) { let unstable = state.borrow::(); if !unstable.0 { eprintln!( "Unstable API '{}'. The --unstable flag must be provided.", api_name ); std::process::exit(70); } } pub trait FfiPermissions { fn check(&mut self, path: &str) -> Result<(), AnyError>; } pub struct NoFfiPermissions; impl FfiPermissions for NoFfiPermissions { fn check(&mut self, _path: &str) -> Result<(), AnyError> { Ok(()) } } struct Symbol { cif: libffi::middle::Cif, ptr: libffi::middle::CodePtr, parameter_types: Vec, result_type: NativeType, } struct DynamicLibraryResource { lib: Library, symbols: HashMap, } impl Resource for DynamicLibraryResource { fn name(&self) -> Cow { "dynamicLibrary".into() } fn close(self: Rc) { drop(self) } } impl DynamicLibraryResource { fn register( &mut self, symbol: String, foreign_fn: ForeignFunction, ) -> Result<(), AnyError> { let fn_ptr = unsafe { self.lib.symbol::<*const c_void>(&symbol) }?; let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _); let parameter_types = foreign_fn.parameters.into_iter().map(NativeType::from); let result_type = NativeType::from(foreign_fn.result); let cif = libffi::middle::Cif::new( parameter_types.clone().map(libffi::middle::Type::from), result_type.into(), ); self.symbols.insert( symbol, Symbol { cif, ptr, parameter_types: parameter_types.collect(), result_type, }, ); Ok(()) } } pub fn init(unstable: bool) -> Extension { Extension::builder() .js(include_js_files!( prefix "deno:ext/ffi", "00_ffi.js", )) .ops(vec![ ("op_ffi_load", op_sync(op_ffi_load::

)), ("op_ffi_call", op_sync(op_ffi_call)), ]) .state(move |state| { // Stolen from deno_webgpu, is there a better option? state.put(Unstable(unstable)); Ok(()) }) .build() } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] #[serde(rename_all = "lowercase")] enum NativeType { Void, U8, I8, U16, I16, U32, I32, U64, I64, USize, ISize, F32, F64, } impl From for libffi::middle::Type { fn from(native_type: NativeType) -> Self { match native_type { NativeType::Void => libffi::middle::Type::void(), NativeType::U8 => 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(), } } } impl From for NativeType { fn from(string: String) -> Self { match string.as_str() { "void" => NativeType::Void, "u8" => NativeType::U8, "i8" => NativeType::I8, "u16" => NativeType::U16, "i16" => NativeType::I16, "u32" => NativeType::U32, "i32" => NativeType::I32, "u64" => NativeType::U64, "i64" => NativeType::I64, "usize" => NativeType::USize, "isize" => NativeType::ISize, "f32" => NativeType::F32, "f64" => NativeType::F64, _ => unimplemented!(), } } } #[repr(C)] union NativeValue { void_value: (), 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, } impl NativeValue { fn new(native_type: NativeType, value: Value) -> Self { match native_type { NativeType::Void => Self { void_value: () }, NativeType::U8 => Self { u8_value: value_as_uint::(value), }, NativeType::I8 => Self { i8_value: value_as_int::(value), }, NativeType::U16 => Self { u16_value: value_as_uint::(value), }, NativeType::I16 => Self { i16_value: value_as_int::(value), }, NativeType::U32 => Self { u32_value: value_as_uint::(value), }, NativeType::I32 => Self { i32_value: value_as_int::(value), }, NativeType::U64 => Self { u64_value: value_as_uint::(value), }, NativeType::I64 => Self { i64_value: value_as_int::(value), }, NativeType::USize => Self { usize_value: value_as_uint::(value), }, NativeType::ISize => Self { isize_value: value_as_int::(value), }, NativeType::F32 => Self { f32_value: value_as_f32(value), }, NativeType::F64 => Self { f64_value: value_as_f64(value), }, } } unsafe fn as_arg(&self, native_type: NativeType) -> Arg { match native_type { NativeType::Void => Arg::new(&self.void_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), } } } fn value_as_uint>(value: Value) -> T { value .as_u64() .and_then(|v| T::try_from(v).ok()) .expect("Expected ffi arg value to be an unsigned integer") } fn value_as_int>(value: Value) -> T { value .as_i64() .and_then(|v| T::try_from(v).ok()) .expect("Expected ffi arg value to be a signed integer") } fn value_as_f32(value: Value) -> f32 { value_as_f64(value) as f32 } fn value_as_f64(value: Value) -> f64 { value .as_f64() .expect("Expected ffi arg value to be a float") } #[derive(Deserialize, Debug)] struct ForeignFunction { parameters: Vec, result: String, } #[derive(Deserialize, Debug)] struct FfiLoadArgs { path: String, symbols: HashMap, } fn op_ffi_load( state: &mut deno_core::OpState, args: FfiLoadArgs, _: (), ) -> Result where FP: FfiPermissions + 'static, { check_unstable(state, "Deno.dlopen"); let permissions = state.borrow_mut::(); permissions.check(&args.path)?; let lib = Library::open(args.path)?; let mut resource = DynamicLibraryResource { lib, symbols: HashMap::new(), }; for (symbol, foreign_fn) in args.symbols { resource.register(symbol, foreign_fn)?; } Ok(state.resource_table.add(resource)) } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct FfiCallArgs { rid: ResourceId, symbol: String, parameters: Vec, } fn op_ffi_call( state: &mut deno_core::OpState, args: FfiCallArgs, _: (), ) -> Result { let resource = state .resource_table .get::(args.rid)?; let symbol = resource .symbols .get(&args.symbol) .ok_or_else(bad_resource_id)?; let native_values = symbol .parameter_types .iter() .zip(args.parameters.into_iter()) .map(|(&native_type, value)| NativeValue::new(native_type, value)) .collect::>(); let call_args = symbol .parameter_types .iter() .zip(native_values.iter()) .map(|(&native_type, native_value)| unsafe { native_value.as_arg(native_type) }) .collect::>(); Ok(match symbol.result_type { NativeType::Void => { json!(unsafe { symbol.cif.call::<()>(symbol.ptr, &call_args) }) } NativeType::U8 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::I8 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::U16 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::I16 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::U32 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::I32 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::U64 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::I64 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::USize => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::ISize => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::F32 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } NativeType::F64 => { json!(unsafe { symbol.cif.call::(symbol.ptr, &call_args) }) } }) }