mirror of
https://github.com/denoland/deno.git
synced 2024-11-01 09:24:20 -04:00
33c8d790c3
This commit removes implementation of "native plugins" and replaces it with FFI API. Effectively "Deno.openPlugin" API was replaced with "Deno.dlopen" API.
397 lines
9.9 KiB
Rust
397 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) })
|
|
}
|
|
})
|
|
}
|