diff --git a/src/context/ffi.rs b/src/context/ffi.rs new file mode 100644 index 000000000..a207e2b70 --- /dev/null +++ b/src/context/ffi.rs @@ -0,0 +1,45 @@ +use super::internal::{ContextInternal, Env, Scope, ScopeMetadata}; +use super::Context; +use neon_runtime::raw; + +/// Opaque type representing the underlying env of a context +/// +/// See `Context::with_raw_env` for details. +#[repr(C)] +pub struct RawEnv { + // Create opaque type as suggested in https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + +impl RawEnv { + /// Creates a context in the env + pub fn with_context(&mut self, f: F) -> T + where + F: for<'b> FnOnce(RawContext<'b>) -> T, + { + let env = unsafe { std::mem::transmute(self) }; + RawContext::with(env, f) + } +} + +pub struct RawContext<'a> { + scope: Scope<'a, raw::HandleScope>, +} + +impl<'a> ContextInternal<'a> for RawContext<'a> { + fn scope_metadata(&self) -> &ScopeMetadata { + &self.scope.metadata + } +} + +impl<'a> Context<'a> for RawContext<'a> {} + +impl<'a> RawContext<'a> { + fn with(env: Env, f: F) -> T + where + F: for<'b> FnOnce(RawContext<'b>) -> T, + { + Scope::with(env, |scope| f(RawContext { scope })) + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index e0fdb9313..d870e2e5b 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -146,6 +146,8 @@ //! [iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators //! [question-mark]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html +#[cfg(feature = "napi-1")] +pub mod ffi; pub(crate) mod internal; use crate::borrow::internal::Ledger; @@ -181,6 +183,8 @@ use std::marker::PhantomData; use std::os::raw::c_void; use std::panic::UnwindSafe; +#[cfg(feature = "napi-1")] +use self::ffi::RawEnv; use self::internal::{ContextInternal, Scope, ScopeMetadata}; #[repr(C)] @@ -294,6 +298,28 @@ impl<'a> Lock<'a> { /// /// A context has a lifetime `'a`, which ensures the safety of handles managed by the JS garbage collector. All handles created during the lifetime of a context are kept alive for that duration and cannot outlive the context. pub trait Context<'a>: ContextInternal<'a> { + /// Gets the underlying env of the context. + /// + /// This method is useful for creating contexts across FFI boundaries. + /// + /// # Safety + /// `&mut RawEnv` can be converted to `*mut void`, passed across FFI boundaries, and then + /// converted back to `&mut RawEnv`. + /// Mutable aliasing is allowed because `RawEnv` is zero-sized (https://rust-lang.github.io/unsafe-code-guidelines/glossary.html), + /// but callers are still responsible for upholding other borrowing rules. + #[cfg(feature = "napi-1")] + fn with_raw_env(&mut self, f: F) -> T + where + F: for<'b> FnOnce(&'b mut RawEnv) -> T, + { + self.check_active(); + self.deactivate(); + let raw_env = unsafe { &mut *(self.env().to_raw().cast::()) }; + let result = f(raw_env); + self.activate(); + result + } + /// Lock the JavaScript engine, returning an RAII guard that keeps the lock active as long as the guard is alive. /// /// If this is not the currently active context (for example, if it was used to spawn a scoped context with `execute_scoped` or `compute_scoped`), this method will panic. diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index f081c07a1..cf364245a 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -25,6 +25,8 @@ use js::objects::*; use js::strings::*; use js::threads::*; use js::types::*; +use neon::context::ffi::RawEnv; +use std::os::raw::c_void; #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { @@ -107,6 +109,17 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { .collect::, _>>()?; assert_eq!(property_names, &["0", "a", "whatever"]); + let forty_two = cx.with_raw_env(|mut env| { + let env_ptr = env as *mut RawEnv as *mut c_void; + let env = unsafe { &mut *(env_ptr.cast::()) }; + env.with_context(|mut cx| { + let num = cx.number(42); + num.value(&mut cx) as u8 + }) + }); + + assert_eq!(forty_two, 42); + cx.export_value("rustCreated", rust_created)?; fn add1(mut cx: FunctionContext) -> JsResult {