mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat: ffi to replace plugins (#11152)
This commit removes implementation of "native plugins" and replaces it with FFI API. Effectively "Deno.openPlugin" API was replaced with "Deno.dlopen" API.
This commit is contained in:
parent
0d1a522a03
commit
33c8d790c3
44 changed files with 860 additions and 511 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -164,9 +164,11 @@ jobs:
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install debootstrap
|
sudo apt-get install debootstrap
|
||||||
|
|
||||||
# Note: git, nc, strace, and time, are needed to run the benchmarks.
|
# `file` and `make` are needed to build libffi-sys.
|
||||||
|
# `curl` is needed to build rusty_v8.
|
||||||
|
# `git`, `nc`, `strace`, and `time`, are needed to run the benchmarks.
|
||||||
sudo debootstrap \
|
sudo debootstrap \
|
||||||
--include=ca-certificates,curl,git,netcat-openbsd,strace,time \
|
--include=ca-certificates,curl,file,git,make,netcat-openbsd,strace,time \
|
||||||
--no-merged-usr --variant=minbase bionic /sysroot \
|
--no-merged-usr --variant=minbase bionic /sysroot \
|
||||||
http://azure.archive.ubuntu.com/ubuntu
|
http://azure.archive.ubuntu.com/ubuntu
|
||||||
sudo mount --rbind /dev /sysroot/dev
|
sudo mount --rbind /dev /sysroot/dev
|
||||||
|
|
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -12,6 +12,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "abort_on_panic"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -568,6 +574,7 @@ dependencies = [
|
||||||
"deno_crypto",
|
"deno_crypto",
|
||||||
"deno_doc",
|
"deno_doc",
|
||||||
"deno_fetch",
|
"deno_fetch",
|
||||||
|
"deno_ffi",
|
||||||
"deno_http",
|
"deno_http",
|
||||||
"deno_lint",
|
"deno_lint",
|
||||||
"deno_net",
|
"deno_net",
|
||||||
|
@ -629,6 +636,27 @@ dependencies = [
|
||||||
"winres",
|
"winres",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno-libffi"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a844ceea9e6233005c62dfe0bb7c1adab786ea78bab4ac1e5ea5cd2a5d47761"
|
||||||
|
dependencies = [
|
||||||
|
"abort_on_panic",
|
||||||
|
"deno-libffi-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno-libffi-sys"
|
||||||
|
version = "0.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "15a12b5205cc4f3944cefa192e851a0ee15c226b41733468b2660d4ab2bdf555"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"make-cmd",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_bench_util"
|
name = "deno_bench_util"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -725,6 +753,16 @@ dependencies = [
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_ffi"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"deno-libffi",
|
||||||
|
"deno_core",
|
||||||
|
"dlopen",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_http"
|
name = "deno_http"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -786,6 +824,7 @@ dependencies = [
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"deno_crypto",
|
"deno_crypto",
|
||||||
"deno_fetch",
|
"deno_fetch",
|
||||||
|
"deno_ffi",
|
||||||
"deno_http",
|
"deno_http",
|
||||||
"deno_net",
|
"deno_net",
|
||||||
"deno_timers",
|
"deno_timers",
|
||||||
|
@ -2106,6 +2145,12 @@ dependencies = [
|
||||||
"syn 1.0.65",
|
"syn 1.0.65",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "make-cmd"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
|
@ -3831,12 +3876,9 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test_plugin"
|
name = "test_ffi"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deno_core",
|
|
||||||
"futures",
|
|
||||||
"serde",
|
|
||||||
"test_util",
|
"test_util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,13 @@ members = [
|
||||||
"cli",
|
"cli",
|
||||||
"core",
|
"core",
|
||||||
"runtime",
|
"runtime",
|
||||||
"test_plugin",
|
"test_ffi",
|
||||||
"test_util",
|
"test_util",
|
||||||
"extensions/broadcast_channel",
|
"extensions/broadcast_channel",
|
||||||
"extensions/console",
|
"extensions/console",
|
||||||
"extensions/crypto",
|
"extensions/crypto",
|
||||||
"extensions/fetch",
|
"extensions/fetch",
|
||||||
|
"extensions/ffi",
|
||||||
"extensions/http",
|
"extensions/http",
|
||||||
"extensions/net",
|
"extensions/net",
|
||||||
"extensions/timers",
|
"extensions/timers",
|
||||||
|
|
|
@ -25,6 +25,7 @@ deno_console = { version = "0.13.0", path = "../extensions/console" }
|
||||||
deno_core = { version = "0.95.0", path = "../core" }
|
deno_core = { version = "0.95.0", path = "../core" }
|
||||||
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||||
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
||||||
|
deno_ffi = { version = "0.1.0", path = "../extensions/ffi" }
|
||||||
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
||||||
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
||||||
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
||||||
|
|
|
@ -54,7 +54,7 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||||
"listen",
|
"listen",
|
||||||
"listenDatagram",
|
"listenDatagram",
|
||||||
"loadavg",
|
"loadavg",
|
||||||
"openPlugin",
|
"dlopen",
|
||||||
"osRelease",
|
"osRelease",
|
||||||
"ppid",
|
"ppid",
|
||||||
"resolveDns",
|
"resolveDns",
|
||||||
|
|
8
cli/dts/lib.deno.ns.d.ts
vendored
8
cli/dts/lib.deno.ns.d.ts
vendored
|
@ -2131,7 +2131,7 @@ declare namespace Deno {
|
||||||
| "write"
|
| "write"
|
||||||
| "net"
|
| "net"
|
||||||
| "env"
|
| "env"
|
||||||
| "plugin"
|
| "ffi"
|
||||||
| "hrtime";
|
| "hrtime";
|
||||||
|
|
||||||
/** The current status of the permission. */
|
/** The current status of the permission. */
|
||||||
|
@ -2167,8 +2167,8 @@ declare namespace Deno {
|
||||||
variable?: string;
|
variable?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginPermissionDescriptor {
|
export interface FFIPermissionDescriptor {
|
||||||
name: "plugin";
|
name: "ffi";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HrtimePermissionDescriptor {
|
export interface HrtimePermissionDescriptor {
|
||||||
|
@ -2183,7 +2183,7 @@ declare namespace Deno {
|
||||||
| WritePermissionDescriptor
|
| WritePermissionDescriptor
|
||||||
| NetPermissionDescriptor
|
| NetPermissionDescriptor
|
||||||
| EnvPermissionDescriptor
|
| EnvPermissionDescriptor
|
||||||
| PluginPermissionDescriptor
|
| FFIPermissionDescriptor
|
||||||
| HrtimePermissionDescriptor;
|
| HrtimePermissionDescriptor;
|
||||||
|
|
||||||
export interface PermissionStatusEventMap {
|
export interface PermissionStatusEventMap {
|
||||||
|
|
76
cli/dts/lib.deno.unstable.d.ts
vendored
76
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -107,36 +107,44 @@ declare namespace Deno {
|
||||||
swapFree: number;
|
swapFree: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** **UNSTABLE**: new API, yet to be vetted.
|
/** All possible types for interfacing with foreign functions */
|
||||||
|
export type NativeType =
|
||||||
|
| "void"
|
||||||
|
| "u8"
|
||||||
|
| "i8"
|
||||||
|
| "u16"
|
||||||
|
| "i16"
|
||||||
|
| "u32"
|
||||||
|
| "i32"
|
||||||
|
| "u64"
|
||||||
|
| "i64"
|
||||||
|
| "usize"
|
||||||
|
| "isize"
|
||||||
|
| "f32"
|
||||||
|
| "f64";
|
||||||
|
|
||||||
|
/** A foreign function as defined by its parameter and result types */
|
||||||
|
export interface ForeignFunction {
|
||||||
|
parameters: NativeType[];
|
||||||
|
result: NativeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A dynamic library resource */
|
||||||
|
export interface DynamicLibrary<S extends Record<string, ForeignFunction>> {
|
||||||
|
/** All of the registered symbols along with functions for calling them */
|
||||||
|
symbols: { [K in keyof S]: (...args: unknown[]) => unknown };
|
||||||
|
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** **UNSTABLE**: new API
|
||||||
*
|
*
|
||||||
* Open and initialize a plugin.
|
* Opens a dynamic library and registers symbols
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { assert } from "https://deno.land/std/testing/asserts.ts";
|
|
||||||
* const rid = Deno.openPlugin("./path/to/some/plugin.so");
|
|
||||||
*
|
|
||||||
* // The Deno.core namespace is needed to interact with plugins, but this is
|
|
||||||
* // internal so we use ts-ignore to skip type checking these calls.
|
|
||||||
* // @ts-ignore
|
|
||||||
* const { op_test_sync, op_test_async } = Deno.core.ops();
|
|
||||||
*
|
|
||||||
* assert(op_test_sync);
|
|
||||||
* assert(op_test_async);
|
|
||||||
*
|
|
||||||
* // @ts-ignore
|
|
||||||
* const result = Deno.core.opSync("op_test_sync");
|
|
||||||
*
|
|
||||||
* // @ts-ignore
|
|
||||||
* const result = await Deno.core.opAsync("op_test_sync");
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Requires `allow-plugin` permission.
|
|
||||||
*
|
|
||||||
* The plugin system is not stable and will change in the future, hence the
|
|
||||||
* lack of docs. For now take a look at the example
|
|
||||||
* https://github.com/denoland/deno/tree/main/test_plugin
|
|
||||||
*/
|
*/
|
||||||
export function openPlugin(filename: string): number;
|
export function dlopen<S extends Record<string, ForeignFunction>>(
|
||||||
|
filename: string,
|
||||||
|
symbols: S,
|
||||||
|
): DynamicLibrary<S>;
|
||||||
|
|
||||||
/** The log category for a diagnostic message. */
|
/** The log category for a diagnostic message. */
|
||||||
export enum DiagnosticCategory {
|
export enum DiagnosticCategory {
|
||||||
|
@ -1043,14 +1051,14 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
net?: "inherit" | boolean | string[];
|
net?: "inherit" | boolean | string[];
|
||||||
|
|
||||||
/** Specifies if the `plugin` permission should be requested or revoked.
|
/** Specifies if the `ffi` permission should be requested or revoked.
|
||||||
* If set to `"inherit"`, the current `plugin` permission will be inherited.
|
* If set to `"inherit"`, the current `ffi` permission will be inherited.
|
||||||
* If set to `true`, the global `plugin` permission will be requested.
|
* If set to `true`, the global `ffi` permission will be requested.
|
||||||
* If set to `false`, the global `plugin` permission will be revoked.
|
* If set to `false`, the global `ffi` permission will be revoked.
|
||||||
*
|
*
|
||||||
* Defaults to "inherit".
|
* Defaults to "inherit".
|
||||||
*/
|
*/
|
||||||
plugin?: "inherit" | boolean;
|
ffi?: "inherit" | boolean;
|
||||||
|
|
||||||
/** Specifies if the `read` permission should be requested or revoked.
|
/** Specifies if the `read` permission should be requested or revoked.
|
||||||
* If set to `"inherit"`, the current `read` permission will be inherited.
|
* If set to `"inherit"`, the current `read` permission will be inherited.
|
||||||
|
@ -1137,7 +1145,7 @@ declare interface WorkerOptions {
|
||||||
* For example: `["https://deno.land", "localhost:8080"]`.
|
* For example: `["https://deno.land", "localhost:8080"]`.
|
||||||
*/
|
*/
|
||||||
net?: "inherit" | boolean | string[];
|
net?: "inherit" | boolean | string[];
|
||||||
plugin?: "inherit" | boolean;
|
ffi?: "inherit" | boolean;
|
||||||
read?: "inherit" | boolean | Array<string | URL>;
|
read?: "inherit" | boolean | Array<string | URL>;
|
||||||
run?: "inherit" | boolean | Array<string | URL>;
|
run?: "inherit" | boolean | Array<string | URL>;
|
||||||
write?: "inherit" | boolean | Array<string | URL>;
|
write?: "inherit" | boolean | Array<string | URL>;
|
||||||
|
|
54
cli/flags.rs
54
cli/flags.rs
|
@ -133,7 +133,7 @@ pub struct Flags {
|
||||||
pub allow_env: Option<Vec<String>>,
|
pub allow_env: Option<Vec<String>>,
|
||||||
pub allow_hrtime: bool,
|
pub allow_hrtime: bool,
|
||||||
pub allow_net: Option<Vec<String>>,
|
pub allow_net: Option<Vec<String>>,
|
||||||
pub allow_plugin: bool,
|
pub allow_ffi: Option<Vec<String>>,
|
||||||
pub allow_read: Option<Vec<PathBuf>>,
|
pub allow_read: Option<Vec<PathBuf>>,
|
||||||
pub allow_run: Option<Vec<String>>,
|
pub allow_run: Option<Vec<String>>,
|
||||||
pub allow_write: Option<Vec<PathBuf>>,
|
pub allow_write: Option<Vec<PathBuf>>,
|
||||||
|
@ -235,8 +235,15 @@ impl Flags {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.allow_plugin {
|
match &self.allow_ffi {
|
||||||
args.push("--allow-plugin".to_string());
|
Some(ffi_allowlist) if ffi_allowlist.is_empty() => {
|
||||||
|
args.push("--allow-ffi".to_string());
|
||||||
|
}
|
||||||
|
Some(ffi_allowlist) => {
|
||||||
|
let s = format!("--allow-ffi={}", ffi_allowlist.join(","));
|
||||||
|
args.push(s);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.allow_hrtime {
|
if self.allow_hrtime {
|
||||||
|
@ -253,7 +260,7 @@ impl From<Flags> for PermissionsOptions {
|
||||||
allow_env: flags.allow_env,
|
allow_env: flags.allow_env,
|
||||||
allow_hrtime: flags.allow_hrtime,
|
allow_hrtime: flags.allow_hrtime,
|
||||||
allow_net: flags.allow_net,
|
allow_net: flags.allow_net,
|
||||||
allow_plugin: flags.allow_plugin,
|
allow_ffi: flags.allow_ffi,
|
||||||
allow_read: flags.allow_read,
|
allow_read: flags.allow_read,
|
||||||
allow_run: flags.allow_run,
|
allow_run: flags.allow_run,
|
||||||
allow_write: flags.allow_write,
|
allow_write: flags.allow_write,
|
||||||
|
@ -1228,9 +1235,13 @@ fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||||
.help("Allow running subprocesses"),
|
.help("Allow running subprocesses"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("allow-plugin")
|
Arg::with_name("allow-ffi")
|
||||||
.long("allow-plugin")
|
.long("allow-ffi")
|
||||||
.help("Allow loading plugins"),
|
.min_values(0)
|
||||||
|
.takes_value(true)
|
||||||
|
.use_delimiter(true)
|
||||||
|
.require_equals(true)
|
||||||
|
.help("Allow loading dynamic libraries"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("allow-hrtime")
|
Arg::with_name("allow-hrtime")
|
||||||
|
@ -1577,7 +1588,7 @@ fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
flags.allow_run = Some(vec![]);
|
flags.allow_run = Some(vec![]);
|
||||||
flags.allow_read = Some(vec![]);
|
flags.allow_read = Some(vec![]);
|
||||||
flags.allow_write = Some(vec![]);
|
flags.allow_write = Some(vec![]);
|
||||||
flags.allow_plugin = true;
|
flags.allow_ffi = Some(vec![]);
|
||||||
flags.allow_hrtime = true;
|
flags.allow_hrtime = true;
|
||||||
// TODO(@satyarohith): remove this flag in 2.0.
|
// TODO(@satyarohith): remove this flag in 2.0.
|
||||||
let as_typescript = matches.is_present("ts");
|
let as_typescript = matches.is_present("ts");
|
||||||
|
@ -1696,7 +1707,7 @@ fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
flags.allow_run = Some(vec![]);
|
flags.allow_run = Some(vec![]);
|
||||||
flags.allow_read = Some(vec![]);
|
flags.allow_read = Some(vec![]);
|
||||||
flags.allow_write = Some(vec![]);
|
flags.allow_write = Some(vec![]);
|
||||||
flags.allow_plugin = true;
|
flags.allow_ffi = Some(vec![]);
|
||||||
flags.allow_hrtime = true;
|
flags.allow_hrtime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1876,9 +1887,12 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
debug!("run allowlist: {:#?}", &flags.allow_run);
|
debug!("run allowlist: {:#?}", &flags.allow_run);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("allow-plugin") {
|
if let Some(ffi_wl) = matches.values_of("allow-ffi") {
|
||||||
flags.allow_plugin = true;
|
let ffi_allowlist: Vec<String> = ffi_wl.map(ToString::to_string).collect();
|
||||||
|
flags.allow_ffi = Some(ffi_allowlist);
|
||||||
|
debug!("ffi allowlist: {:#?}", &flags.allow_ffi);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present("allow-hrtime") {
|
if matches.is_present("allow-hrtime") {
|
||||||
flags.allow_hrtime = true;
|
flags.allow_hrtime = true;
|
||||||
}
|
}
|
||||||
|
@ -1888,7 +1902,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||||
flags.allow_net = Some(vec![]);
|
flags.allow_net = Some(vec![]);
|
||||||
flags.allow_run = Some(vec![]);
|
flags.allow_run = Some(vec![]);
|
||||||
flags.allow_write = Some(vec![]);
|
flags.allow_write = Some(vec![]);
|
||||||
flags.allow_plugin = true;
|
flags.allow_ffi = Some(vec![]);
|
||||||
flags.allow_hrtime = true;
|
flags.allow_hrtime = true;
|
||||||
}
|
}
|
||||||
if matches.is_present("prompt") {
|
if matches.is_present("prompt") {
|
||||||
|
@ -2227,7 +2241,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2564,7 +2578,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2587,7 +2601,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2611,7 +2625,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2648,7 +2662,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2678,7 +2692,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2698,7 +2712,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -2732,7 +2746,7 @@ mod tests {
|
||||||
allow_run: Some(vec![]),
|
allow_run: Some(vec![]),
|
||||||
allow_read: Some(vec![]),
|
allow_read: Some(vec![]),
|
||||||
allow_write: Some(vec![]),
|
allow_write: Some(vec![]),
|
||||||
allow_plugin: true,
|
allow_ffi: Some(vec![]),
|
||||||
allow_hrtime: true,
|
allow_hrtime: true,
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ fn metadata_to_flags(metadata: &Metadata) -> Flags {
|
||||||
allow_env: permissions.allow_env,
|
allow_env: permissions.allow_env,
|
||||||
allow_hrtime: permissions.allow_hrtime,
|
allow_hrtime: permissions.allow_hrtime,
|
||||||
allow_net: permissions.allow_net,
|
allow_net: permissions.allow_net,
|
||||||
allow_plugin: permissions.allow_plugin,
|
allow_ffi: permissions.allow_ffi,
|
||||||
allow_read: permissions.allow_read,
|
allow_read: permissions.allow_read,
|
||||||
allow_run: permissions.allow_run,
|
allow_run: permissions.allow_run,
|
||||||
allow_write: permissions.allow_write,
|
allow_write: permissions.allow_write,
|
||||||
|
|
|
@ -502,7 +502,7 @@ fn lsp_hover_unstable_disabled() {
|
||||||
"uri": "file:///a/file.ts",
|
"uri": "file:///a/file.ts",
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "console.log(Deno.openPlugin);\n"
|
"text": "console.log(Deno.dlopen);\n"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -537,7 +537,7 @@ fn lsp_hover_unstable_disabled() {
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 0,
|
"line": 0,
|
||||||
"character": 27
|
"character": 23
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -555,7 +555,7 @@ fn lsp_hover_unstable_enabled() {
|
||||||
"uri": "file:///a/file.ts",
|
"uri": "file:///a/file.ts",
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "console.log(Deno.openPlugin);\n"
|
"text": "console.log(Deno.ppid);\n"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -580,9 +580,9 @@ fn lsp_hover_unstable_enabled() {
|
||||||
"contents":[
|
"contents":[
|
||||||
{
|
{
|
||||||
"language":"typescript",
|
"language":"typescript",
|
||||||
"value":"function Deno.openPlugin(filename: string): number"
|
"value":"const Deno.ppid: number"
|
||||||
},
|
},
|
||||||
"**UNSTABLE**: new API, yet to be vetted.\n\nOpen and initialize a plugin.\n\n```ts\nimport { assert } from \"https://deno.land/std/testing/asserts.ts\";\nconst rid = Deno.openPlugin(\"./path/to/some/plugin.so\");\n\n// The Deno.core namespace is needed to interact with plugins, but this is\n// internal so we use ts-ignore to skip type checking these calls.\n// @ts-ignore\nconst { op_test_sync, op_test_async } = Deno.core.ops();\n\nassert(op_test_sync);\nassert(op_test_async);\n\n// @ts-ignore\nconst result = Deno.core.opSync(\"op_test_sync\");\n\n// @ts-ignore\nconst result = await Deno.core.opAsync(\"op_test_sync\");\n```\n\nRequires `allow-plugin` permission.\n\nThe plugin system is not stable and will change in the future, hence the\nlack of docs. For now take a look at the example\nhttps://github.com/denoland/deno/tree/main/test_plugin"
|
"The pid of the current process's parent."
|
||||||
],
|
],
|
||||||
"range":{
|
"range":{
|
||||||
"start":{
|
"start":{
|
||||||
|
@ -591,7 +591,7 @@ fn lsp_hover_unstable_enabled() {
|
||||||
},
|
},
|
||||||
"end":{
|
"end":{
|
||||||
"line":0,
|
"line":0,
|
||||||
"character":27
|
"character":21
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -10,8 +10,8 @@ test env false ... ok [WILDCARD]
|
||||||
test env true ... ok [WILDCARD]
|
test env true ... ok [WILDCARD]
|
||||||
test run false ... ok [WILDCARD]
|
test run false ... ok [WILDCARD]
|
||||||
test run true ... ok [WILDCARD]
|
test run true ... ok [WILDCARD]
|
||||||
test plugin false ... ok [WILDCARD]
|
test ffi false ... ok [WILDCARD]
|
||||||
test plugin true ... ok [WILDCARD]
|
test ffi true ... ok [WILDCARD]
|
||||||
test hrtime false ... ok [WILDCARD]
|
test hrtime false ... ok [WILDCARD]
|
||||||
test hrtime true ... ok [WILDCARD]
|
test hrtime true ... ok [WILDCARD]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ const permissions: Deno.PermissionName[] = [
|
||||||
"net",
|
"net",
|
||||||
"env",
|
"env",
|
||||||
"run",
|
"run",
|
||||||
"plugin",
|
"ffi",
|
||||||
"hrtime",
|
"hrtime",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ test write ... FAILED [WILDCARD]
|
||||||
test net ... FAILED [WILDCARD]
|
test net ... FAILED [WILDCARD]
|
||||||
test env ... FAILED [WILDCARD]
|
test env ... FAILED [WILDCARD]
|
||||||
test run ... FAILED [WILDCARD]
|
test run ... FAILED [WILDCARD]
|
||||||
test plugin ... FAILED [WILDCARD]
|
test ffi ... FAILED [WILDCARD]
|
||||||
test hrtime ... FAILED [WILDCARD]
|
test hrtime ... FAILED [WILDCARD]
|
||||||
|
|
||||||
failures:
|
failures:
|
||||||
|
@ -30,7 +30,7 @@ run
|
||||||
PermissionDenied: Can't escalate parent thread permissions
|
PermissionDenied: Can't escalate parent thread permissions
|
||||||
[WILDCARD]
|
[WILDCARD]
|
||||||
|
|
||||||
plugin
|
ffi
|
||||||
PermissionDenied: Can't escalate parent thread permissions
|
PermissionDenied: Can't escalate parent thread permissions
|
||||||
[WILDCARD]
|
[WILDCARD]
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ failures:
|
||||||
net
|
net
|
||||||
env
|
env
|
||||||
run
|
run
|
||||||
plugin
|
ffi
|
||||||
hrtime
|
hrtime
|
||||||
|
|
||||||
test result: FAILED. 0 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
|
test result: FAILED. 0 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
|
||||||
|
|
|
@ -6,7 +6,7 @@ const permissions: Deno.PermissionName[] = [
|
||||||
"net",
|
"net",
|
||||||
"env",
|
"env",
|
||||||
"run",
|
"run",
|
||||||
"plugin",
|
"ffi",
|
||||||
"hrtime",
|
"hrtime",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ Deno.test({
|
||||||
net: true,
|
net: true,
|
||||||
env: true,
|
env: true,
|
||||||
run: true,
|
run: true,
|
||||||
plugin: true,
|
ffi: true,
|
||||||
hrtime: true,
|
hrtime: true,
|
||||||
},
|
},
|
||||||
ignore: true,
|
ignore: true,
|
||||||
|
|
|
@ -32,7 +32,7 @@ interface UnitTestPermissions {
|
||||||
net?: boolean;
|
net?: boolean;
|
||||||
env?: boolean;
|
env?: boolean;
|
||||||
run?: boolean;
|
run?: boolean;
|
||||||
plugin?: boolean;
|
ffi?: boolean;
|
||||||
hrtime?: boolean;
|
hrtime?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export function unitTest(
|
||||||
net: false,
|
net: false,
|
||||||
env: false,
|
env: false,
|
||||||
run: false,
|
run: false,
|
||||||
plugin: false,
|
ffi: false,
|
||||||
hrtime: false,
|
hrtime: false,
|
||||||
}, options.perms),
|
}, options.perms),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
self.onmessage = async () => {
|
self.onmessage = async () => {
|
||||||
const hrtime = await Deno.permissions.query({ name: "hrtime" });
|
const hrtime = await Deno.permissions.query({ name: "hrtime" });
|
||||||
const net = await Deno.permissions.query({ name: "net" });
|
const net = await Deno.permissions.query({ name: "net" });
|
||||||
const plugin = await Deno.permissions.query({ name: "plugin" });
|
const ffi = await Deno.permissions.query({ name: "ffi" });
|
||||||
const read = await Deno.permissions.query({ name: "read" });
|
const read = await Deno.permissions.query({ name: "read" });
|
||||||
const run = await Deno.permissions.query({ name: "run" });
|
const run = await Deno.permissions.query({ name: "run" });
|
||||||
const write = await Deno.permissions.query({ name: "write" });
|
const write = await Deno.permissions.query({ name: "write" });
|
||||||
self.postMessage(
|
self.postMessage(
|
||||||
hrtime.state === "denied" &&
|
hrtime.state === "denied" &&
|
||||||
net.state === "denied" &&
|
net.state === "denied" &&
|
||||||
plugin.state === "denied" &&
|
ffi.state === "denied" &&
|
||||||
read.state === "denied" &&
|
read.state === "denied" &&
|
||||||
run.state === "denied" &&
|
run.state === "denied" &&
|
||||||
write.state === "denied",
|
write.state === "denied",
|
||||||
|
|
|
@ -201,7 +201,7 @@ pub fn compile_to_runtime_flags(
|
||||||
allow_env: flags.allow_env,
|
allow_env: flags.allow_env,
|
||||||
allow_hrtime: flags.allow_hrtime,
|
allow_hrtime: flags.allow_hrtime,
|
||||||
allow_net: flags.allow_net,
|
allow_net: flags.allow_net,
|
||||||
allow_plugin: flags.allow_plugin,
|
allow_ffi: flags.allow_ffi,
|
||||||
allow_read: flags.allow_read,
|
allow_read: flags.allow_read,
|
||||||
allow_run: flags.allow_run,
|
allow_run: flags.allow_run,
|
||||||
allow_write: flags.allow_write,
|
allow_write: flags.allow_write,
|
||||||
|
|
30
extensions/ffi/00_ffi.js
Normal file
30
extensions/ffi/00_ffi.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
((window) => {
|
||||||
|
const core = window.Deno.core;
|
||||||
|
|
||||||
|
class DynamicLibrary {
|
||||||
|
#rid;
|
||||||
|
symbols = {};
|
||||||
|
|
||||||
|
constructor(path, symbols) {
|
||||||
|
this.#rid = core.opSync("op_ffi_load", { path, symbols });
|
||||||
|
|
||||||
|
for (const symbol in symbols) {
|
||||||
|
this.symbols[symbol] = (...parameters) =>
|
||||||
|
core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
core.close(this.#rid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dlopen(path, symbols) {
|
||||||
|
return new DynamicLibrary(path, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.__bootstrap.ffi = { dlopen };
|
||||||
|
})(this);
|
20
extensions/ffi/Cargo.toml
Normal file
20
extensions/ffi/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "deno_ffi"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["the Deno authors"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/denoland/deno"
|
||||||
|
description = "Dynamic library ffi for deno"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
deno_core = { version = "0.95.0", path = "../../core" }
|
||||||
|
dlopen = "0.1.8"
|
||||||
|
libffi = { version = "0.0.3", package = "deno-libffi" }
|
||||||
|
serde = { version = "1.0.125", features = ["derive"] }
|
3
extensions/ffi/README.md
Normal file
3
extensions/ffi/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# deno_ffi
|
||||||
|
|
||||||
|
This crate implements dynamic library ffi.
|
397
extensions/ffi/lib.rs
Normal file
397
extensions/ffi/lib.rs
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
// 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) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ deno_console = { version = "0.13.0", path = "../extensions/console" }
|
||||||
deno_core = { version = "0.95.0", path = "../core" }
|
deno_core = { version = "0.95.0", path = "../core" }
|
||||||
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||||
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
||||||
|
deno_ffi = { version = "0.1.0", path = "../extensions/ffi" }
|
||||||
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
||||||
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
||||||
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
||||||
|
@ -43,6 +44,7 @@ deno_console = { version = "0.13.0", path = "../extensions/console" }
|
||||||
deno_core = { version = "0.95.0", path = "../core" }
|
deno_core = { version = "0.95.0", path = "../core" }
|
||||||
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||||
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
deno_fetch = { version = "0.36.0", path = "../extensions/fetch" }
|
||||||
|
deno_ffi = { version = "0.1.0", path = "../extensions/ffi" }
|
||||||
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
deno_http = { version = "0.4.0", path = "../extensions/http" }
|
||||||
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
deno_net = { version = "0.4.0", path = "../extensions/net" }
|
||||||
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
||||||
|
|
|
@ -60,6 +60,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec<PathBuf>) {
|
||||||
deno_broadcast_channel::InMemoryBroadcastChannel::default(),
|
deno_broadcast_channel::InMemoryBroadcastChannel::default(),
|
||||||
false, // No --unstable.
|
false, // No --unstable.
|
||||||
),
|
),
|
||||||
|
deno_ffi::init::<deno_ffi::NoFfiPermissions>(false),
|
||||||
deno_net::init::<deno_net::NoNetPermissions>(None, false), // No --unstable.
|
deno_net::init::<deno_net::NoNetPermissions>(None, false), // No --unstable.
|
||||||
deno_http::init(),
|
deno_http::init(),
|
||||||
];
|
];
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
env = "inherit",
|
env = "inherit",
|
||||||
hrtime = "inherit",
|
hrtime = "inherit",
|
||||||
net = "inherit",
|
net = "inherit",
|
||||||
plugin = "inherit",
|
ffi = "inherit",
|
||||||
read = "inherit",
|
read = "inherit",
|
||||||
run = "inherit",
|
run = "inherit",
|
||||||
write = "inherit",
|
write = "inherit",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
env: parseUnitPermission(env, "env"),
|
env: parseUnitPermission(env, "env"),
|
||||||
hrtime: parseUnitPermission(hrtime, "hrtime"),
|
hrtime: parseUnitPermission(hrtime, "hrtime"),
|
||||||
net: parseArrayPermission(net, "net"),
|
net: parseArrayPermission(net, "net"),
|
||||||
plugin: parseUnitPermission(plugin, "plugin"),
|
ffi: parseUnitPermission(ffi, "ffi"),
|
||||||
read: parseArrayPermission(read, "read"),
|
read: parseArrayPermission(read, "read"),
|
||||||
run: parseUnitPermission(run, "run"),
|
run: parseUnitPermission(run, "run"),
|
||||||
write: parseArrayPermission(write, "write"),
|
write: parseArrayPermission(write, "write"),
|
||||||
|
@ -175,7 +175,7 @@
|
||||||
env: false,
|
env: false,
|
||||||
hrtime: false,
|
hrtime: false,
|
||||||
net: false,
|
net: false,
|
||||||
plugin: false,
|
ffi: false,
|
||||||
read: false,
|
read: false,
|
||||||
run: false,
|
run: false,
|
||||||
write: false,
|
write: false,
|
||||||
|
|
|
@ -28,14 +28,14 @@
|
||||||
* @property {PermissionStatus} status
|
* @property {PermissionStatus} status
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "plugin" | "hrtime">} */
|
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "ffi" | "hrtime">} */
|
||||||
const permissionNames = [
|
const permissionNames = [
|
||||||
"read",
|
"read",
|
||||||
"write",
|
"write",
|
||||||
"net",
|
"net",
|
||||||
"env",
|
"env",
|
||||||
"run",
|
"run",
|
||||||
"plugin",
|
"ffi",
|
||||||
"hrtime",
|
"hrtime",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
((window) => {
|
|
||||||
const core = window.Deno.core;
|
|
||||||
|
|
||||||
function openPlugin(filename) {
|
|
||||||
const rid = core.opSync("op_open_plugin", filename);
|
|
||||||
core.syncOpsCache();
|
|
||||||
return rid;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.__bootstrap.plugins = {
|
|
||||||
openPlugin,
|
|
||||||
};
|
|
||||||
})(this);
|
|
|
@ -110,7 +110,6 @@
|
||||||
Signal: __bootstrap.signals.Signal,
|
Signal: __bootstrap.signals.Signal,
|
||||||
SignalStream: __bootstrap.signals.SignalStream,
|
SignalStream: __bootstrap.signals.SignalStream,
|
||||||
emit: __bootstrap.compilerApi.emit,
|
emit: __bootstrap.compilerApi.emit,
|
||||||
openPlugin: __bootstrap.plugins.openPlugin,
|
|
||||||
kill: __bootstrap.process.kill,
|
kill: __bootstrap.process.kill,
|
||||||
setRaw: __bootstrap.tty.setRaw,
|
setRaw: __bootstrap.tty.setRaw,
|
||||||
consoleSize: __bootstrap.tty.consoleSize,
|
consoleSize: __bootstrap.tty.consoleSize,
|
||||||
|
@ -136,5 +135,6 @@
|
||||||
HttpClient: __bootstrap.fetch.HttpClient,
|
HttpClient: __bootstrap.fetch.HttpClient,
|
||||||
createHttpClient: __bootstrap.fetch.createHttpClient,
|
createHttpClient: __bootstrap.fetch.createHttpClient,
|
||||||
http: __bootstrap.http,
|
http: __bootstrap.http,
|
||||||
|
dlopen: __bootstrap.ffi.dlopen,
|
||||||
};
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub use deno_broadcast_channel;
|
||||||
pub use deno_console;
|
pub use deno_console;
|
||||||
pub use deno_crypto;
|
pub use deno_crypto;
|
||||||
pub use deno_fetch;
|
pub use deno_fetch;
|
||||||
|
pub use deno_ffi;
|
||||||
pub use deno_http;
|
pub use deno_http;
|
||||||
pub use deno_net;
|
pub use deno_net;
|
||||||
pub use deno_timers;
|
pub use deno_timers;
|
||||||
|
|
|
@ -6,7 +6,6 @@ pub mod http;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod os;
|
pub mod os;
|
||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
pub mod plugin;
|
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub struct PermissionArgs {
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
variable: Option<String>,
|
variable: Option<String>,
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
library: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn op_query_permission(
|
pub fn op_query_permission(
|
||||||
|
@ -49,7 +50,7 @@ pub fn op_query_permission(
|
||||||
),
|
),
|
||||||
"env" => permissions.env.query(args.variable.as_deref()),
|
"env" => permissions.env.query(args.variable.as_deref()),
|
||||||
"run" => permissions.run.query(args.command.as_deref()),
|
"run" => permissions.run.query(args.command.as_deref()),
|
||||||
"plugin" => permissions.plugin.query(),
|
"ffi" => permissions.ffi.query(args.library.as_deref()),
|
||||||
"hrtime" => permissions.hrtime.query(),
|
"hrtime" => permissions.hrtime.query(),
|
||||||
n => {
|
n => {
|
||||||
return Err(custom_error(
|
return Err(custom_error(
|
||||||
|
@ -80,7 +81,7 @@ pub fn op_revoke_permission(
|
||||||
),
|
),
|
||||||
"env" => permissions.env.revoke(args.variable.as_deref()),
|
"env" => permissions.env.revoke(args.variable.as_deref()),
|
||||||
"run" => permissions.run.revoke(args.command.as_deref()),
|
"run" => permissions.run.revoke(args.command.as_deref()),
|
||||||
"plugin" => permissions.plugin.revoke(),
|
"ffi" => permissions.ffi.revoke(args.library.as_deref()),
|
||||||
"hrtime" => permissions.hrtime.revoke(),
|
"hrtime" => permissions.hrtime.revoke(),
|
||||||
n => {
|
n => {
|
||||||
return Err(custom_error(
|
return Err(custom_error(
|
||||||
|
@ -111,7 +112,7 @@ pub fn op_request_permission(
|
||||||
),
|
),
|
||||||
"env" => permissions.env.request(args.variable.as_deref()),
|
"env" => permissions.env.request(args.variable.as_deref()),
|
||||||
"run" => permissions.run.request(args.command.as_deref()),
|
"run" => permissions.run.request(args.command.as_deref()),
|
||||||
"plugin" => permissions.plugin.request(),
|
"ffi" => permissions.ffi.request(args.library.as_deref()),
|
||||||
"hrtime" => permissions.hrtime.request(),
|
"hrtime" => permissions.hrtime.request(),
|
||||||
n => {
|
n => {
|
||||||
return Err(custom_error(
|
return Err(custom_error(
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
use crate::permissions::Permissions;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_core::op_sync;
|
|
||||||
use deno_core::Extension;
|
|
||||||
use deno_core::OpState;
|
|
||||||
use deno_core::Resource;
|
|
||||||
use deno_core::ResourceId;
|
|
||||||
use dlopen::symbor::Library;
|
|
||||||
use log::debug;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::mem;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
/// A default `init` function for plugins which mimics the way the internal
|
|
||||||
/// extensions are initalized. Plugins currently do not support all extension
|
|
||||||
/// features and are most likely not going to in the future. Currently only
|
|
||||||
/// `init_state` and `init_ops` are supported while `init_middleware` and `init_js`
|
|
||||||
/// are not. Currently the `PluginResource` does not support being closed due to
|
|
||||||
/// certain risks in unloading the dynamic library without unloading dependent
|
|
||||||
/// functions and resources.
|
|
||||||
pub type InitFn = fn() -> Extension;
|
|
||||||
|
|
||||||
pub fn init() -> Extension {
|
|
||||||
Extension::builder()
|
|
||||||
.ops(vec![("op_open_plugin", op_sync(op_open_plugin))])
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn op_open_plugin(
|
|
||||||
state: &mut OpState,
|
|
||||||
filename: String,
|
|
||||||
_: (),
|
|
||||||
) -> Result<ResourceId, AnyError> {
|
|
||||||
let filename = PathBuf::from(&filename);
|
|
||||||
|
|
||||||
super::check_unstable(state, "Deno.openPlugin");
|
|
||||||
let permissions = state.borrow_mut::<Permissions>();
|
|
||||||
permissions.plugin.check()?;
|
|
||||||
|
|
||||||
debug!("Loading Plugin: {:#?}", filename);
|
|
||||||
let plugin_lib = Library::open(filename).map(Rc::new)?;
|
|
||||||
let plugin_resource = PluginResource::new(&plugin_lib);
|
|
||||||
|
|
||||||
// Forgets the plugin_lib value to prevent segfaults when the process exits
|
|
||||||
mem::forget(plugin_lib);
|
|
||||||
|
|
||||||
let init = *unsafe { plugin_resource.0.symbol::<InitFn>("init") }?;
|
|
||||||
let rid = state.resource_table.add(plugin_resource);
|
|
||||||
let mut extension = init();
|
|
||||||
|
|
||||||
if !extension.init_js().is_empty() {
|
|
||||||
panic!("Plugins do not support loading js");
|
|
||||||
}
|
|
||||||
|
|
||||||
if extension.init_middleware().is_some() {
|
|
||||||
panic!("Plugins do not support middleware");
|
|
||||||
}
|
|
||||||
|
|
||||||
extension.init_state(state)?;
|
|
||||||
let ops = extension.init_ops().unwrap_or_default();
|
|
||||||
for (name, opfn) in ops {
|
|
||||||
state.op_table.register_op(name, opfn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(rid)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PluginResource(Rc<Library>);
|
|
||||||
|
|
||||||
impl Resource for PluginResource {
|
|
||||||
fn name(&self) -> Cow<str> {
|
|
||||||
"plugin".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close(self: Rc<Self>) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PluginResource {
|
|
||||||
fn new(lib: &Rc<Library>) -> Self {
|
|
||||||
Self(lib.clone())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::permissions::resolve_read_allowlist;
|
use crate::permissions::resolve_read_allowlist;
|
||||||
use crate::permissions::resolve_write_allowlist;
|
use crate::permissions::resolve_write_allowlist;
|
||||||
use crate::permissions::EnvDescriptor;
|
use crate::permissions::EnvDescriptor;
|
||||||
|
use crate::permissions::FfiDescriptor;
|
||||||
use crate::permissions::NetDescriptor;
|
use crate::permissions::NetDescriptor;
|
||||||
use crate::permissions::PermissionState;
|
use crate::permissions::PermissionState;
|
||||||
use crate::permissions::Permissions;
|
use crate::permissions::Permissions;
|
||||||
|
@ -218,6 +219,26 @@ fn merge_run_permission(
|
||||||
Ok(main)
|
Ok(main)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_ffi_permission(
|
||||||
|
mut main: UnaryPermission<FfiDescriptor>,
|
||||||
|
worker: Option<UnaryPermission<FfiDescriptor>>,
|
||||||
|
) -> Result<UnaryPermission<FfiDescriptor>, AnyError> {
|
||||||
|
if let Some(worker) = worker {
|
||||||
|
if (worker.global_state < main.global_state)
|
||||||
|
|| !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok())
|
||||||
|
{
|
||||||
|
return Err(custom_error(
|
||||||
|
"PermissionDenied",
|
||||||
|
"Can't escalate parent thread permissions",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
main.global_state = worker.global_state;
|
||||||
|
main.granted_list = worker.granted_list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(main)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_worker_permissions(
|
pub fn create_worker_permissions(
|
||||||
main_perms: Permissions,
|
main_perms: Permissions,
|
||||||
worker_perms: PermissionsArg,
|
worker_perms: PermissionsArg,
|
||||||
|
@ -226,7 +247,7 @@ pub fn create_worker_permissions(
|
||||||
env: merge_env_permission(main_perms.env, worker_perms.env)?,
|
env: merge_env_permission(main_perms.env, worker_perms.env)?,
|
||||||
hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?,
|
hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?,
|
||||||
net: merge_net_permission(main_perms.net, worker_perms.net)?,
|
net: merge_net_permission(main_perms.net, worker_perms.net)?,
|
||||||
plugin: merge_boolean_permission(main_perms.plugin, worker_perms.plugin)?,
|
ffi: merge_ffi_permission(main_perms.ffi, worker_perms.ffi)?,
|
||||||
read: merge_read_permission(main_perms.read, worker_perms.read)?,
|
read: merge_read_permission(main_perms.read, worker_perms.read)?,
|
||||||
run: merge_run_permission(main_perms.run, worker_perms.run)?,
|
run: merge_run_permission(main_perms.run, worker_perms.run)?,
|
||||||
write: merge_write_permission(main_perms.write, worker_perms.write)?,
|
write: merge_write_permission(main_perms.write, worker_perms.write)?,
|
||||||
|
@ -241,8 +262,8 @@ pub struct PermissionsArg {
|
||||||
hrtime: Option<PermissionState>,
|
hrtime: Option<PermissionState>,
|
||||||
#[serde(default, deserialize_with = "as_unary_net_permission")]
|
#[serde(default, deserialize_with = "as_unary_net_permission")]
|
||||||
net: Option<UnaryPermission<NetDescriptor>>,
|
net: Option<UnaryPermission<NetDescriptor>>,
|
||||||
#[serde(default, deserialize_with = "as_permission_state")]
|
#[serde(default, deserialize_with = "as_unary_ffi_permission")]
|
||||||
plugin: Option<PermissionState>,
|
ffi: Option<UnaryPermission<FfiDescriptor>>,
|
||||||
#[serde(default, deserialize_with = "as_unary_read_permission")]
|
#[serde(default, deserialize_with = "as_unary_read_permission")]
|
||||||
read: Option<UnaryPermission<ReadDescriptor>>,
|
read: Option<UnaryPermission<ReadDescriptor>>,
|
||||||
#[serde(default, deserialize_with = "as_unary_run_permission")]
|
#[serde(default, deserialize_with = "as_unary_run_permission")]
|
||||||
|
@ -414,6 +435,22 @@ where
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_unary_ffi_permission<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<UnaryPermission<FfiDescriptor>>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value: UnaryPermissionBase =
|
||||||
|
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
|
||||||
|
|
||||||
|
Ok(Some(UnaryPermission::<FfiDescriptor> {
|
||||||
|
global_state: value.global_state,
|
||||||
|
granted_list: value.paths.into_iter().map(FfiDescriptor).collect(),
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreateWorkerArgs {
|
pub struct CreateWorkerArgs {
|
||||||
|
|
|
@ -202,6 +202,9 @@ pub struct EnvDescriptor(pub String);
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
|
||||||
pub struct RunDescriptor(pub String);
|
pub struct RunDescriptor(pub String);
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
|
||||||
|
pub struct FfiDescriptor(pub String);
|
||||||
|
|
||||||
impl UnaryPermission<ReadDescriptor> {
|
impl UnaryPermission<ReadDescriptor> {
|
||||||
pub fn query(&self, path: Option<&Path>) -> PermissionState {
|
pub fn query(&self, path: Option<&Path>) -> PermissionState {
|
||||||
let path = path.map(|p| resolve_from_cwd(p).unwrap());
|
let path = path.map(|p| resolve_from_cwd(p).unwrap());
|
||||||
|
@ -787,6 +790,104 @@ impl UnaryPermission<RunDescriptor> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UnaryPermission<FfiDescriptor> {
|
||||||
|
pub fn query(&self, lib: Option<&str>) -> PermissionState {
|
||||||
|
if self.global_state == PermissionState::Denied
|
||||||
|
&& match lib {
|
||||||
|
None => true,
|
||||||
|
Some(lib) => self.denied_list.iter().any(|lib_| lib_.0 == lib),
|
||||||
|
}
|
||||||
|
{
|
||||||
|
PermissionState::Denied
|
||||||
|
} else if self.global_state == PermissionState::Granted
|
||||||
|
|| match lib {
|
||||||
|
None => false,
|
||||||
|
Some(lib) => self.granted_list.iter().any(|lib_| lib_.0 == lib),
|
||||||
|
}
|
||||||
|
{
|
||||||
|
PermissionState::Granted
|
||||||
|
} else {
|
||||||
|
PermissionState::Prompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request(&mut self, lib: Option<&str>) -> PermissionState {
|
||||||
|
if let Some(lib) = lib {
|
||||||
|
let state = self.query(Some(lib));
|
||||||
|
if state == PermissionState::Prompt {
|
||||||
|
if permission_prompt(&format!("ffi access to \"{}\"", lib)) {
|
||||||
|
self.granted_list.retain(|lib_| lib_.0 != lib);
|
||||||
|
self.granted_list.insert(FfiDescriptor(lib.to_string()));
|
||||||
|
PermissionState::Granted
|
||||||
|
} else {
|
||||||
|
self.denied_list.retain(|lib_| lib_.0 != lib);
|
||||||
|
self.denied_list.insert(FfiDescriptor(lib.to_string()));
|
||||||
|
self.global_state = PermissionState::Denied;
|
||||||
|
PermissionState::Denied
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let state = self.query(None);
|
||||||
|
if state == PermissionState::Prompt {
|
||||||
|
if permission_prompt("ffi access") {
|
||||||
|
self.granted_list.clear();
|
||||||
|
self.global_state = PermissionState::Granted;
|
||||||
|
PermissionState::Granted
|
||||||
|
} else {
|
||||||
|
self.global_state = PermissionState::Denied;
|
||||||
|
PermissionState::Denied
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke(&mut self, lib: Option<&str>) -> PermissionState {
|
||||||
|
if let Some(lib) = lib {
|
||||||
|
self.granted_list.retain(|lib_| lib_.0 != lib);
|
||||||
|
} else {
|
||||||
|
self.granted_list.clear();
|
||||||
|
if self.global_state == PermissionState::Granted {
|
||||||
|
self.global_state = PermissionState::Prompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.query(lib)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&mut self, lib: &str) -> Result<(), AnyError> {
|
||||||
|
let (result, prompted) = self.query(Some(lib)).check(
|
||||||
|
self.name,
|
||||||
|
Some(&format!("\"{}\"", lib)),
|
||||||
|
self.prompt,
|
||||||
|
);
|
||||||
|
if prompted {
|
||||||
|
if result.is_ok() {
|
||||||
|
self.granted_list.insert(FfiDescriptor(lib.to_string()));
|
||||||
|
} else {
|
||||||
|
self.denied_list.insert(FfiDescriptor(lib.to_string()));
|
||||||
|
self.global_state = PermissionState::Denied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_all(&mut self) -> Result<(), AnyError> {
|
||||||
|
let (result, prompted) =
|
||||||
|
self.query(None).check(self.name, Some("all"), self.prompt);
|
||||||
|
if prompted {
|
||||||
|
if result.is_ok() {
|
||||||
|
self.global_state = PermissionState::Granted;
|
||||||
|
} else {
|
||||||
|
self.global_state = PermissionState::Denied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct Permissions {
|
pub struct Permissions {
|
||||||
pub read: UnaryPermission<ReadDescriptor>,
|
pub read: UnaryPermission<ReadDescriptor>,
|
||||||
|
@ -794,7 +895,7 @@ pub struct Permissions {
|
||||||
pub net: UnaryPermission<NetDescriptor>,
|
pub net: UnaryPermission<NetDescriptor>,
|
||||||
pub env: UnaryPermission<EnvDescriptor>,
|
pub env: UnaryPermission<EnvDescriptor>,
|
||||||
pub run: UnaryPermission<RunDescriptor>,
|
pub run: UnaryPermission<RunDescriptor>,
|
||||||
pub plugin: UnitPermission,
|
pub ffi: UnaryPermission<FfiDescriptor>,
|
||||||
pub hrtime: UnitPermission,
|
pub hrtime: UnitPermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,7 +904,7 @@ pub struct PermissionsOptions {
|
||||||
pub allow_env: Option<Vec<String>>,
|
pub allow_env: Option<Vec<String>>,
|
||||||
pub allow_hrtime: bool,
|
pub allow_hrtime: bool,
|
||||||
pub allow_net: Option<Vec<String>>,
|
pub allow_net: Option<Vec<String>>,
|
||||||
pub allow_plugin: bool,
|
pub allow_ffi: Option<Vec<String>>,
|
||||||
pub allow_read: Option<Vec<PathBuf>>,
|
pub allow_read: Option<Vec<PathBuf>>,
|
||||||
pub allow_run: Option<Vec<String>>,
|
pub allow_run: Option<Vec<String>>,
|
||||||
pub allow_write: Option<Vec<PathBuf>>,
|
pub allow_write: Option<Vec<PathBuf>>,
|
||||||
|
@ -904,8 +1005,21 @@ impl Permissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_plugin(state: bool, prompt: bool) -> UnitPermission {
|
pub fn new_ffi(
|
||||||
unit_permission_from_flag_bool(state, "plugin", "open a plugin", prompt)
|
state: &Option<Vec<String>>,
|
||||||
|
prompt: bool,
|
||||||
|
) -> UnaryPermission<FfiDescriptor> {
|
||||||
|
UnaryPermission::<FfiDescriptor> {
|
||||||
|
name: "ffi",
|
||||||
|
description: "load a dynamic library",
|
||||||
|
global_state: global_state_from_option(state),
|
||||||
|
granted_list: state
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.iter().map(|x| FfiDescriptor(x.clone())).collect())
|
||||||
|
.unwrap_or_else(HashSet::new),
|
||||||
|
denied_list: Default::default(),
|
||||||
|
prompt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_hrtime(state: bool, prompt: bool) -> UnitPermission {
|
pub fn new_hrtime(state: bool, prompt: bool) -> UnitPermission {
|
||||||
|
@ -924,7 +1038,7 @@ impl Permissions {
|
||||||
net: Permissions::new_net(&opts.allow_net, opts.prompt),
|
net: Permissions::new_net(&opts.allow_net, opts.prompt),
|
||||||
env: Permissions::new_env(&opts.allow_env, opts.prompt),
|
env: Permissions::new_env(&opts.allow_env, opts.prompt),
|
||||||
run: Permissions::new_run(&opts.allow_run, opts.prompt),
|
run: Permissions::new_run(&opts.allow_run, opts.prompt),
|
||||||
plugin: Permissions::new_plugin(opts.allow_plugin, opts.prompt),
|
ffi: Permissions::new_ffi(&opts.allow_ffi, opts.prompt),
|
||||||
hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.prompt),
|
hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.prompt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -936,7 +1050,7 @@ impl Permissions {
|
||||||
net: Permissions::new_net(&Some(vec![]), false),
|
net: Permissions::new_net(&Some(vec![]), false),
|
||||||
env: Permissions::new_env(&Some(vec![]), false),
|
env: Permissions::new_env(&Some(vec![]), false),
|
||||||
run: Permissions::new_run(&Some(vec![]), false),
|
run: Permissions::new_run(&Some(vec![]), false),
|
||||||
plugin: Permissions::new_plugin(true, false),
|
ffi: Permissions::new_ffi(&Some(vec![]), false),
|
||||||
hrtime: Permissions::new_hrtime(true, false),
|
hrtime: Permissions::new_hrtime(true, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1005,6 +1119,12 @@ impl deno_websocket::WebSocketPermissions for Permissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl deno_ffi::FfiPermissions for Permissions {
|
||||||
|
fn check(&mut self, path: &str) -> Result<(), AnyError> {
|
||||||
|
self.ffi.check(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn unit_permission_from_flag_bool(
|
fn unit_permission_from_flag_bool(
|
||||||
flag: bool,
|
flag: bool,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -1457,9 +1577,9 @@ mod tests {
|
||||||
global_state: PermissionState::Prompt,
|
global_state: PermissionState::Prompt,
|
||||||
..Permissions::new_run(&Some(svec!["deno"]), false)
|
..Permissions::new_run(&Some(svec!["deno"]), false)
|
||||||
},
|
},
|
||||||
plugin: UnitPermission {
|
ffi: UnaryPermission {
|
||||||
state: PermissionState::Prompt,
|
global_state: PermissionState::Prompt,
|
||||||
..Default::default()
|
..Permissions::new_ffi(&Some(svec!["deno"]), false)
|
||||||
},
|
},
|
||||||
hrtime: UnitPermission {
|
hrtime: UnitPermission {
|
||||||
state: PermissionState::Prompt,
|
state: PermissionState::Prompt,
|
||||||
|
@ -1490,8 +1610,10 @@ mod tests {
|
||||||
assert_eq!(perms1.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
assert_eq!(perms1.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
|
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
|
||||||
assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
assert_eq!(perms1.plugin.query(), PermissionState::Granted);
|
assert_eq!(perms1.ffi.query(None), PermissionState::Granted);
|
||||||
assert_eq!(perms2.plugin.query(), PermissionState::Prompt);
|
assert_eq!(perms1.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
|
assert_eq!(perms2.ffi.query(None), PermissionState::Prompt);
|
||||||
|
assert_eq!(perms2.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
assert_eq!(perms1.hrtime.query(), PermissionState::Granted);
|
assert_eq!(perms1.hrtime.query(), PermissionState::Granted);
|
||||||
assert_eq!(perms2.hrtime.query(), PermissionState::Prompt);
|
assert_eq!(perms2.hrtime.query(), PermissionState::Prompt);
|
||||||
};
|
};
|
||||||
|
@ -1528,9 +1650,10 @@ mod tests {
|
||||||
set_prompt_result(false);
|
set_prompt_result(false);
|
||||||
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
|
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
set_prompt_result(true);
|
set_prompt_result(true);
|
||||||
assert_eq!(perms.plugin.request(), PermissionState::Granted);
|
assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
|
assert_eq!(perms.ffi.query(None), PermissionState::Prompt);
|
||||||
set_prompt_result(false);
|
set_prompt_result(false);
|
||||||
assert_eq!(perms.plugin.request(), PermissionState::Granted);
|
assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
|
||||||
set_prompt_result(false);
|
set_prompt_result(false);
|
||||||
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
|
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
|
||||||
set_prompt_result(true);
|
set_prompt_result(true);
|
||||||
|
@ -1561,9 +1684,9 @@ mod tests {
|
||||||
global_state: PermissionState::Prompt,
|
global_state: PermissionState::Prompt,
|
||||||
..Permissions::new_run(&Some(svec!["deno"]), false)
|
..Permissions::new_run(&Some(svec!["deno"]), false)
|
||||||
},
|
},
|
||||||
plugin: UnitPermission {
|
ffi: UnaryPermission {
|
||||||
state: PermissionState::Prompt,
|
global_state: PermissionState::Prompt,
|
||||||
..Default::default()
|
..Permissions::new_ffi(&Some(svec!["deno"]), false)
|
||||||
},
|
},
|
||||||
hrtime: UnitPermission {
|
hrtime: UnitPermission {
|
||||||
state: PermissionState::Denied,
|
state: PermissionState::Denied,
|
||||||
|
@ -1582,7 +1705,7 @@ mod tests {
|
||||||
assert_eq!(perms.net.revoke(Some(&("127.0.0.1", None))), PermissionState::Prompt);
|
assert_eq!(perms.net.revoke(Some(&("127.0.0.1", None))), PermissionState::Prompt);
|
||||||
assert_eq!(perms.env.revoke(Some(&"HOME".to_string())), PermissionState::Prompt);
|
assert_eq!(perms.env.revoke(Some(&"HOME".to_string())), PermissionState::Prompt);
|
||||||
assert_eq!(perms.run.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
|
assert_eq!(perms.run.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
|
||||||
assert_eq!(perms.plugin.revoke(), PermissionState::Prompt);
|
assert_eq!(perms.ffi.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
|
||||||
assert_eq!(perms.hrtime.revoke(), PermissionState::Denied);
|
assert_eq!(perms.hrtime.revoke(), PermissionState::Denied);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1595,7 +1718,7 @@ mod tests {
|
||||||
net: Permissions::new_net(&None, true),
|
net: Permissions::new_net(&None, true),
|
||||||
env: Permissions::new_env(&None, true),
|
env: Permissions::new_env(&None, true),
|
||||||
run: Permissions::new_run(&None, true),
|
run: Permissions::new_run(&None, true),
|
||||||
plugin: Permissions::new_plugin(false, true),
|
ffi: Permissions::new_ffi(&None, true),
|
||||||
hrtime: Permissions::new_hrtime(false, true),
|
hrtime: Permissions::new_hrtime(false, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1648,7 +1771,7 @@ mod tests {
|
||||||
net: Permissions::new_net(&None, true),
|
net: Permissions::new_net(&None, true),
|
||||||
env: Permissions::new_env(&None, true),
|
env: Permissions::new_env(&None, true),
|
||||||
run: Permissions::new_run(&None, true),
|
run: Permissions::new_run(&None, true),
|
||||||
plugin: Permissions::new_plugin(false, true),
|
ffi: Permissions::new_ffi(&None, true),
|
||||||
hrtime: Permissions::new_hrtime(false, true),
|
hrtime: Permissions::new_hrtime(false, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -315,6 +315,8 @@ impl WebWorker {
|
||||||
deno_crypto::init(options.seed),
|
deno_crypto::init(options.seed),
|
||||||
deno_webgpu::init(options.unstable),
|
deno_webgpu::init(options.unstable),
|
||||||
deno_timers::init::<Permissions>(),
|
deno_timers::init::<Permissions>(),
|
||||||
|
// ffi
|
||||||
|
deno_ffi::init::<Permissions>(options.unstable),
|
||||||
// Metrics
|
// Metrics
|
||||||
metrics::init(),
|
metrics::init(),
|
||||||
// Permissions ext (worker specific state)
|
// Permissions ext (worker specific state)
|
||||||
|
@ -340,7 +342,6 @@ impl WebWorker {
|
||||||
),
|
),
|
||||||
ops::os::init(),
|
ops::os::init(),
|
||||||
ops::permissions::init(),
|
ops::permissions::init(),
|
||||||
ops::plugin::init(),
|
|
||||||
ops::process::init(),
|
ops::process::init(),
|
||||||
ops::signal::init(),
|
ops::signal::init(),
|
||||||
ops::tty::init(),
|
ops::tty::init(),
|
||||||
|
|
|
@ -115,6 +115,8 @@ impl MainWorker {
|
||||||
),
|
),
|
||||||
deno_webgpu::init(options.unstable),
|
deno_webgpu::init(options.unstable),
|
||||||
deno_timers::init::<Permissions>(),
|
deno_timers::init::<Permissions>(),
|
||||||
|
// ffi
|
||||||
|
deno_ffi::init::<Permissions>(options.unstable),
|
||||||
// Metrics
|
// Metrics
|
||||||
metrics::init(),
|
metrics::init(),
|
||||||
// Runtime ops
|
// Runtime ops
|
||||||
|
@ -127,7 +129,6 @@ impl MainWorker {
|
||||||
deno_net::init::<Permissions>(options.ca_data.clone(), options.unstable),
|
deno_net::init::<Permissions>(options.ca_data.clone(), options.unstable),
|
||||||
ops::os::init(),
|
ops::os::init(),
|
||||||
ops::permissions::init(),
|
ops::permissions::init(),
|
||||||
ops::plugin::init(),
|
|
||||||
ops::process::init(),
|
ops::process::init(),
|
||||||
ops::signal::init(),
|
ops::signal::init(),
|
||||||
ops::tty::init(),
|
ops::tty::init(),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "test_plugin"
|
name = "test_ffi"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
authors = ["the deno authors"]
|
authors = ["the deno authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
@ -10,10 +10,5 @@ publish = false
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
deno_core = { path = "../core" }
|
|
||||||
futures = "0.3.15"
|
|
||||||
serde = "1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test_util = { path = "../test_util" }
|
test_util = { path = "../test_util" }
|
1
test_ffi/README.md
Normal file
1
test_ffi/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# `test_ffi` crate
|
9
test_ffi/src/lib.rs
Normal file
9
test_ffi/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn print_something() {
|
||||||
|
println!("something");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn add(a: u32, b: u32) -> u32 {
|
||||||
|
a + b
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ const BUILD_VARIANT: &str = "release";
|
||||||
fn basic() {
|
fn basic() {
|
||||||
let mut build_plugin_base = Command::new("cargo");
|
let mut build_plugin_base = Command::new("cargo");
|
||||||
let mut build_plugin =
|
let mut build_plugin =
|
||||||
build_plugin_base.arg("build").arg("-p").arg("test_plugin");
|
build_plugin_base.arg("build").arg("-p").arg("test_ffi");
|
||||||
if BUILD_VARIANT == "release" {
|
if BUILD_VARIANT == "release" {
|
||||||
build_plugin = build_plugin.arg("--release");
|
build_plugin = build_plugin.arg("--release");
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,11 @@ fn basic() {
|
||||||
assert!(build_plugin_output.status.success());
|
assert!(build_plugin_output.status.success());
|
||||||
let output = deno_cmd()
|
let output = deno_cmd()
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("--allow-plugin")
|
.arg("--allow-ffi")
|
||||||
|
.arg("--allow-read")
|
||||||
.arg("--unstable")
|
.arg("--unstable")
|
||||||
.arg("tests/test.js")
|
.arg("tests/test.js")
|
||||||
.arg(BUILD_VARIANT)
|
.env("NO_COLOR", "1")
|
||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||||||
|
@ -36,23 +37,9 @@ fn basic() {
|
||||||
println!("{:?}", output.status);
|
println!("{:?}", output.status);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let expected = "\
|
let expected = "\
|
||||||
Plugin rid: 3\n\
|
something\n\
|
||||||
Hello from sync plugin op.\n\
|
579\n\
|
||||||
args: TestArgs { val: \"1\" }\n\
|
Correct number of resources\n";
|
||||||
zero_copy: test\n\
|
|
||||||
op_test_sync returned: test\n\
|
|
||||||
Hello from async plugin op.\n\
|
|
||||||
args: TestArgs { val: \"1\" }\n\
|
|
||||||
zero_copy: 123\n\
|
|
||||||
op_test_async returned: test\n\
|
|
||||||
Hello from resource_table.add plugin op.\n\
|
|
||||||
TestResource rid: 4\n\
|
|
||||||
Hello from resource_table.get plugin op.\n\
|
|
||||||
TestResource get value: hello plugin!\n\
|
|
||||||
Hello from sync plugin op.\n\
|
|
||||||
args: TestArgs { val: \"1\" }\n\
|
|
||||||
Ops completed count is correct!\n\
|
|
||||||
Ops dispatched count is correct!\n";
|
|
||||||
assert_eq!(stdout, expected);
|
assert_eq!(stdout, expected);
|
||||||
assert_eq!(stderr, "");
|
assert_eq!(stderr, "");
|
||||||
}
|
}
|
33
test_ffi/tests/test.js
Normal file
33
test_ffi/tests/test.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
|
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
|
||||||
|
const [libPrefix, libSuffix] = {
|
||||||
|
darwin: ["lib", "dylib"],
|
||||||
|
linux: ["lib", "so"],
|
||||||
|
windows: ["", "dll"],
|
||||||
|
}[Deno.build.os];
|
||||||
|
const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
|
||||||
|
|
||||||
|
const resourcesPre = Deno.resources();
|
||||||
|
const dylib = Deno.dlopen(libPath, {
|
||||||
|
"print_something": { parameters: [], result: "void" },
|
||||||
|
"add": { parameters: ["u32", "u32"], result: "u32" },
|
||||||
|
});
|
||||||
|
|
||||||
|
dylib.symbols.print_something();
|
||||||
|
console.log(dylib.symbols.add(123, 456));
|
||||||
|
|
||||||
|
dylib.close();
|
||||||
|
const resourcesPost = Deno.resources();
|
||||||
|
|
||||||
|
const preStr = JSON.stringify(resourcesPre, null, 2);
|
||||||
|
const postStr = JSON.stringify(resourcesPost, null, 2);
|
||||||
|
if (preStr !== postStr) {
|
||||||
|
throw new Error(
|
||||||
|
`Difference in open resources before dlopen and after closing:
|
||||||
|
Before: ${preStr}
|
||||||
|
After: ${postStr}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log("Correct number of resources");
|
|
@ -1,9 +0,0 @@
|
||||||
# `test_plugin` crate
|
|
||||||
|
|
||||||
## To run this test manually
|
|
||||||
|
|
||||||
```
|
|
||||||
cd test_plugin
|
|
||||||
|
|
||||||
../target/debug/deno run --unstable --allow-plugin tests/test.js debug
|
|
||||||
```
|
|
|
@ -1,114 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use deno_core::error::bad_resource_id;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_core::op_async;
|
|
||||||
use deno_core::op_sync;
|
|
||||||
use deno_core::Extension;
|
|
||||||
use deno_core::OpState;
|
|
||||||
use deno_core::Resource;
|
|
||||||
use deno_core::ResourceId;
|
|
||||||
use deno_core::ZeroCopyBuf;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn init() -> Extension {
|
|
||||||
Extension::builder()
|
|
||||||
.ops(vec![
|
|
||||||
("op_test_sync", op_sync(op_test_sync)),
|
|
||||||
("op_test_async", op_async(op_test_async)),
|
|
||||||
(
|
|
||||||
"op_test_resource_table_add",
|
|
||||||
op_sync(op_test_resource_table_add),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"op_test_resource_table_get",
|
|
||||||
op_sync(op_test_resource_table_get),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct TestArgs {
|
|
||||||
val: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn op_test_sync(
|
|
||||||
_state: &mut OpState,
|
|
||||||
args: TestArgs,
|
|
||||||
zero_copy: Option<ZeroCopyBuf>,
|
|
||||||
) -> Result<String, AnyError> {
|
|
||||||
println!("Hello from sync plugin op.");
|
|
||||||
|
|
||||||
println!("args: {:?}", args);
|
|
||||||
|
|
||||||
if let Some(buf) = zero_copy {
|
|
||||||
let buf_str = std::str::from_utf8(&buf[..])?;
|
|
||||||
println!("zero_copy: {}", buf_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("test".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn op_test_async(
|
|
||||||
_state: Rc<RefCell<OpState>>,
|
|
||||||
args: TestArgs,
|
|
||||||
zero_copy: Option<ZeroCopyBuf>,
|
|
||||||
) -> Result<String, AnyError> {
|
|
||||||
println!("Hello from async plugin op.");
|
|
||||||
|
|
||||||
println!("args: {:?}", args);
|
|
||||||
|
|
||||||
if let Some(buf) = zero_copy {
|
|
||||||
let buf_str = std::str::from_utf8(&buf[..])?;
|
|
||||||
println!("zero_copy: {}", buf_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = futures::channel::oneshot::channel::<Result<(), ()>>();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
tx.send(Ok(())).unwrap();
|
|
||||||
});
|
|
||||||
assert!(rx.await.is_ok());
|
|
||||||
|
|
||||||
Ok("test".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestResource(String);
|
|
||||||
impl Resource for TestResource {
|
|
||||||
fn name(&self) -> Cow<str> {
|
|
||||||
"TestResource".into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn op_test_resource_table_add(
|
|
||||||
state: &mut OpState,
|
|
||||||
text: String,
|
|
||||||
_: (),
|
|
||||||
) -> Result<u32, AnyError> {
|
|
||||||
println!("Hello from resource_table.add plugin op.");
|
|
||||||
|
|
||||||
Ok(state.resource_table.add(TestResource(text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn op_test_resource_table_get(
|
|
||||||
state: &mut OpState,
|
|
||||||
rid: ResourceId,
|
|
||||||
_: (),
|
|
||||||
) -> Result<String, AnyError> {
|
|
||||||
println!("Hello from resource_table.get plugin op.");
|
|
||||||
|
|
||||||
Ok(
|
|
||||||
state
|
|
||||||
.resource_table
|
|
||||||
.get::<TestResource>(rid)
|
|
||||||
.ok_or_else(bad_resource_id)?
|
|
||||||
.0
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
// deno-lint-ignore-file
|
|
||||||
|
|
||||||
const filenameBase = "test_plugin";
|
|
||||||
|
|
||||||
let filenameSuffix = ".so";
|
|
||||||
let filenamePrefix = "lib";
|
|
||||||
|
|
||||||
if (Deno.build.os === "windows") {
|
|
||||||
filenameSuffix = ".dll";
|
|
||||||
filenamePrefix = "";
|
|
||||||
} else if (Deno.build.os === "darwin") {
|
|
||||||
filenameSuffix = ".dylib";
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = `../target/${
|
|
||||||
Deno.args[0]
|
|
||||||
}/${filenamePrefix}${filenameBase}${filenameSuffix}`;
|
|
||||||
|
|
||||||
const resourcesPre = Deno.resources();
|
|
||||||
|
|
||||||
const pluginRid = Deno.openPlugin(filename);
|
|
||||||
console.log(`Plugin rid: ${pluginRid}`);
|
|
||||||
|
|
||||||
const {
|
|
||||||
op_test_sync,
|
|
||||||
op_test_async,
|
|
||||||
op_test_resource_table_add,
|
|
||||||
op_test_resource_table_get,
|
|
||||||
} = Deno.core.ops();
|
|
||||||
|
|
||||||
if (
|
|
||||||
op_test_sync === null ||
|
|
||||||
op_test_async === null ||
|
|
||||||
op_test_resource_table_add === null ||
|
|
||||||
op_test_resource_table_get === null
|
|
||||||
) {
|
|
||||||
throw new Error("Not all expected ops were registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTestSync() {
|
|
||||||
const result = Deno.core.opSync(
|
|
||||||
"op_test_sync",
|
|
||||||
{ val: "1" },
|
|
||||||
new Uint8Array([116, 101, 115, 116]),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`op_test_sync returned: ${result}`);
|
|
||||||
|
|
||||||
if (result !== "test") {
|
|
||||||
throw new Error("op_test_sync returned an unexpected value!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runTestAsync() {
|
|
||||||
const promise = Deno.core.opAsync(
|
|
||||||
"op_test_async",
|
|
||||||
{ val: "1" },
|
|
||||||
new Uint8Array([49, 50, 51]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!(promise instanceof Promise)) {
|
|
||||||
throw new Error("Expected promise!");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await promise;
|
|
||||||
console.log(`op_test_async returned: ${result}`);
|
|
||||||
|
|
||||||
if (result !== "test") {
|
|
||||||
throw new Error("op_test_async promise resolved to an unexpected value!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTestResourceTable() {
|
|
||||||
const expect = "hello plugin!";
|
|
||||||
|
|
||||||
const testRid = Deno.core.opSync("op_test_resource_table_add", expect);
|
|
||||||
console.log(`TestResource rid: ${testRid}`);
|
|
||||||
|
|
||||||
if (testRid === null || Deno.resources()[testRid] !== "TestResource") {
|
|
||||||
throw new Error("TestResource was not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
const testValue = Deno.core.opSync("op_test_resource_table_get", testRid);
|
|
||||||
console.log(`TestResource get value: ${testValue}`);
|
|
||||||
|
|
||||||
if (testValue !== expect) {
|
|
||||||
throw new Error("Did not get correct resource value!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Deno.close(testRid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTestOpCount() {
|
|
||||||
const start = Deno.metrics();
|
|
||||||
|
|
||||||
Deno.core.opSync("op_test_sync", { val: "1" });
|
|
||||||
|
|
||||||
const end = Deno.metrics();
|
|
||||||
|
|
||||||
if (end.opsCompleted - start.opsCompleted !== 1) {
|
|
||||||
throw new Error("The opsCompleted metric is not correct!");
|
|
||||||
}
|
|
||||||
console.log("Ops completed count is correct!");
|
|
||||||
|
|
||||||
if (end.opsDispatched - start.opsDispatched !== 1) {
|
|
||||||
throw new Error("The opsDispatched metric is not correct!");
|
|
||||||
}
|
|
||||||
console.log("Ops dispatched count is correct!");
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTestPluginClose() {
|
|
||||||
// Closing does not yet work
|
|
||||||
Deno.close(pluginRid);
|
|
||||||
|
|
||||||
const resourcesPost = Deno.resources();
|
|
||||||
|
|
||||||
const preStr = JSON.stringify(resourcesPre, null, 2);
|
|
||||||
const postStr = JSON.stringify(resourcesPost, null, 2);
|
|
||||||
if (preStr !== postStr) {
|
|
||||||
throw new Error(
|
|
||||||
`Difference in open resources before openPlugin and after Plugin.close():
|
|
||||||
Before: ${preStr}
|
|
||||||
After: ${postStr}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log("Correct number of resources");
|
|
||||||
}
|
|
||||||
|
|
||||||
runTestSync();
|
|
||||||
await runTestAsync();
|
|
||||||
runTestResourceTable();
|
|
||||||
|
|
||||||
runTestOpCount();
|
|
||||||
// runTestPluginClose();
|
|
Loading…
Reference in a new issue