summaryrefslogtreecommitdiff
path: root/ext/napi
diff options
context:
space:
mode:
authorsnek <the@snek.dev>2024-06-10 09:20:44 -0700
committerGitHub <noreply@github.com>2024-06-10 16:20:44 +0000
commite3b2ee183bc7497ec0432bc764678f5eda6495a7 (patch)
tree7a5fa0208ef56cb83fa6bae9bad0bc89334ed533 /ext/napi
parent7c5dbd5d54770dba5e56442b633e9597403ef5da (diff)
fix: Rewrite Node-API (#24101)
Phase 1 node-api rewrite
Diffstat (limited to 'ext/napi')
-rw-r--r--ext/napi/function.rs39
-rw-r--r--ext/napi/lib.rs284
-rw-r--r--ext/napi/value.rs13
3 files changed, 151 insertions, 185 deletions
diff --git a/ext/napi/function.rs b/ext/napi/function.rs
index 5cc23dcd0..2d2933b95 100644
--- a/ext/napi/function.rs
+++ b/ext/napi/function.rs
@@ -27,9 +27,10 @@ impl CallbackInfo {
}
extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) {
- let info = unsafe { &*info };
- let args = v8::FunctionCallbackArguments::from_function_callback_info(info);
- let mut rv = v8::ReturnValue::from_function_callback_info(info);
+ let callback_info = unsafe { &*info };
+ let args =
+ v8::FunctionCallbackArguments::from_function_callback_info(callback_info);
+ let mut rv = v8::ReturnValue::from_function_callback_info(callback_info);
// SAFETY: create_function guarantees that the data is a CallbackInfo external.
let info_ptr: *mut CallbackInfo = unsafe {
let external_value = v8::Local::<v8::External>::cast(args.data());
@@ -43,19 +44,29 @@ extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) {
if let Some(f) = info.cb {
// SAFETY: calling user provided function pointer.
let value = unsafe { f(info.env, info_ptr as *mut _) };
- // SAFETY: napi_value is represented as v8::Local<v8::Value> internally.
- rv.set(unsafe { transmute::<napi_value, v8::Local<v8::Value>>(value) });
+ if let Some(exc) = unsafe { &mut *(info.env as *mut Env) }
+ .last_exception
+ .take()
+ {
+ let scope = unsafe { &mut v8::CallbackScope::new(callback_info) };
+ let exc = v8::Local::new(scope, exc);
+ scope.throw_exception(exc);
+ }
+ if let Some(value) = *value {
+ rv.set(value);
+ }
}
}
-#[allow(clippy::not_unsafe_ptr_arg_deref)]
-pub fn create_function<'a>(
+/// # Safety
+/// env_ptr must be valid
+pub unsafe fn create_function<'a>(
env_ptr: *mut Env,
name: Option<v8::Local<v8::String>>,
cb: napi_callback,
cb_info: napi_callback_info,
) -> v8::Local<'a, v8::Function> {
- let env: &mut Env = unsafe { &mut *env_ptr };
+ let env = unsafe { &mut *env_ptr };
let scope = &mut env.scope();
let external = v8::External::new(
@@ -74,14 +85,15 @@ pub fn create_function<'a>(
function
}
-#[allow(clippy::not_unsafe_ptr_arg_deref)]
-pub fn create_function_template<'a>(
+/// # Safety
+/// env_ptr must be valid
+pub unsafe fn create_function_template<'a>(
env_ptr: *mut Env,
- name: Option<&str>,
+ name: Option<v8::Local<v8::String>>,
cb: napi_callback,
cb_info: napi_callback_info,
) -> v8::Local<'a, v8::FunctionTemplate> {
- let env: &mut Env = unsafe { &mut *env_ptr };
+ let env = unsafe { &mut *env_ptr };
let scope = &mut env.scope();
let external = v8::External::new(
@@ -92,8 +104,7 @@ pub fn create_function_template<'a>(
.data(external.into())
.build(scope);
- if let Some(name) = name {
- let v8str = v8::String::new(scope, name).unwrap();
+ if let Some(v8str) = name {
function.set_class_name(v8str);
}
diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs
index f4fa33438..39b303f86 100644
--- a/ext/napi/lib.rs
+++ b/ext/napi/lib.rs
@@ -8,18 +8,14 @@
use core::ptr::NonNull;
use deno_core::error::type_error;
use deno_core::error::AnyError;
-use deno_core::futures::channel::mpsc;
use deno_core::op2;
-use deno_core::parking_lot::Mutex;
+use deno_core::ExternalOpsTracker;
use deno_core::OpState;
use deno_core::V8CrossThreadTaskSpawner;
use std::cell::RefCell;
-use std::ffi::CString;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
-use std::sync::atomic::AtomicUsize;
-use std::sync::Arc;
use std::thread_local;
#[cfg(unix)]
@@ -32,7 +28,6 @@ use libloading::os::windows::*;
// `use deno_napi::*`
pub use deno_core::v8;
pub use std::ffi::CStr;
-pub use std::mem::transmute;
pub use std::os::raw::c_char;
pub use std::os::raw::c_void;
pub use std::ptr;
@@ -52,6 +47,7 @@ pub type napi_callback_scope = *mut c_void;
pub type napi_escapable_handle_scope = *mut c_void;
pub type napi_async_cleanup_hook_handle = *mut c_void;
pub type napi_async_work = *mut c_void;
+pub type napi_async_context = *mut c_void;
pub const napi_ok: napi_status = 0;
pub const napi_invalid_arg: napi_status = 1;
@@ -75,6 +71,35 @@ pub const napi_date_expected: napi_status = 18;
pub const napi_arraybuffer_expected: napi_status = 19;
pub const napi_detachable_arraybuffer_expected: napi_status = 20;
pub const napi_would_deadlock: napi_status = 21;
+pub const napi_no_external_buffers_allowed: napi_status = 22;
+pub const napi_cannot_run_js: napi_status = 23;
+
+pub static ERROR_MESSAGES: &[&CStr] = &[
+ c"",
+ c"Invalid argument",
+ c"An object was expected",
+ c"A string was expected",
+ c"A string or symbol was expected",
+ c"A function was expected",
+ c"A number was expected",
+ c"A boolean was expected",
+ c"An array was expected",
+ c"Unknown failure",
+ c"An exception is pending",
+ c"The async work item was cancelled",
+ c"napi_escape_handle already called on scope",
+ c"Invalid handle scope usage",
+ c"Invalid callback scope usage",
+ c"Thread-safe function queue is full",
+ c"Thread-safe function handle is closing",
+ c"A bigint was expected",
+ c"A date was expected",
+ c"An arraybuffer was expected",
+ c"A detachable arraybuffer was expected",
+ c"Main thread would deadlock",
+ c"External buffers are not allowed",
+ c"Cannot run JavaScript",
+];
pub const NAPI_AUTO_LENGTH: usize = usize::MAX;
@@ -83,7 +108,9 @@ thread_local! {
}
type napi_addon_register_func =
- extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
+ unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
+type napi_register_module_v1 =
+ unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
#[repr(C)]
#[derive(Clone)]
@@ -113,7 +140,7 @@ pub const napi_bigint: napi_valuetype = 9;
pub type napi_threadsafe_function_release_mode = i32;
pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0;
-pub const napi_tsfn_abortext: napi_threadsafe_function_release_mode = 1;
+pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1;
pub type napi_threadsafe_function_call_mode = i32;
@@ -153,6 +180,8 @@ pub const napi_float64_array: napi_typedarray_type = 8;
pub const napi_bigint64_array: napi_typedarray_type = 9;
pub const napi_biguint64_array: napi_typedarray_type = 10;
+#[repr(C)]
+#[derive(Clone, Copy, PartialEq)]
pub struct napi_type_tag {
pub lower: u64,
pub upper: u64,
@@ -187,6 +216,8 @@ pub type napi_threadsafe_function_call_js = unsafe extern "C" fn(
pub type napi_async_cleanup_hook =
unsafe extern "C" fn(env: napi_env, data: *mut c_void);
+pub type napi_cleanup_hook = unsafe extern "C" fn(data: *mut c_void);
+
pub type napi_property_attributes = i32;
pub const napi_default: napi_property_attributes = 0;
@@ -233,17 +264,9 @@ pub struct napi_node_version {
pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {}
impl<T> PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {}
-pub type ThreadsafeFunctionRefCounters = Vec<(usize, Arc<AtomicUsize>)>;
pub struct NapiState {
// Thread safe functions.
- pub active_threadsafe_functions: usize,
- pub threadsafe_function_receiver:
- mpsc::UnboundedReceiver<ThreadSafeFunctionStatus>,
- pub threadsafe_function_sender:
- mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
- pub env_cleanup_hooks:
- Rc<RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>>,
- pub tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
+ pub env_cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
}
impl Drop for NapiState {
@@ -267,7 +290,10 @@ impl Drop for NapiState {
continue;
}
- (hook.0)(hook.1);
+ unsafe {
+ (hook.0)(hook.1);
+ }
+
{
self
.env_cleanup_hooks
@@ -277,38 +303,44 @@ impl Drop for NapiState {
}
}
}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct InstanceData {
+ pub data: *mut c_void,
+ pub finalize_cb: Option<napi_finalize>,
+ pub finalize_hint: *mut c_void,
+}
+
#[repr(C)]
#[derive(Debug)]
/// Env that is shared between all contexts in same native module.
pub struct EnvShared {
- pub instance_data: *mut c_void,
- pub data_finalize: Option<napi_finalize>,
- pub data_finalize_hint: *mut c_void,
+ pub instance_data: Option<InstanceData>,
pub napi_wrap: v8::Global<v8::Private>,
+ pub type_tag: v8::Global<v8::Private>,
pub finalize: Option<napi_finalize>,
pub finalize_hint: *mut c_void,
- pub filename: *const c_char,
+ pub filename: String,
}
impl EnvShared {
- pub fn new(napi_wrap: v8::Global<v8::Private>) -> Self {
+ pub fn new(
+ napi_wrap: v8::Global<v8::Private>,
+ type_tag: v8::Global<v8::Private>,
+ filename: String,
+ ) -> Self {
Self {
- instance_data: std::ptr::null_mut(),
- data_finalize: None,
- data_finalize_hint: std::ptr::null_mut(),
+ instance_data: None,
napi_wrap,
+ type_tag,
finalize: None,
finalize_hint: std::ptr::null_mut(),
- filename: std::ptr::null(),
+ filename,
}
}
}
-pub enum ThreadSafeFunctionStatus {
- Alive,
- Dead,
-}
-
#[repr(C)]
pub struct Env {
context: NonNull<v8::Context>,
@@ -316,46 +348,48 @@ pub struct Env {
pub open_handle_scopes: usize,
pub shared: *mut EnvShared,
pub async_work_sender: V8CrossThreadTaskSpawner,
- pub threadsafe_function_sender:
- mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
- pub cleanup_hooks:
- Rc<RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>>,
- pub tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
+ pub cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
+ pub external_ops_tracker: ExternalOpsTracker,
pub last_error: napi_extended_error_info,
+ pub last_exception: Option<v8::Global<v8::Value>>,
pub global: NonNull<v8::Value>,
+ pub buffer_constructor: NonNull<v8::Function>,
+ pub report_error: NonNull<v8::Function>,
}
unsafe impl Send for Env {}
unsafe impl Sync for Env {}
impl Env {
+ #[allow(clippy::too_many_arguments)]
pub fn new(
isolate_ptr: *mut v8::OwnedIsolate,
context: v8::Global<v8::Context>,
global: v8::Global<v8::Value>,
+ buffer_constructor: v8::Global<v8::Function>,
+ report_error: v8::Global<v8::Function>,
sender: V8CrossThreadTaskSpawner,
- threadsafe_function_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
- cleanup_hooks: Rc<
- RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>,
- >,
- tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
+ cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
+ external_ops_tracker: ExternalOpsTracker,
) -> Self {
Self {
isolate_ptr,
context: context.into_raw(),
global: global.into_raw(),
+ buffer_constructor: buffer_constructor.into_raw(),
+ report_error: report_error.into_raw(),
shared: std::ptr::null_mut(),
open_handle_scopes: 0,
async_work_sender: sender,
- threadsafe_function_sender,
cleanup_hooks,
- tsfn_ref_counters,
+ external_ops_tracker,
last_error: napi_extended_error_info {
error_message: std::ptr::null(),
engine_reserved: std::ptr::null_mut(),
engine_error_code: 0,
error_code: napi_ok,
},
+ last_exception: None,
}
}
@@ -384,7 +418,9 @@ impl Env {
// SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is
// already on the stack, but we don't have access to it.
let context = unsafe {
- transmute::<NonNull<v8::Context>, v8::Local<v8::Context>>(self.context)
+ std::mem::transmute::<NonNull<v8::Context>, v8::Local<v8::Context>>(
+ self.context,
+ )
};
// SAFETY: there must be a `HandleScope` on the stack, this is ensured because
// we are in a V8 callback or the module has already opened a `HandleScope`
@@ -392,20 +428,12 @@ impl Env {
unsafe { v8::CallbackScope::new(context) }
}
- pub fn add_threadsafe_function_ref_counter(
- &mut self,
- id: usize,
- counter: Arc<AtomicUsize>,
- ) {
- let mut counters = self.tsfn_ref_counters.lock();
- assert!(!counters.iter().any(|(i, _)| *i == id));
- counters.push((id, counter));
+ pub fn threadsafe_function_ref(&mut self) {
+ self.external_ops_tracker.ref_op();
}
- pub fn remove_threadsafe_function_ref_counter(&mut self, id: usize) {
- let mut counters = self.tsfn_ref_counters.lock();
- let index = counters.iter().position(|(i, _)| *i == id).unwrap();
- counters.remove(index);
+ pub fn threadsafe_function_unref(&mut self) {
+ self.external_ops_tracker.unref_op();
}
}
@@ -415,14 +443,8 @@ deno_core::extension!(deno_napi,
op_napi_open<P>
],
state = |state| {
- let (threadsafe_function_sender, threadsafe_function_receiver) =
- mpsc::unbounded::<ThreadSafeFunctionStatus>();
state.put(NapiState {
- threadsafe_function_sender,
- threadsafe_function_receiver,
- active_threadsafe_functions: 0,
env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
- tsfn_ref_counters: Arc::new(Mutex::new(vec![])),
});
},
);
@@ -441,69 +463,21 @@ impl NapiPermissions for deno_permissions::PermissionsContainer {
}
}
-/// # Safety
-///
-/// This function is unsafe because it dereferences raw pointer Env.
-/// - The caller must ensure that the pointer is valid.
-/// - The caller must ensure that the pointer is not freed.
-pub unsafe fn weak_local(
- env_ptr: *mut Env,
- value: v8::Local<v8::Value>,
- data: *mut c_void,
- finalize_cb: napi_finalize,
- finalize_hint: *mut c_void,
-) -> Option<v8::Local<v8::Value>> {
- use std::cell::Cell;
-
- let env = &mut *env_ptr;
-
- let weak_ptr = Rc::new(Cell::new(None));
- let scope = &mut env.scope();
-
- let weak = v8::Weak::with_finalizer(
- scope,
- value,
- Box::new({
- let weak_ptr = weak_ptr.clone();
- move |isolate| {
- finalize_cb(env_ptr as _, data as _, finalize_hint as _);
-
- // Self-deleting weak.
- if let Some(weak_ptr) = weak_ptr.get() {
- let weak: v8::Weak<v8::Value> =
- unsafe { v8::Weak::from_raw(isolate, Some(weak_ptr)) };
- drop(weak);
- }
- }
- }),
- );
-
- let value = weak.to_local(scope);
- let raw = weak.into_raw();
- weak_ptr.set(raw);
-
- value
-}
-
-#[op2]
+#[op2(reentrant)]
fn op_napi_open<NP, 'scope>(
scope: &mut v8::HandleScope<'scope>,
op_state: Rc<RefCell<OpState>>,
#[string] path: String,
global: v8::Local<'scope, v8::Value>,
+ buffer_constructor: v8::Local<'scope, v8::Function>,
+ report_error: v8::Local<'scope, v8::Function>,
) -> std::result::Result<v8::Local<'scope, v8::Value>, AnyError>
where
NP: NapiPermissions + 'static,
{
// We must limit the OpState borrow because this function can trigger a
// re-borrow through the NAPI module.
- let (
- async_work_sender,
- tsfn_sender,
- isolate_ptr,
- cleanup_hooks,
- tsfn_ref_counters,
- ) = {
+ let (async_work_sender, isolate_ptr, cleanup_hooks, external_ops_tracker) = {
let mut op_state = op_state.borrow_mut();
let permissions = op_state.borrow_mut::<NP>();
permissions.check(Some(&PathBuf::from(&path)))?;
@@ -511,10 +485,9 @@ where
let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>();
(
op_state.borrow::<V8CrossThreadTaskSpawner>().clone(),
- napi_state.threadsafe_function_sender.clone(),
*isolate_ptr,
napi_state.env_cleanup_hooks.clone(),
- napi_state.tsfn_ref_counters.clone(),
+ op_state.external_ops_tracker.clone(),
)
};
@@ -522,23 +495,25 @@ where
let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name));
let napi_wrap = v8::Global::new(scope, napi_wrap);
+ let type_tag_name = v8::String::new(scope, "type_tag").unwrap();
+ let type_tag = v8::Private::new(scope, Some(type_tag_name));
+ let type_tag = v8::Global::new(scope, type_tag);
+
// The `module.exports` object.
let exports = v8::Object::new(scope);
- let mut env_shared = EnvShared::new(napi_wrap);
- let cstr = CString::new(&*path).unwrap();
- env_shared.filename = cstr.as_ptr();
- std::mem::forget(cstr);
+ let env_shared = EnvShared::new(napi_wrap, type_tag, path.clone());
let ctx = scope.get_current_context();
let mut env = Env::new(
isolate_ptr,
v8::Global::new(scope, ctx),
v8::Global::new(scope, global),
+ v8::Global::new(scope, buffer_constructor),
+ v8::Global::new(scope, report_error),
async_work_sender,
- tsfn_sender,
cleanup_hooks,
- tsfn_ref_counters,
+ external_ops_tracker,
);
env.shared = Box::into_raw(Box::new(env_shared));
let env_ptr = Box::into_raw(Box::new(env)) as _;
@@ -567,63 +542,30 @@ where
slot.take()
});
- if let Some(module_to_register) = maybe_module {
+ let maybe_exports = if let Some(module_to_register) = maybe_module {
// SAFETY: napi_register_module guarantees that `module_to_register` is valid.
let nm = unsafe { &*module_to_register };
assert_eq!(nm.nm_version, 1);
// SAFETY: we are going blind, calling the register function on the other side.
- let maybe_exports = unsafe {
- (nm.nm_register_func)(
- env_ptr,
- std::mem::transmute::<v8::Local<v8::Value>, napi_value>(exports.into()),
- )
- };
-
- let exports = if maybe_exports.is_some() {
- // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
- // to a value, they have the same layout
- unsafe {
- std::mem::transmute::<napi_value, v8::Local<v8::Value>>(maybe_exports)
- }
- } else {
- exports.into()
- };
-
- // NAPI addons can't be unloaded, so we're going to "forget" the library
- // object so it lives till the program exit.
- std::mem::forget(library);
- return Ok(exports);
- }
-
- // Initializer callback.
- // SAFETY: we are going blind, calling the register function on the other side.
-
- let maybe_exports = unsafe {
- let Ok(init) = library
- .get::<unsafe extern "C" fn(
- env: napi_env,
- exports: napi_value,
- ) -> napi_value>(b"napi_register_module_v1") else {
- return Err(type_error(format!("Unable to find napi_register_module_v1 symbol in {}", path)));
- };
- init(
- env_ptr,
- std::mem::transmute::<v8::Local<v8::Value>, napi_value>(exports.into()),
- )
- };
-
- let exports = if maybe_exports.is_some() {
- // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
- // to a value, they have the same layout
- unsafe {
- std::mem::transmute::<napi_value, v8::Local<v8::Value>>(maybe_exports)
- }
+ unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
+ } else if let Ok(init) = unsafe {
+ library.get::<napi_register_module_v1>(b"napi_register_module_v1")
+ } {
+ // Initializer callback.
+ // SAFETY: we are going blind, calling the register function on the other side.
+ unsafe { init(env_ptr, exports.into()) }
} else {
- exports.into()
+ return Err(type_error(format!(
+ "Unable to find register Node-API module at {}",
+ path
+ )));
};
+ let exports = maybe_exports.unwrap_or(exports.into());
+
// NAPI addons can't be unloaded, so we're going to "forget" the library
// object so it lives till the program exit.
std::mem::forget(library);
+
Ok(exports)
}
diff --git a/ext/napi/value.rs b/ext/napi/value.rs
index c1607f2c2..6fb96758c 100644
--- a/ext/napi/value.rs
+++ b/ext/napi/value.rs
@@ -37,6 +37,19 @@ where
}
}
+impl<'s, T> From<Option<v8::Local<'s, T>>> for napi_value<'s>
+where
+ v8::Local<'s, T>: Into<v8::Local<'s, v8::Value>>,
+{
+ fn from(v: Option<v8::Local<'s, T>>) -> Self {
+ if let Some(v) = v {
+ NapiValue::from(v)
+ } else {
+ Self(None, std::marker::PhantomData)
+ }
+ }
+}
+
const _: () = {
assert!(
std::mem::size_of::<napi_value>() == std::mem::size_of::<*mut c_void>()