diff --git a/Cargo.lock b/Cargo.lock index 4976d7f0294234..ee7ce518bedfe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2579,7 +2579,6 @@ dependencies = [ name = "test_plugin" version = "0.0.1" dependencies = [ - "deno", "deno_core", "futures 0.3.4", ] diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index b91a61c3a90c85..a53e5ac16c693d 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -19,7 +19,7 @@ pub mod net; mod net_unix; pub mod os; pub mod permissions; -pub mod plugins; +pub mod plugin; pub mod process; pub mod random; pub mod repl; diff --git a/cli/ops/plugin.rs b/cli/ops/plugin.rs new file mode 100644 index 00000000000000..cabb3329db72cd --- /dev/null +++ b/cli/ops/plugin.rs @@ -0,0 +1,153 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::fs::resolve_from_cwd; +use crate::op_error::OpError; +use crate::ops::dispatch_json::Deserialize; +use crate::ops::dispatch_json::JsonOp; +use crate::ops::dispatch_json::Value; +use crate::ops::json_op; +use crate::state::State; +use deno_core::plugin_api; +use deno_core::CoreIsolate; +use deno_core::Op; +use deno_core::OpAsyncFuture; +use deno_core::OpId; +use deno_core::ZeroCopyBuf; +use dlopen::symbor::Library; +use futures::prelude::*; +use std::path::Path; +use std::pin::Pin; +use std::rc::Rc; +use std::task::Context; +use std::task::Poll; + +pub fn init(i: &mut CoreIsolate, s: &State) { + i.register_op( + "op_open_plugin", + s.core_op(json_op(s.stateful_op2(op_open_plugin))), + ); +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct OpenPluginArgs { + filename: String, +} + +pub fn op_open_plugin( + isolate: &mut CoreIsolate, + state: &State, + args: Value, + _zero_copy: Option, +) -> Result { + state.check_unstable("Deno.openPlugin"); + let args: OpenPluginArgs = serde_json::from_value(args).unwrap(); + let filename = resolve_from_cwd(Path::new(&args.filename))?; + + state.check_plugin(&filename)?; + + debug!("Loading Plugin: {:#?}", filename); + let plugin_lib = Library::open(filename) + .map(Rc::new) + .map_err(OpError::from)?; + let plugin_resource = PluginResource::new(&plugin_lib); + + let mut resource_table = isolate.resource_table.borrow_mut(); + let rid = resource_table.add("plugin", Box::new(plugin_resource)); + let plugin_resource = resource_table.get::(rid).unwrap(); + + let deno_plugin_init = *unsafe { + plugin_resource + .lib + .symbol::("deno_plugin_init") + } + .unwrap(); + drop(resource_table); + + let mut interface = PluginInterface::new(isolate, &plugin_lib); + deno_plugin_init(&mut interface); + + Ok(JsonOp::Sync(json!(rid))) +} + +struct PluginResource { + lib: Rc, +} + +impl PluginResource { + fn new(lib: &Rc) -> Self { + Self { lib: lib.clone() } + } +} + +struct PluginInterface<'a> { + isolate: &'a mut CoreIsolate, + plugin_lib: &'a Rc, +} + +impl<'a> PluginInterface<'a> { + fn new(isolate: &'a mut CoreIsolate, plugin_lib: &'a Rc) -> Self { + Self { + isolate, + plugin_lib, + } + } +} + +impl<'a> plugin_api::Interface for PluginInterface<'a> { + /// Does the same as `core::Isolate::register_op()`, but additionally makes + /// the registered op dispatcher, as well as the op futures created by it, + /// keep reference to the plugin `Library` object, so that the plugin doesn't + /// get unloaded before all its op registrations and the futures created by + /// them are dropped. + fn register_op( + &mut self, + name: &str, + dispatch_op_fn: plugin_api::DispatchOpFn, + ) -> OpId { + let plugin_lib = self.plugin_lib.clone(); + self.isolate.op_registry.register( + name, + move |isolate, control, zero_copy| { + let mut interface = PluginInterface::new(isolate, &plugin_lib); + let op = dispatch_op_fn(&mut interface, control, zero_copy); + match op { + sync_op @ Op::Sync(..) => sync_op, + Op::Async(fut) => { + Op::Async(PluginOpAsyncFuture::new(&plugin_lib, fut)) + } + Op::AsyncUnref(fut) => { + Op::AsyncUnref(PluginOpAsyncFuture::new(&plugin_lib, fut)) + } + } + }, + ) + } +} + +struct PluginOpAsyncFuture { + fut: Option, + _plugin_lib: Rc, +} + +impl PluginOpAsyncFuture { + fn new(plugin_lib: &Rc, fut: OpAsyncFuture) -> Pin> { + let wrapped_fut = Self { + fut: Some(fut), + _plugin_lib: plugin_lib.clone(), + }; + Box::pin(wrapped_fut) + } +} + +impl Future for PluginOpAsyncFuture { + type Output = ::Output; + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + self.fut.as_mut().unwrap().poll_unpin(ctx) + } +} + +impl Drop for PluginOpAsyncFuture { + fn drop(&mut self) { + self.fut.take(); + } +} diff --git a/cli/ops/plugins.rs b/cli/ops/plugins.rs deleted file mode 100644 index f4212f57927cba..00000000000000 --- a/cli/ops/plugins.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::dispatch_json::{Deserialize, JsonOp, Value}; -use crate::fs as deno_fs; -use crate::op_error::OpError; -use crate::ops::json_op; -use crate::state::State; -use deno_core::CoreIsolate; -use deno_core::ZeroCopyBuf; -use dlopen::symbor::Library; -use std::ffi::OsStr; -use std::path::Path; - -pub type PluginInitFn = fn(isolate: &mut CoreIsolate); - -pub fn init(i: &mut CoreIsolate, s: &State) { - i.register_op( - "op_open_plugin", - s.core_op(json_op(s.stateful_op2(op_open_plugin))), - ); -} - -fn open_plugin>(lib_path: P) -> Result { - debug!("Loading Plugin: {:#?}", lib_path.as_ref()); - Library::open(lib_path).map_err(OpError::from) -} - -struct PluginResource { - lib: Library, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct OpenPluginArgs { - filename: String, -} - -pub fn op_open_plugin( - isolate: &mut CoreIsolate, - state: &State, - args: Value, - _zero_copy: Option, -) -> Result { - state.check_unstable("Deno.openPlugin"); - let args: OpenPluginArgs = serde_json::from_value(args).unwrap(); - let filename = deno_fs::resolve_from_cwd(Path::new(&args.filename))?; - - state.check_plugin(&filename)?; - - let lib = open_plugin(filename).unwrap(); - let plugin_resource = PluginResource { lib }; - - let mut resource_table = isolate.resource_table.borrow_mut(); - let rid = resource_table.add("plugin", Box::new(plugin_resource)); - let plugin_resource = resource_table.get::(rid).unwrap(); - - let deno_plugin_init = *unsafe { - plugin_resource - .lib - .symbol::("deno_plugin_init") - } - .unwrap(); - drop(resource_table); - - deno_plugin_init(isolate); - - Ok(JsonOp::Sync(json!(rid))) -} diff --git a/cli/web_worker.rs b/cli/web_worker.rs index ebb3f1d8666547..490d9a5f328c42 100644 --- a/cli/web_worker.rs +++ b/cli/web_worker.rs @@ -136,7 +136,7 @@ impl WebWorker { ops::runtime_compiler::init(isolate, &state); ops::fs::init(isolate, &state); ops::fs_events::init(isolate, &state); - ops::plugins::init(isolate, &state); + ops::plugin::init(isolate, &state); ops::net::init(isolate, &state); ops::tls::init(isolate, &state); ops::os::init(isolate, &state); diff --git a/cli/worker.rs b/cli/worker.rs index 858958ecfb9513..f5eae937857284 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -243,7 +243,7 @@ impl MainWorker { ops::fs::init(isolate, &state); ops::fs_events::init(isolate, &state); ops::io::init(isolate, &state); - ops::plugins::init(isolate, &state); + ops::plugin::init(isolate, &state); ops::net::init(isolate, &state); ops::tls::init(isolate, &state); ops::os::init(isolate, &state); diff --git a/core/lib.rs b/core/lib.rs index 661640910fe387..ffccc8febb5bc4 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -17,6 +17,7 @@ mod js_errors; mod module_specifier; mod modules; mod ops; +pub mod plugin_api; mod resources; mod shared_queue; diff --git a/core/plugin_api.rs b/core/plugin_api.rs new file mode 100644 index 00000000000000..2e93fdb77e5eec --- /dev/null +++ b/core/plugin_api.rs @@ -0,0 +1,23 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This file defines the public interface for dynamically loaded plugins. + +// The plugin needs to do all interaction with the CLI crate through trait +// objects and function pointers. This ensures that no concrete internal methods +// (such as register_op and the closures created by it) can end up in the plugin +// shared library itself, which would cause segfaults when the plugin is +// unloaded and all functions in the plugin library are unmapped from memory. + +pub use crate::Buf; +pub use crate::Op; +pub use crate::OpId; +pub use crate::ZeroCopyBuf; + +pub type InitFn = fn(&mut dyn Interface); + +pub type DispatchOpFn = + fn(&mut dyn Interface, &[u8], Option) -> Op; + +pub trait Interface { + fn register_op(&mut self, name: &str, dispatcher: DispatchOpFn) -> OpId; +} diff --git a/test_plugin/Cargo.toml b/test_plugin/Cargo.toml index 5dcfb61edcd70e..704cee8314fc4c 100644 --- a/test_plugin/Cargo.toml +++ b/test_plugin/Cargo.toml @@ -11,4 +11,3 @@ crate-type = ["cdylib"] [dependencies] futures = "0.3.4" deno_core = { path = "../core" } -deno = { path = "../cli" } diff --git a/test_plugin/src/lib.rs b/test_plugin/src/lib.rs index 2304cd65b2b496..37868b31026580 100644 --- a/test_plugin/src/lib.rs +++ b/test_plugin/src/lib.rs @@ -1,20 +1,17 @@ -extern crate deno_core; -extern crate futures; - -use deno_core::Buf; -use deno_core::CoreIsolate; -use deno_core::Op; -use deno_core::ZeroCopyBuf; +use deno_core::plugin_api::Buf; +use deno_core::plugin_api::Interface; +use deno_core::plugin_api::Op; +use deno_core::plugin_api::ZeroCopyBuf; use futures::future::FutureExt; #[no_mangle] -pub fn deno_plugin_init(isolate: &mut CoreIsolate) { - isolate.register_op("testSync", op_test_sync); - isolate.register_op("testAsync", op_test_async); +pub fn deno_plugin_init(interface: &mut dyn Interface) { + interface.register_op("testSync", op_test_sync); + interface.register_op("testAsync", op_test_async); } -pub fn op_test_sync( - _isolate: &mut CoreIsolate, +fn op_test_sync( + _interface: &mut dyn Interface, data: &[u8], zero_copy: Option, ) -> Op { @@ -31,8 +28,8 @@ pub fn op_test_sync( Op::Sync(result_box) } -pub fn op_test_async( - _isolate: &mut CoreIsolate, +fn op_test_async( + _interface: &mut dyn Interface, data: &[u8], zero_copy: Option, ) -> Op { diff --git a/test_plugin/tests/integration_tests.rs b/test_plugin/tests/integration_tests.rs index 2f61ec9aa5d998..17002fc0142b47 100644 --- a/test_plugin/tests/integration_tests.rs +++ b/test_plugin/tests/integration_tests.rs @@ -1,13 +1,26 @@ // To run this test manually: // cd test_plugin -// ../target/debug/deno --allow-plugin tests/test.js debug +// ../target/debug/deno run --unstable --allow-plugin tests/test.js debug -// TODO(ry) Re-enable this test on windows. It is flaky for an unknown reason. -#![cfg(not(windows))] - -use deno::test_util::*; +use std::path::PathBuf; use std::process::Command; +fn target_dir() -> PathBuf { + let current_exe = std::env::current_exe().unwrap(); + let target_dir = current_exe.parent().unwrap().parent().unwrap(); + println!("target_dir {}", target_dir.display()); + target_dir.into() +} + +fn deno_exe_path() -> PathBuf { + // Something like /Users/rld/src/deno/target/debug/deps/deno + let mut p = target_dir().join("deno"); + if cfg!(windows) { + p.set_extension("exe"); + } + p +} + fn deno_cmd() -> Command { assert!(deno_exe_path().exists()); Command::new(deno_exe_path())