Skip to content

Commit

Permalink
feat(neon): sys feature to give access to Node-API
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Apr 14, 2023
1 parent 3e83d73 commit 738bbad
Show file tree
Hide file tree
Showing 24 changed files with 683 additions and 418 deletions.
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
neon-check = " check --all --all-targets --features napi-experimental,futures,external-buffers"
neon-clippy = "clippy --all --all-targets --features napi-experimental,futures,external-buffers -- -A clippy::missing_safety_doc"
neon-test = " test --all --features=doc-dependencies,doc-comment,napi-experimental,futures,external-buffers"
neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures,external-buffers -- --cfg docsrs"
neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures,external-buffers,sys -- --cfg docsrs"
4 changes: 4 additions & 0 deletions crates/neon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ external-buffers = []
# https://github.com/neon-bindings/rfcs/pull/46
futures = ["tokio"]

# Enable low-level system APIs. The `sys` API allows augmenting the Neon API
# from external crates.
sys = []

# Default N-API version. Prefer to select a minimum required version.
# DEPRECATED: This is an alias that should be removed
napi-runtime = ["napi-8"]
Expand Down
6 changes: 3 additions & 3 deletions crates/neon/src/context/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::{cell::RefCell, ffi::c_void, mem::MaybeUninit};

use crate::{
context::ModuleContext,
handle::{Handle, Managed},
handle::Handle,
result::NeonResult,
sys::{self, raw},
types::JsObject,
types::{private::ValueInternal, JsObject},
};

#[repr(C)]
Expand Down Expand Up @@ -64,7 +64,7 @@ pub unsafe fn initialize_module(
});

let env = Env(env);
let exports = Handle::new_internal(JsObject::from_raw(env, exports.cast()));
let exports = Handle::new_internal(JsObject::from_local(env, exports.cast()));

