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 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 \
|
||||
--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 \
|
||||
http://azure.archive.ubuntu.com/ubuntu
|
||||
sudo mount --rbind /dev /sysroot/dev
|
||||
|
|
52
Cargo.lock
generated
52
Cargo.lock
generated
|
@ -12,6 +12,12 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "abort_on_panic"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116"
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
|
@ -568,6 +574,7 @@ dependencies = [
|
|||
"deno_crypto",
|
||||
"deno_doc",
|
||||
"deno_fetch",
|
||||
"deno_ffi",
|
||||
"deno_http",
|
||||
"deno_lint",
|
||||
"deno_net",
|
||||
|
@ -629,6 +636,27 @@ dependencies = [
|
|||
"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]]
|
||||
name = "deno_bench_util"
|
||||
version = "0.7.0"
|
||||
|
@ -725,6 +753,16 @@ dependencies = [
|
|||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deno-libffi",
|
||||
"deno_core",
|
||||
"dlopen",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_http"
|
||||
version = "0.4.0"
|
||||
|
@ -786,6 +824,7 @@ dependencies = [
|
|||
"deno_core",
|
||||
"deno_crypto",
|
||||
"deno_fetch",
|
||||
"deno_ffi",
|
||||
"deno_http",
|
||||
"deno_net",
|
||||
"deno_timers",
|
||||
|
@ -2106,6 +2145,12 @@ dependencies = [
|
|||
"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]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
|
@ -3831,12 +3876,9 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_plugin"
|
||||
version = "0.0.1"
|
||||
name = "test_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"futures",
|
||||
"serde",
|
||||
"test_util",
|
||||
]
|
||||
|
||||
|
|
|
@ -6,12 +6,13 @@ members = [
|
|||
"cli",
|
||||
"core",
|
||||
"runtime",
|
||||
"test_plugin",
|
||||
"test_ffi",
|
||||
"test_util",
|
||||
"extensions/broadcast_channel",
|
||||
"extensions/console",
|
||||
"extensions/crypto",
|
||||
"extensions/fetch",
|
||||
"extensions/ffi",
|
||||
"extensions/http",
|
||||
"extensions/net",
|
||||
"extensions/timers",
|
||||
|
|
|
@ -25,6 +25,7 @@ deno_console = { version = "0.13.0", path = "../extensions/console" }
|
|||
deno_core = { version = "0.95.0", path = "../core" }
|
||||
deno_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||
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_net = { version = "0.4.0", path = "../extensions/net" }
|
||||
deno_timers = { version = "0.11.0", path = "../extensions/timers" }
|
||||
|
|
|
@ -54,7 +54,7 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
|||
"listen",
|
||||
"listenDatagram",
|
||||
"loadavg",
|
||||
"openPlugin",
|
||||
"dlopen",
|
||||
"osRelease",
|
||||
"ppid",
|
||||
"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"
|
||||
| "net"
|
||||
| "env"
|
||||
| "plugin"
|
||||
| "ffi"
|
||||
| "hrtime";
|
||||
|
||||
/** The current status of the permission. */
|
||||
|
@ -2167,8 +2167,8 @@ declare namespace Deno {
|
|||
variable?: string;
|
||||
}
|
||||
|
||||
export interface PluginPermissionDescriptor {
|
||||
name: "plugin";
|
||||
export interface FFIPermissionDescriptor {
|
||||
name: "ffi";
|
||||
}
|
||||
|
||||
export interface HrtimePermissionDescriptor {
|
||||
|
@ -2183,7 +2183,7 @@ declare namespace Deno {
|
|||
| WritePermissionDescriptor
|
||||
| NetPermissionDescriptor
|
||||
| EnvPermissionDescriptor
|
||||
| PluginPermissionDescriptor
|
||||
| FFIPermissionDescriptor
|
||||
| HrtimePermissionDescriptor;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** **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.
|
||||
*
|
||||
* ```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
|
||||
* Opens a dynamic library and registers symbols
|
||||
*/
|
||||
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. */
|
||||
export enum DiagnosticCategory {
|
||||
|
@ -1043,14 +1051,14 @@ declare namespace Deno {
|
|||
*/
|
||||
net?: "inherit" | boolean | string[];
|
||||
|
||||
/** Specifies if the `plugin` permission should be requested or revoked.
|
||||
* If set to `"inherit"`, the current `plugin` permission will be inherited.
|
||||
* If set to `true`, the global `plugin` permission will be requested.
|
||||
* If set to `false`, the global `plugin` permission will be revoked.
|
||||
/** Specifies if the `ffi` permission should be requested or revoked.
|
||||
* If set to `"inherit"`, the current `ffi` permission will be inherited.
|
||||
* If set to `true`, the global `ffi` permission will be requested.
|
||||
* If set to `false`, the global `ffi` permission will be revoked.
|
||||
*
|
||||
* Defaults to "inherit".
|
||||
*/
|
||||
plugin?: "inherit" | boolean;
|
||||
ffi?: "inherit" | boolean;
|
||||
|
||||
/** Specifies if the `read` permission should be requested or revoked.
|
||||
* 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"]`.
|
||||
*/
|
||||
net?: "inherit" | boolean | string[];
|
||||
plugin?: "inherit" | boolean;
|
||||
ffi?: "inherit" | boolean;
|
||||
read?: "inherit" | boolean | Array<string | URL>;
|
||||
run?: "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_hrtime: bool,
|
||||
pub allow_net: Option<Vec<String>>,
|
||||
pub allow_plugin: bool,
|
||||
pub allow_ffi: Option<Vec<String>>,
|
||||
pub allow_read: Option<Vec<PathBuf>>,
|
||||
pub allow_run: Option<Vec<String>>,
|
||||
pub allow_write: Option<Vec<PathBuf>>,
|
||||
|
@ -235,8 +235,15 @@ impl Flags {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
if self.allow_plugin {
|
||||
args.push("--allow-plugin".to_string());
|
||||
match &self.allow_ffi {
|
||||
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 {
|
||||
|
@ -253,7 +260,7 @@ impl From<Flags> for PermissionsOptions {
|
|||
allow_env: flags.allow_env,
|
||||
allow_hrtime: flags.allow_hrtime,
|
||||
allow_net: flags.allow_net,
|
||||
allow_plugin: flags.allow_plugin,
|
||||
allow_ffi: flags.allow_ffi,
|
||||
allow_read: flags.allow_read,
|
||||
allow_run: flags.allow_run,
|
||||
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"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("allow-plugin")
|
||||
.long("allow-plugin")
|
||||
.help("Allow loading plugins"),
|
||||
Arg::with_name("allow-ffi")
|
||||
.long("allow-ffi")
|
||||
.min_values(0)
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.require_equals(true)
|
||||
.help("Allow loading dynamic libraries"),
|
||||
)
|
||||
.arg(
|
||||
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_read = Some(vec![]);
|
||||
flags.allow_write = Some(vec![]);
|
||||
flags.allow_plugin = true;
|
||||
flags.allow_ffi = Some(vec![]);
|
||||
flags.allow_hrtime = true;
|
||||
// TODO(@satyarohith): remove this flag in 2.0.
|
||||
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_read = Some(vec![]);
|
||||
flags.allow_write = Some(vec![]);
|
||||
flags.allow_plugin = true;
|
||||
flags.allow_ffi = Some(vec![]);
|
||||
flags.allow_hrtime = true;
|
||||
}
|
||||
|
||||
|
@ -1876,9 +1887,12 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
debug!("run allowlist: {:#?}", &flags.allow_run);
|
||||
}
|
||||
|
||||
if matches.is_present("allow-plugin") {
|
||||
flags.allow_plugin = true;
|
||||
if let Some(ffi_wl) = matches.values_of("allow-ffi") {
|
||||
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") {
|
||||
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_run = Some(vec![]);
|
||||
flags.allow_write = Some(vec![]);
|
||||
flags.allow_plugin = true;
|
||||
flags.allow_ffi = Some(vec![]);
|
||||
flags.allow_hrtime = true;
|
||||
}
|
||||
if matches.is_present("prompt") {
|
||||
|
@ -2227,7 +2241,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2564,7 +2578,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2587,7 +2601,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2611,7 +2625,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2648,7 +2662,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2678,7 +2692,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2698,7 +2712,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
@ -2732,7 +2746,7 @@ mod tests {
|
|||
allow_run: Some(vec![]),
|
||||
allow_read: Some(vec![]),
|
||||
allow_write: Some(vec![]),
|
||||
allow_plugin: true,
|
||||
allow_ffi: Some(vec![]),
|
||||
allow_hrtime: true,
|
||||
..Flags::default()
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ fn metadata_to_flags(metadata: &Metadata) -> Flags {
|
|||
allow_env: permissions.allow_env,
|
||||
allow_hrtime: permissions.allow_hrtime,
|
||||
allow_net: permissions.allow_net,
|
||||
allow_plugin: permissions.allow_plugin,
|
||||
allow_ffi: permissions.allow_ffi,
|
||||
allow_read: permissions.allow_read,
|
||||
allow_run: permissions.allow_run,
|
||||
allow_write: permissions.allow_write,
|
||||
|
|
|
@ -502,7 +502,7 @@ fn lsp_hover_unstable_disabled() {
|
|||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "console.log(Deno.openPlugin);\n"
|
||||
"text": "console.log(Deno.dlopen);\n"
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -537,7 +537,7 @@ fn lsp_hover_unstable_disabled() {
|
|||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 27
|
||||
"character": 23
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -555,7 +555,7 @@ fn lsp_hover_unstable_enabled() {
|
|||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "console.log(Deno.openPlugin);\n"
|
||||
"text": "console.log(Deno.ppid);\n"
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -580,9 +580,9 @@ fn lsp_hover_unstable_enabled() {
|
|||
"contents":[
|
||||
{
|
||||
"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":{
|
||||
"start":{
|
||||
|
@ -591,7 +591,7 @@ fn lsp_hover_unstable_enabled() {
|
|||
},
|
||||
"end":{
|
||||
"line":0,
|
||||
"character":27
|
||||
"character":21
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -10,8 +10,8 @@ test env false ... ok [WILDCARD]
|
|||
test env true ... ok [WILDCARD]
|
||||
test run false ... ok [WILDCARD]
|
||||
test run true ... ok [WILDCARD]
|
||||
test plugin false ... ok [WILDCARD]
|
||||
test plugin true ... ok [WILDCARD]
|
||||
test ffi false ... ok [WILDCARD]
|
||||
test ffi true ... ok [WILDCARD]
|
||||
test hrtime false ... ok [WILDCARD]
|
||||
test hrtime true ... ok [WILDCARD]
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ const permissions: Deno.PermissionName[] = [
|
|||
"net",
|
||||
"env",
|
||||
"run",
|
||||
"plugin",
|
||||
"ffi",
|
||||
"hrtime",
|
||||
];
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ test write ... FAILED [WILDCARD]
|
|||
test net ... FAILED [WILDCARD]
|
||||
test env ... FAILED [WILDCARD]
|
||||
test run ... FAILED [WILDCARD]
|
||||
test plugin ... FAILED [WILDCARD]
|
||||
test ffi ... FAILED [WILDCARD]
|
||||
test hrtime ... FAILED [WILDCARD]
|
||||
|
||||
failures:
|
||||
|
@ -30,7 +30,7 @@ run
|
|||
PermissionDenied: Can't escalate parent thread permissions
|
||||
[WILDCARD]
|
||||
|
||||
plugin
|
||||
ffi
|
||||
PermissionDenied: Can't escalate parent thread permissions
|
||||
[WILDCARD]
|
||||
|
||||
|
@ -45,7 +45,7 @@ failures:
|
|||
net
|
||||
env
|
||||
run
|
||||
plugin
|
||||
ffi
|
||||
hrtime
|
||||
|
||||
test result: FAILED. 0 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
|
||||
|
|
|
@ -6,7 +6,7 @@ const permissions: Deno.PermissionName[] = [
|
|||
"net",
|
||||
"env",
|
||||
"run",
|
||||
"plugin",
|
||||
"ffi",
|
||||
"hrtime",
|
||||
];
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ Deno.test({
|
|||
net: true,
|
||||
env: true,
|
||||
run: true,
|
||||
plugin: true,
|
||||
ffi: true,
|
||||
hrtime: true,
|
||||
},
|
||||
ignore: true,
|
||||
|
|
|
@ -32,7 +32,7 @@ interface UnitTestPermissions {
|
|||
net?: boolean;
|
||||
env?: boolean;
|
||||
run?: boolean;
|
||||
plugin?: boolean;
|
||||
ffi?: boolean;
|
||||
hrtime?: boolean;
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ export function unitTest(
|
|||
net: false,
|
||||
env: false,
|
||||
run: false,
|
||||
plugin: false,
|
||||
ffi: false,
|
||||
hrtime: false,
|
||||
}, options.perms),
|
||||
};
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
self.onmessage = async () => {
|
||||
const hrtime = await Deno.permissions.query({ name: "hrtime" });
|
||||
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 run = await Deno.permissions.query({ name: "run" });
|
||||
const write = await Deno.permissions.query({ name: "write" });
|
||||
self.postMessage(
|
||||
hrtime.state === "denied" &&
|
||||
net.state === "denied" &&
|
||||
plugin.state === "denied" &&
|
||||
ffi.state === "denied" &&
|
||||
read.state === "denied" &&
|
||||
run.state === "denied" &&
|
||||
write.state === "denied",
|
||||
|
|
|
@ -201,7 +201,7 @@ pub fn compile_to_runtime_flags(
|
|||
allow_env: flags.allow_env,
|
||||
allow_hrtime: flags.allow_hrtime,
|
||||
allow_net: flags.allow_net,
|
||||
allow_plugin: flags.allow_plugin,
|
||||
allow_ffi: flags.allow_ffi,
|
||||
allow_read: flags.allow_read,
|
||||
allow_run: flags.allow_run,
|
||||
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_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||
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_net = { version = "0.4.0", path = "../extensions/net" }
|
||||
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_crypto = { version = "0.27.0", path = "../extensions/crypto" }
|
||||
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_net = { version = "0.4.0", path = "../extensions/net" }
|
||||
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(),
|
||||
false, // No --unstable.
|
||||
),
|
||||
deno_ffi::init::<deno_ffi::NoFfiPermissions>(false),
|
||||
deno_net::init::<deno_net::NoNetPermissions>(None, false), // No --unstable.
|
||||
deno_http::init(),
|
||||
];
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
env = "inherit",
|
||||
hrtime = "inherit",
|
||||
net = "inherit",
|
||||
plugin = "inherit",
|
||||
ffi = "inherit",
|
||||
read = "inherit",
|
||||
run = "inherit",
|
||||
write = "inherit",
|
||||
|
@ -128,7 +128,7 @@
|
|||
env: parseUnitPermission(env, "env"),
|
||||
hrtime: parseUnitPermission(hrtime, "hrtime"),
|
||||
net: parseArrayPermission(net, "net"),
|
||||
plugin: parseUnitPermission(plugin, "plugin"),
|
||||
ffi: parseUnitPermission(ffi, "ffi"),
|
||||
read: parseArrayPermission(read, "read"),
|
||||
run: parseUnitPermission(run, "run"),
|
||||
write: parseArrayPermission(write, "write"),
|
||||
|
@ -175,7 +175,7 @@
|
|||
env: false,
|
||||
hrtime: false,
|
||||
net: false,
|
||||
plugin: false,
|
||||
ffi: false,
|
||||
read: false,
|
||||
run: false,
|
||||
write: false,
|
||||
|
|
|
@ -28,14 +28,14 @@
|
|||
* @property {PermissionStatus} status
|
||||
*/
|
||||
|
||||
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "plugin" | "hrtime">} */
|
||||
/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "run" | "ffi" | "hrtime">} */
|
||||
const permissionNames = [
|
||||
"read",
|
||||
"write",
|
||||
"net",
|
||||
"env",
|
||||
"run",
|
||||
"plugin",
|
||||
"ffi",
|
||||
"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,
|
||||
SignalStream: __bootstrap.signals.SignalStream,
|
||||
emit: __bootstrap.compilerApi.emit,
|
||||
openPlugin: __bootstrap.plugins.openPlugin,
|
||||
kill: __bootstrap.process.kill,
|
||||
setRaw: __bootstrap.tty.setRaw,
|
||||
consoleSize: __bootstrap.tty.consoleSize,
|
||||
|
@ -136,5 +135,6 @@
|
|||
HttpClient: __bootstrap.fetch.HttpClient,
|
||||
createHttpClient: __bootstrap.fetch.createHttpClient,
|
||||
http: __bootstrap.http,
|
||||
dlopen: __bootstrap.ffi.dlopen,
|
||||
};
|
||||
})(this);
|
||||
|
|
|
@ -4,6 +4,7 @@ pub use deno_broadcast_channel;
|
|||
pub use deno_console;
|
||||
pub use deno_crypto;
|
||||
pub use deno_fetch;
|
||||
pub use deno_ffi;
|
||||
pub use deno_http;
|
||||
pub use deno_net;
|
||||
pub use deno_timers;
|
||||
|
|
|
@ -6,7 +6,6 @@ pub mod http;
|
|||
pub mod io;
|
||||
pub mod os;
|
||||
pub mod permissions;
|
||||
pub mod plugin;
|
||||
pub mod process;
|
||||
pub mod runtime;
|
||||
pub mod signal;
|
||||
|
|
|
@ -28,6 +28,7 @@ pub struct PermissionArgs {
|
|||
host: Option<String>,
|
||||
variable: Option<String>,
|
||||
command: Option<String>,
|
||||
library: Option<String>,
|
||||
}
|
||||
|
||||
pub fn op_query_permission(
|
||||
|
@ -49,7 +50,7 @@ pub fn op_query_permission(
|
|||
),
|
||||
"env" => permissions.env.query(args.variable.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(),
|
||||
n => {
|
||||
return Err(custom_error(
|
||||
|
@ -80,7 +81,7 @@ pub fn op_revoke_permission(
|
|||
),
|
||||
"env" => permissions.env.revoke(args.variable.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(),
|
||||
n => {
|
||||
return Err(custom_error(
|
||||
|
@ -111,7 +112,7 @@ pub fn op_request_permission(
|
|||
),
|
||||
"env" => permissions.env.request(args.variable.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(),
|
||||
n => {
|
||||
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_write_allowlist;
|
||||
use crate::permissions::EnvDescriptor;
|
||||
use crate::permissions::FfiDescriptor;
|
||||
use crate::permissions::NetDescriptor;
|
||||
use crate::permissions::PermissionState;
|
||||
use crate::permissions::Permissions;
|
||||
|
@ -218,6 +219,26 @@ fn merge_run_permission(
|
|||
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(
|
||||
main_perms: Permissions,
|
||||
worker_perms: PermissionsArg,
|
||||
|
@ -226,7 +247,7 @@ pub fn create_worker_permissions(
|
|||
env: merge_env_permission(main_perms.env, worker_perms.env)?,
|
||||
hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?,
|
||||
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)?,
|
||||
run: merge_run_permission(main_perms.run, worker_perms.run)?,
|
||||
write: merge_write_permission(main_perms.write, worker_perms.write)?,
|
||||
|
@ -241,8 +262,8 @@ pub struct PermissionsArg {
|
|||
hrtime: Option<PermissionState>,
|
||||
#[serde(default, deserialize_with = "as_unary_net_permission")]
|
||||
net: Option<UnaryPermission<NetDescriptor>>,
|
||||
#[serde(default, deserialize_with = "as_permission_state")]
|
||||
plugin: Option<PermissionState>,
|
||||
#[serde(default, deserialize_with = "as_unary_ffi_permission")]
|
||||
ffi: Option<UnaryPermission<FfiDescriptor>>,
|
||||
#[serde(default, deserialize_with = "as_unary_read_permission")]
|
||||
read: Option<UnaryPermission<ReadDescriptor>>,
|
||||
#[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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateWorkerArgs {
|
||||
|
|
|
@ -202,6 +202,9 @@ pub struct EnvDescriptor(pub String);
|
|||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
|
||||
pub struct RunDescriptor(pub String);
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)]
|
||||
pub struct FfiDescriptor(pub String);
|
||||
|
||||
impl UnaryPermission<ReadDescriptor> {
|
||||
pub fn query(&self, path: Option<&Path>) -> PermissionState {
|
||||
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)]
|
||||
pub struct Permissions {
|
||||
pub read: UnaryPermission<ReadDescriptor>,
|
||||
|
@ -794,7 +895,7 @@ pub struct Permissions {
|
|||
pub net: UnaryPermission<NetDescriptor>,
|
||||
pub env: UnaryPermission<EnvDescriptor>,
|
||||
pub run: UnaryPermission<RunDescriptor>,
|
||||
pub plugin: UnitPermission,
|
||||
pub ffi: UnaryPermission<FfiDescriptor>,
|
||||
pub hrtime: UnitPermission,
|
||||
}
|
||||
|
||||
|
@ -803,7 +904,7 @@ pub struct PermissionsOptions {
|
|||
pub allow_env: Option<Vec<String>>,
|
||||
pub allow_hrtime: bool,
|
||||
pub allow_net: Option<Vec<String>>,
|
||||
pub allow_plugin: bool,
|
||||
pub allow_ffi: Option<Vec<String>>,
|
||||
pub allow_read: Option<Vec<PathBuf>>,
|
||||
pub allow_run: Option<Vec<String>>,
|
||||
pub allow_write: Option<Vec<PathBuf>>,
|
||||
|
@ -904,8 +1005,21 @@ impl Permissions {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_plugin(state: bool, prompt: bool) -> UnitPermission {
|
||||
unit_permission_from_flag_bool(state, "plugin", "open a plugin", prompt)
|
||||
pub fn new_ffi(
|
||||
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 {
|
||||
|
@ -924,7 +1038,7 @@ impl Permissions {
|
|||
net: Permissions::new_net(&opts.allow_net, opts.prompt),
|
||||
env: Permissions::new_env(&opts.allow_env, 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),
|
||||
}
|
||||
}
|
||||
|
@ -936,7 +1050,7 @@ impl Permissions {
|
|||
net: Permissions::new_net(&Some(vec![]), false),
|
||||
env: Permissions::new_env(&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),
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
flag: bool,
|
||||
name: &'static str,
|
||||
|
@ -1457,9 +1577,9 @@ mod tests {
|
|||
global_state: PermissionState::Prompt,
|
||||
..Permissions::new_run(&Some(svec!["deno"]), false)
|
||||
},
|
||||
plugin: UnitPermission {
|
||||
state: PermissionState::Prompt,
|
||||
..Default::default()
|
||||
ffi: UnaryPermission {
|
||||
global_state: PermissionState::Prompt,
|
||||
..Permissions::new_ffi(&Some(svec!["deno"]), false)
|
||||
},
|
||||
hrtime: UnitPermission {
|
||||
state: PermissionState::Prompt,
|
||||
|
@ -1490,8 +1610,10 @@ mod tests {
|
|||
assert_eq!(perms1.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
|
||||
assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted);
|
||||
assert_eq!(perms1.plugin.query(), PermissionState::Granted);
|
||||
assert_eq!(perms2.plugin.query(), PermissionState::Prompt);
|
||||
assert_eq!(perms1.ffi.query(None), PermissionState::Granted);
|
||||
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!(perms2.hrtime.query(), PermissionState::Prompt);
|
||||
};
|
||||
|
@ -1528,9 +1650,10 @@ mod tests {
|
|||
set_prompt_result(false);
|
||||
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
|
||||
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);
|
||||
assert_eq!(perms.plugin.request(), PermissionState::Granted);
|
||||
assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
|
||||
set_prompt_result(false);
|
||||
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
|
||||
set_prompt_result(true);
|
||||
|
@ -1561,9 +1684,9 @@ mod tests {
|
|||
global_state: PermissionState::Prompt,
|
||||
..Permissions::new_run(&Some(svec!["deno"]), false)
|
||||
},
|
||||
plugin: UnitPermission {
|
||||
state: PermissionState::Prompt,
|
||||
..Default::default()
|
||||
ffi: UnaryPermission {
|
||||
global_state: PermissionState::Prompt,
|
||||
..Permissions::new_ffi(&Some(svec!["deno"]), false)
|
||||
},
|
||||
hrtime: UnitPermission {
|
||||
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.env.revoke(Some(&"HOME".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);
|
||||
};
|
||||
}
|
||||
|
@ -1595,7 +1718,7 @@ mod tests {
|
|||
net: Permissions::new_net(&None, true),
|
||||
env: Permissions::new_env(&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),
|
||||
};
|
||||
|
||||
|
@ -1648,7 +1771,7 @@ mod tests {
|
|||
net: Permissions::new_net(&None, true),
|
||||
env: Permissions::new_env(&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),
|
||||
};
|
||||
|
||||
|
|
|
@ -315,6 +315,8 @@ impl WebWorker {
|
|||
deno_crypto::init(options.seed),
|
||||
deno_webgpu::init(options.unstable),
|
||||
deno_timers::init::<Permissions>(),
|
||||
// ffi
|
||||
deno_ffi::init::<Permissions>(options.unstable),
|
||||
// Metrics
|
||||
metrics::init(),
|
||||
// Permissions ext (worker specific state)
|
||||
|
@ -340,7 +342,6 @@ impl WebWorker {
|
|||
),
|
||||
ops::os::init(),
|
||||
ops::permissions::init(),
|
||||
ops::plugin::init(),
|
||||
ops::process::init(),
|
||||
ops::signal::init(),
|
||||
ops::tty::init(),
|
||||
|
|
|
@ -115,6 +115,8 @@ impl MainWorker {
|
|||
),
|
||||
deno_webgpu::init(options.unstable),
|
||||
deno_timers::init::<Permissions>(),
|
||||
// ffi
|
||||
deno_ffi::init::<Permissions>(options.unstable),
|
||||
// Metrics
|
||||
metrics::init(),
|
||||
// Runtime ops
|
||||
|
@ -127,7 +129,6 @@ impl MainWorker {
|
|||
deno_net::init::<Permissions>(options.ca_data.clone(), options.unstable),
|
||||
ops::os::init(),
|
||||
ops::permissions::init(),
|
||||
ops::plugin::init(),
|
||||
ops::process::init(),
|
||||
ops::signal::init(),
|
||||
ops::tty::init(),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "test_plugin"
|
||||
version = "0.0.1"
|
||||
name = "test_ffi"
|
||||
version = "0.1.0"
|
||||
authors = ["the deno authors"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
@ -10,10 +10,5 @@ publish = false
|
|||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
deno_core = { path = "../core" }
|
||||
futures = "0.3.15"
|
||||
serde = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
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() {
|
||||
let mut build_plugin_base = Command::new("cargo");
|
||||
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" {
|
||||
build_plugin = build_plugin.arg("--release");
|
||||
}
|
||||
|
@ -21,10 +21,11 @@ fn basic() {
|
|||
assert!(build_plugin_output.status.success());
|
||||
let output = deno_cmd()
|
||||
.arg("run")
|
||||
.arg("--allow-plugin")
|
||||
.arg("--allow-ffi")
|
||||
.arg("--allow-read")
|
||||
.arg("--unstable")
|
||||
.arg("tests/test.js")
|
||||
.arg(BUILD_VARIANT)
|
||||
.env("NO_COLOR", "1")
|
||||
.output()
|
||||
.unwrap();
|
||||
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||||
|
@ -36,23 +37,9 @@ fn basic() {
|
|||
println!("{:?}", output.status);
|
||||
assert!(output.status.success());
|
||||
let expected = "\
|
||||
Plugin rid: 3\n\
|
||||
Hello from sync plugin op.\n\
|
||||
args: TestArgs { val: \"1\" }\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";
|
||||
something\n\
|
||||
579\n\
|
||||
Correct number of resources\n";
|
||||
assert_eq!(stdout, expected);
|
||||
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