mirror of
https://github.com/denoland/deno.git
synced 2024-11-01 09:24:20 -04:00
398 lines
9.9 KiB
Rust
398 lines
9.9 KiB
Rust
|
// 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::<Unstable>();
|
||
|
|
||
|
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<NativeType>,
|
||
|
result_type: NativeType,
|
||
|
}
|
||
|
|
||
|
struct DynamicLibraryResource {
|
||
|
lib: Library,
|
||
|
symbols: HashMap<String, Symbol>,
|
||
|
}
|
||
|
|
||
|
impl Resource for DynamicLibraryResource {
|
||
|
fn name(&self) -> Cow<str> {
|
||
|
"dynamicLibrary".into()
|
||
|
}
|
||
|
|
||
|
fn close(self: Rc<Self>) {
|
||
|
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<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
|
||
|
Extension::builder()
|
||
|
.js(include_js_files!(
|
||
|
prefix "deno:extensions/ffi",
|
||
|
"00_ffi.js",
|
||
|
))
|
||
|
.ops(vec![
|
||
|
("op_ffi_load", op_sync(op_ffi_load::<P>)),
|
||
|
("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<NativeType> 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<String> 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::<u8>(value),
|
||
|
},
|
||
|
NativeType::I8 => Self {
|
||
|
i8_value: value_as_int::<i8>(value),
|
||
|
},
|
||
|
NativeType::U16 => Self {
|
||
|
u16_value: value_as_uint::<u16>(value),
|
||
|
},
|
||
|
NativeType::I16 => Self {
|
||
|
i16_value: value_as_int::<i16>(value),
|
||
|
},
|
||
|
NativeType::U32 => Self {
|
||
|
u32_value: value_as_uint::<u32>(value),
|
||
|
},
|
||
|
NativeType::I32 => Self {
|
||
|
i32_value: value_as_int::<i32>(value),
|
||
|
},
|
||
|
NativeType::U64 => Self {
|
||
|
u64_value: value_as_uint::<u64>(value),
|
||
|
},
|
||
|
NativeType::I64 => Self {
|
||
|
i64_value: value_as_int::<i64>(value),
|
||
|
},
|
||
|
NativeType::USize => Self {
|
||
|
usize_value: value_as_uint::<usize>(value),
|
||
|
},
|
||
|
NativeType::ISize => Self {
|
||
|
isize_value: value_as_int::<isize>(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<T: TryFrom<u64>>(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<T: TryFrom<i64>>(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<String>,
|
||
|
result: String,
|
||
|
}
|
||
|
|
||
|
#[derive(Deserialize, Debug)]
|
||
|
struct FfiLoadArgs {
|
||
|
path: String,
|
||
|
symbols: HashMap<String, ForeignFunction>,
|
||
|
}
|
||
|
|
||
|
fn op_ffi_load<FP>(
|
||
|
state: &mut deno_core::OpState,
|
||
|
args: FfiLoadArgs,
|
||
|
_: (),
|
||
|
) -> Result<ResourceId, AnyError>
|
||
|
where
|
||
|
FP: FfiPermissions + 'static,
|
||
|
{
|
||
|
check_unstable(state, "Deno.dlopen");
|
||
|
let permissions = state.borrow_mut::<FP>();
|
||
|
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<Value>,
|
||
|
}
|
||
|
|
||
|
fn op_ffi_call(
|
||
|
state: &mut deno_core::OpState,
|
||
|
args: FfiCallArgs,
|
||
|
_: (),
|
||
|
) -> Result<Value, AnyError> {
|
||
|
let resource = state
|
||
|
.resource_table
|
||
|
.get::<DynamicLibraryResource>(args.rid)
|
||
|
.ok_or_else(bad_resource_id)?;
|
||
|
|
||
|
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::<Vec<_>>();
|
||
|
|
||
|
let call_args = symbol
|
||
|
.parameter_types
|
||
|
.iter()
|
||
|
.zip(native_values.iter())
|
||
|
.map(|(&native_type, native_value)| unsafe {
|
||
|
native_value.as_arg(native_type)
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
|
||
|
Ok(match symbol.result_type {
|
||
|
NativeType::Void => {
|
||
|
json!(unsafe { symbol.cif.call::<()>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::U8 => {
|
||
|
json!(unsafe { symbol.cif.call::<u8>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::I8 => {
|
||
|
json!(unsafe { symbol.cif.call::<i8>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::U16 => {
|
||
|
json!(unsafe { symbol.cif.call::<u16>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::I16 => {
|
||
|
json!(unsafe { symbol.cif.call::<i16>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::U32 => {
|
||
|
json!(unsafe { symbol.cif.call::<u32>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::I32 => {
|
||
|
json!(unsafe { symbol.cif.call::<i32>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::U64 => {
|
||
|
json!(unsafe { symbol.cif.call::<u64>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::I64 => {
|
||
|
json!(unsafe { symbol.cif.call::<i64>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::USize => {
|
||
|
json!(unsafe { symbol.cif.call::<usize>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::ISize => {
|
||
|
json!(unsafe { symbol.cif.call::<isize>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::F32 => {
|
||
|
json!(unsafe { symbol.cif.call::<f32>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
NativeType::F64 => {
|
||
|
json!(unsafe { symbol.cif.call::<f64>(symbol.ptr, &call_args) })
|
||
|
}
|
||
|
})
|
||
|
}
|