ModuleContext::with(env, exports, |cx| {
let _ = init(cx);
Expand Down
60 changes: 54 additions & 6 deletions crates/neon/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub use crate::types::buffer::lock::Lock;

use crate::{
event::TaskBuilder,
handle::{Handle, Managed},
handle::Handle,
object::Object,
result::{JsResult, NeonResult, Throw},
sys::{
Expand All @@ -157,6 +157,7 @@ use crate::{
types::{
boxed::{Finalize, JsBox},
error::JsError,
private::ValueInternal,
Deferred, JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsFunction, JsNull, JsNumber,
JsObject, JsPromise, JsString, JsUndefined, JsValue, StringResult, Value,
},
Expand Down Expand Up @@ -276,9 +277,11 @@ pub trait Context<'a>: ContextInternal<'a> {
phantom_inner: PhantomData,
};

let escapee = unsafe { scope.escape(f(cx)?.to_raw()) };
let escapee = unsafe { scope.escape(f(cx)?.to_local()) };

Ok(Handle::new_internal(V::from_raw(self.env(), escapee)))
Ok(Handle::new_internal(unsafe {
V::from_local(self.env(), escapee)
}))
}

#[cfg_attr(
Expand Down Expand Up @@ -366,9 +369,9 @@ pub trait Context<'a>: ContextInternal<'a> {
/// Throws a JS value.
fn throw<T: Value, U>(&mut self, v: Handle<T>) -> NeonResult<U> {
unsafe {
sys::error::throw(self.env().to_raw(), v.to_raw());
sys::error::throw(self.env().to_raw(), v.to_local());
Err(Throw::new())
}
Err(Throw::new())
}

/// Creates a direct instance of the [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) class.
Expand Down Expand Up @@ -505,6 +508,13 @@ pub trait Context<'a>: ContextInternal<'a> {
{
TaskBuilder::new(self, execute)
}

#[cfg(feature = "sys")]
#[cfg_attr(docsrs, doc(cfg(feature = "sys")))]
/// Gets the raw `sys::Env` for usage with Node-API.
fn to_raw(&self) -> sys::Env {
self.env().to_raw()
}
}

/// An execution context of module initialization.
Expand Down Expand Up @@ -648,7 +658,7 @@ impl<'a> FunctionContext<'a> {
};

argv.get(i)
.map(|v| Handle::new_internal(JsValue::from_raw(self.env(), v)))
.map(|v| Handle::new_internal(unsafe { JsValue::from_local(self.env(), v) }))
}

/// Produces the `i`th argument and casts it to the type `V`, or throws an exception if `i` is greater than or equal to `self.len()` or cannot be cast to `V`.
Expand Down Expand Up @@ -726,3 +736,41 @@ impl<'a> ContextInternal<'a> for FinalizeContext<'a> {
}

impl<'a> Context<'a> for FinalizeContext<'a> {}

#[cfg(feature = "sys")]
#[cfg_attr(docsrs, doc(cfg(feature = "sys")))]
/// An execution context constructed from a raw [`Env`](crate::sys::bindings::Env).
pub struct SysContext<'cx> {
env: Env,
_phantom_inner: PhantomData<&'cx ()>,
}

#[cfg(feature = "sys")]
impl<'cx> SysContext<'cx> {
/// Creates a context from a raw `Env`.
///
/// # Safety
///
/// Once a `SysContext` has been created, it is unsafe to use
/// the `Env`. The handle scope for the `Env` must be valid for
/// the lifetime `'cx`.
pub unsafe fn from_raw(env: sys::Env) -> Self {
Self {
env: env.into(),
_phantom_inner: PhantomData,
}
}
}

#[cfg(feature = "sys")]
impl<'cx> SysContext<'cx> {}

#[cfg(feature = "sys")]
impl<'cx> ContextInternal<'cx> for SysContext<'cx> {
fn env(&self) -> Env {
self.env
}
}

#[cfg(feature = "sys")]
impl<'cx> Context<'cx> for SysContext<'cx> {}
8 changes: 6 additions & 2 deletions crates/neon/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
//!
//! ```
//! # use neon::prelude::*;
//! #
//! # #[cfg(not(feature = "napi-6"))]
//! # type Channel = ();
//! # fn parse(filename: String, callback: Root<JsFunction>, channel: Channel) { }
//! #
//! # #[cfg(feature = "napi-6")]
//! fn parse_async(mut cx: FunctionContext) -> JsResult<JsUndefined> {
//! // The types `String`, `Root<JsFunction>`, and `Channel` can all be
//! // sent across threads.
Expand Down Expand Up @@ -60,13 +61,16 @@
//!
//! ```
//! # use neon::prelude::*;
//! # #[cfg(not(feature = "napi-6"))]
//! # type Channel = ();
//! # use psd::Psd;
//! # use anyhow::{Context as _, Result};
//! #
//! fn psd_from_filename(filename: String) -> Result<Psd> {
//! Psd::from_bytes(&std::fs::read(&filename)?).context("invalid psd file")
//! }
//!
//! # #[cfg(feature = "napi-6")]
//! fn parse(filename: String, callback: Root<JsFunction>, channel: Channel) {
//! let result = psd_from_filename(filename);
//!
Expand Down
39 changes: 16 additions & 23 deletions crates/neon/src/handle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,24 @@ use std::{
pub use self::root::Root;

use crate::{
context::{internal::Env, Context},
context::Context,
handle::internal::{SuperType, TransparentNoCopyWrapper},
result::{JsResult, ResultExt},
sys::{self, raw},
sys,
types::Value,
};

/// The trait of data owned by the JavaScript engine and that can only be accessed via handles.
pub trait Managed: TransparentNoCopyWrapper {
fn to_raw(&self) -> raw::Local;

fn from_raw(env: Env, h: raw::Local) -> Self;
}

/// A handle to a JavaScript value that is owned by the JavaScript engine.
#[derive(Debug)]
#[repr(transparent)]
pub struct Handle<'a, T: Managed + 'a> {
pub struct Handle<'a, V: Value + 'a> {
// Contains the actual `Copy` JavaScript value data. It will be wrapped in
// in a `!Copy` type when dereferencing. Only `T` should be visible to the user.
value: <T as TransparentNoCopyWrapper>::Inner,
phantom: PhantomData<&'a T>,
// in a `!Copy` type when dereferencing. Only `V` should be visible to the user.
value: <V as TransparentNoCopyWrapper>::Inner,
phantom: PhantomData<&'a V>,
}

impl<'a, T: Managed> Clone for Handle<'a, T> {
impl<'a, V: Value> Clone for Handle<'a, V> {
fn clone(&self) -> Self {
Self {
value: self.value,
Expand All @@ -94,10 +87,10 @@ impl<'a, T: Managed> Clone for Handle<'a, T> {
}
}

impl<'a, T: Managed> Copy for Handle<'a, T> {}
impl<'a, V: Value> Copy for Handle<'a, V> {}

impl<'a, T: Managed + 'a> Handle<'a, T> {
pub(crate) fn new_internal(value: T) -> Handle<'a, T> {
impl<'a, V: Value + 'a> Handle<'a, V> {
pub(crate) fn new_internal(value: V) -> Handle<'a, V> {
Handle {
value: value.into_inner(),
phantom: PhantomData,
Expand Down Expand Up @@ -196,19 +189,19 @@ impl<'a, T: Value> Handle<'a, T> {
cx: &mut C,
other: Handle<'b, U>,
) -> bool {
unsafe { sys::mem::strict_equals(cx.env().to_raw(), self.to_raw(), other.to_raw()) }
unsafe { sys::mem::strict_equals(cx.env().to_raw(), self.to_local(), other.to_local()) }
}
}

impl<'a, T: Managed> Deref for Handle<'a, T> {
type Target = T;
fn deref(&self) -> &T {
impl<'a, V: Value> Deref for Handle<'a, V> {
type Target = V;
fn deref(&self) -> &V {
unsafe { mem::transmute(&self.value) }
}
}

impl<'a, T: Managed> DerefMut for Handle<'a, T> {
fn deref_mut(&mut self) -> &mut T {
impl<'a, V: Value> DerefMut for Handle<'a, V> {
fn deref_mut(&mut self) -> &mut V {
unsafe { mem::transmute(&mut self.value) }
}
}
6 changes: 3 additions & 3 deletions crates/neon/src/handle/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl<T: Object> Root<T> {
/// * N-API >= 6, Neon will drop from a global queue at a runtime cost
pub fn new<'a, C: Context<'a>>(cx: &mut C, value: &T) -> Self {
let env = cx.env().to_raw();
let internal = unsafe { reference::new(env, value.to_raw()) };
let internal = unsafe { reference::new(env, value.to_local()) };

Self {
internal: Some(NapiRef(internal as *mut _)),
Expand Down Expand Up @@ -159,7 +159,7 @@ impl<T: Object> Root<T> {
internal.unref(env.to_raw());
}

Handle::new_internal(T::from_raw(env, local))
Handle::new_internal(unsafe { T::from_local(env, local) })
}

/// Access the inner JavaScript object without consuming the `Root`
Expand All @@ -174,7 +174,7 @@ impl<T: Object> Root<T> {
let env = cx.env();
let local = unsafe { reference::get(env.to_raw(), self.as_napi_ref(cx).0 as *mut _) };

Handle::new_internal(T::from_raw(env, local))
Handle::new_internal(unsafe { T::from_local(env, local) })
}

fn as_napi_ref<'a, C: Context<'a>>(&self, cx: &mut C) -> &NapiRef {
Expand Down
43 changes: 43 additions & 0 deletions crates/neon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub mod object;
pub mod prelude;
pub mod reflect;
pub mod result;
#[cfg(not(feature = "sys"))]
mod sys;
#[cfg(feature = "napi-6")]
pub mod thread;
Expand All @@ -95,6 +96,10 @@ pub mod thread;
mod types_docs;
mod types_impl;

#[cfg(feature = "sys")]
#[cfg_attr(docsrs, doc(cfg(feature = "sys")))]
pub mod sys;

pub use types_docs::exports as types;

#[doc(hidden)]
Expand Down Expand Up @@ -127,3 +132,41 @@ static MODULE_TAG: once_cell::sync::Lazy<crate::sys::TypeTag> = once_cell::sync:
// https://github.com/nodejs/node/blob/5fad0b93667ffc6e4def52996b9529ac99b26319/src/js_native_api_v8.cc#L2455
crate::sys::TypeTag { lower, upper: 1 }
});

#[test]
#[ignore]
fn feature_matrix() {
use std::{env, process::Command};

const EXTERNAL_BUFFERS: &str = "external-buffers";
const FUTURES: &str = "futures";
const NODE_API_VERSIONS: &[&str] = &[
"napi-1", "napi-2", "napi-3", "napi-4", "napi-5", "napi-6", "napi-7", "napi-8",
];

// If the number of features in Neon grows, we can use `itertools` to generate permutations.
// https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.permutations
const FEATURES: &[&[&str]] = &[
&[],
&[EXTERNAL_BUFFERS],
&[FUTURES],
&[EXTERNAL_BUFFERS, FUTURES],
];

let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());

for features in FEATURES {
for version in NODE_API_VERSIONS.iter().map(|f| f.to_string()) {
let features = features.iter().fold(version, |f, s| f + "," + s);
let status = Command::new(&cargo)
.args(["check", "-p", "neon", "--features"])
.arg(features)
.spawn()
.unwrap()
.wait()
.unwrap();

assert!(status.success());
}
}
}
Loading

0 comments on commit 738bbad

Please sign in to comment.