1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix: Rewrite Node-API (#24101)

Phase 1 node-api rewrite
This commit is contained in:
snek 2024-06-10 09:20:44 -07:00 committed by Nathan Whitaker
parent 44a4e2bcca
commit 21736392dc
No known key found for this signature in database
21 changed files with 4797 additions and 3317 deletions

View file

@ -3,11 +3,8 @@
This directory contains source for Deno's Node-API implementation. It depends on This directory contains source for Deno's Node-API implementation. It depends on
`napi_sym` and `deno_napi`. `napi_sym` and `deno_napi`.
- [`async.rs`](./async.rs) - Asynchronous work related functions. Files are generally organized the same as in Node.js's implementation to ease in
- [`env.rs`](./env.rs) - Environment related functions. ensuring compatibility.
- [`js_native_api.rs`](./js_native_api.rs) - V8/JS related functions.
- [`thread_safe_function.rs`](./threadsafe_functions.rs) - Thread safe function
related functions.
## Adding a new function ## Adding a new function

View file

@ -1,102 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_runtime::deno_napi::*;
use crate::check_env;
use crate::napi::threadsafe_functions::SendPtr;
#[repr(C)]
pub struct AsyncWork {
pub data: *mut c_void,
pub execute: napi_async_execute_callback,
pub complete: napi_async_complete_callback,
}
unsafe impl Send for AsyncWork {}
unsafe impl Sync for AsyncWork {}
#[napi_sym::napi_sym]
fn napi_create_async_work(
_env: *mut Env,
_async_resource: napi_value,
_async_resource_name: napi_value,
execute: napi_async_execute_callback,
complete: napi_async_complete_callback,
data: *mut c_void,
result: *mut napi_async_work,
) -> napi_status {
let mut work = AsyncWork {
data,
execute,
complete,
};
let work_box = Box::new(work);
*result = transmute::<*mut AsyncWork, _>(Box::into_raw(work_box));
napi_ok
}
#[napi_sym::napi_sym]
fn napi_cancel_async_work(
_env: &mut Env,
_async_work: napi_async_work,
) -> napi_status {
napi_ok
}
/// Frees a previously allocated work object.
#[napi_sym::napi_sym]
fn napi_delete_async_work(
_env: &mut Env,
work: napi_async_work,
) -> napi_status {
let work = Box::from_raw(work as *mut AsyncWork);
drop(work);
napi_ok
}
#[napi_sym::napi_sym]
fn napi_queue_async_work(
env_ptr: *mut Env,
work: napi_async_work,
) -> napi_status {
let work: &AsyncWork = &*(work as *const AsyncWork);
let Some(env) = env_ptr.as_mut() else {
return napi_invalid_arg;
};
let send_env = SendPtr(env_ptr);
#[inline(always)]
fn do_work(ptr: SendPtr<Env>, work: &AsyncWork) {
// SAFETY: This is a valid async work queue call and it runs on the event loop thread
unsafe {
(work.execute)(ptr.0 as napi_env, work.data);
(work.complete)(ptr.0 as napi_env, napi_ok, work.data);
}
}
env.add_async_work(move || do_work(send_env, work));
napi_ok
}
// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops.
#[napi_sym::napi_sym]
fn napi_async_init(
env: *mut Env,
_async_resource: napi_value,
_async_resource_name: napi_value,
result: *mut *mut (),
) -> napi_status {
check_env!(env);
*result = ptr::null_mut();
napi_ok
}
#[napi_sym::napi_sym]
fn napi_async_destroy(env: *mut Env, async_context: *mut ()) -> napi_status {
check_env!(env);
assert!(async_context.is_null());
napi_ok
}

View file

@ -1,174 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_runtime::deno_napi::*;
use std::os::raw::c_char;
/// # Safety
///
/// It's an N-API symbol
#[no_mangle]
pub unsafe extern "C" fn napi_fatal_error(
location: *const c_char,
location_len: isize,
message: *const c_char,
message_len: isize,
) -> ! {
let location = if location.is_null() {
None
} else {
Some(if location_len < 0 {
std::ffi::CStr::from_ptr(location).to_str().unwrap()
} else {
let slice = std::slice::from_raw_parts(
location as *const u8,
location_len as usize,
);
std::str::from_utf8(slice).unwrap()
})
};
let message = if message_len < 0 {
std::ffi::CStr::from_ptr(message).to_str().unwrap()
} else {
let slice =
std::slice::from_raw_parts(message as *const u8, message_len as usize);
std::str::from_utf8(slice).unwrap()
};
panic!(
"Fatal exception triggered by napi_fatal_error!\nLocation: {location:?}\n{message}"
);
}
// napi-3
#[napi_sym::napi_sym]
fn napi_fatal_exception(env: *mut Env, value: napi_value) -> napi_status {
let Some(env) = env.as_mut() else {
return napi_invalid_arg;
};
let value = transmute::<napi_value, v8::Local<v8::Value>>(value);
let error = value.to_rust_string_lossy(&mut env.scope());
panic!("Fatal exception triggered by napi_fatal_exception!\n{error}");
}
#[napi_sym::napi_sym]
fn napi_add_env_cleanup_hook(
env: *mut Env,
hook: extern "C" fn(*const c_void),
data: *const c_void,
) -> napi_status {
let Some(env) = env.as_mut() else {
return napi_invalid_arg;
};
{
let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut();
if env_cleanup_hooks
.iter()
.any(|pair| pair.0 == hook && pair.1 == data)
{
panic!("Cleanup hook with this data already registered");
}
env_cleanup_hooks.push((hook, data));
}
napi_ok
}
#[napi_sym::napi_sym]
fn napi_remove_env_cleanup_hook(
env: *mut Env,
hook: extern "C" fn(*const c_void),
data: *const c_void,
) -> napi_status {
let Some(env) = env.as_mut() else {
return napi_invalid_arg;
};
{
let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut();
// Hooks are supposed to be removed in LIFO order
let maybe_index = env_cleanup_hooks
.iter()
.rposition(|&pair| pair.0 == hook && pair.1 == data);
if let Some(index) = maybe_index {
env_cleanup_hooks.remove(index);
} else {
panic!("Cleanup hook with this data not found");
}
}
napi_ok
}
#[napi_sym::napi_sym]
fn napi_open_callback_scope(
_env: *mut Env,
_resource_object: napi_value,
_context: napi_value,
_result: *mut napi_callback_scope,
) -> napi_status {
// we open scope automatically when it's needed
napi_ok
}
#[napi_sym::napi_sym]
fn napi_close_callback_scope(
_env: *mut Env,
_scope: napi_callback_scope,
) -> napi_status {
// we close scope automatically when it's needed
napi_ok
}
#[napi_sym::napi_sym]
fn node_api_get_module_file_name(
env: *mut Env,
result: *mut *const c_char,
) -> napi_status {
let Some(env) = env.as_mut() else {
return napi_invalid_arg;
};
let shared = env.shared();
*result = shared.filename;
napi_ok
}
#[napi_sym::napi_sym]
fn napi_module_register(module: *const NapiModule) -> napi_status {
MODULE_TO_REGISTER.with(|cell| {
let mut slot = cell.borrow_mut();
let prev = slot.replace(module);
assert!(prev.is_none());
});
napi_ok
}
#[napi_sym::napi_sym]
fn napi_get_uv_event_loop(
_env: *mut Env,
uv_loop: *mut *mut (),
) -> napi_status {
// There is no uv_loop in Deno
*uv_loop = std::ptr::null_mut();
napi_ok
}
const NODE_VERSION: napi_node_version = napi_node_version {
major: 18,
minor: 13,
patch: 0,
release: "Deno\0".as_ptr() as *const c_char,
};
#[napi_sym::napi_sym]
fn napi_get_node_version(
env: *mut Env,
result: *mut *const napi_node_version,
) -> napi_status {
crate::check_env!(env);
crate::check_arg!(env, result);
*result = &NODE_VERSION as *const napi_node_version;
napi_ok
}

View file

@ -1 +1 @@
{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; }; { "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "node_api_create_external_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "node_api_create_external_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "node_api_symbol_for"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; "node_api_create_property_key_utf16"; "napi_type_tag_object"; "napi_check_object_type_tag"; "node_api_post_finalizer"; "napi_add_async_cleanup_hook"; "napi_remove_async_cleanup_hook"; };

View file

@ -34,6 +34,7 @@ _napi_create_external
_napi_get_threadsafe_function_context _napi_get_threadsafe_function_context
_napi_get_null _napi_get_null
_napi_create_string_utf16 _napi_create_string_utf16
_node_api_create_external_string_utf16
_napi_get_value_bigint_uint64 _napi_get_value_bigint_uint64
_napi_module_register _napi_module_register
_napi_is_typedarray _napi_is_typedarray
@ -74,6 +75,7 @@ _napi_adjust_external_memory
_napi_release_threadsafe_function _napi_release_threadsafe_function
_napi_delete_async_work _napi_delete_async_work
_napi_create_string_latin1 _napi_create_string_latin1
_node_api_create_external_string_latin1
_napi_is_array _napi_is_array
_napi_unref_threadsafe_function _napi_unref_threadsafe_function
_napi_throw_error _napi_throw_error
@ -137,8 +139,15 @@ _napi_create_uint32
_napi_has_element _napi_has_element
_napi_create_external_arraybuffer _napi_create_external_arraybuffer
_napi_create_symbol _napi_create_symbol
_node_api_symbol_for
_napi_coerce_to_string _napi_coerce_to_string
_napi_create_type_error _napi_create_type_error
_napi_fatal_exception _napi_fatal_exception
_napi_create_async_work _napi_create_async_work
_napi_async_init _napi_async_init
_node_api_create_property_key_utf16
_napi_type_tag_object
_napi_check_object_type_tag
_node_api_post_finalizer
_napi_add_async_cleanup_hook
_napi_remove_async_cleanup_hook

View file

@ -36,6 +36,7 @@ EXPORTS
napi_get_threadsafe_function_context napi_get_threadsafe_function_context
napi_get_null napi_get_null
napi_create_string_utf16 napi_create_string_utf16
node_api_create_external_string_utf16
napi_get_value_bigint_uint64 napi_get_value_bigint_uint64
napi_module_register napi_module_register
napi_is_typedarray napi_is_typedarray
@ -76,6 +77,7 @@ EXPORTS
napi_release_threadsafe_function napi_release_threadsafe_function
napi_delete_async_work napi_delete_async_work
napi_create_string_latin1 napi_create_string_latin1
node_api_create_external_string_latin1
napi_is_array napi_is_array
napi_unref_threadsafe_function napi_unref_threadsafe_function
napi_throw_error napi_throw_error
@ -139,8 +141,15 @@ EXPORTS
napi_has_element napi_has_element
napi_create_external_arraybuffer napi_create_external_arraybuffer
napi_create_symbol napi_create_symbol
node_api_symbol_for
napi_coerce_to_string napi_coerce_to_string
napi_create_type_error napi_create_type_error
napi_fatal_exception napi_fatal_exception
napi_create_async_work napi_create_async_work
napi_async_init napi_async_init
node_api_create_property_key_utf16
napi_type_tag_object
napi_check_object_type_tag
node_api_post_finalizer
napi_add_async_cleanup_hook
napi_remove_async_cleanup_hook

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,6 @@
//! 2. Add the function's identifier to this JSON list. //! 2. Add the function's identifier to this JSON list.
//! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `cli/napi/generated_symbol_exports_list_*.def`. //! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `cli/napi/generated_symbol_exports_list_*.def`.
pub mod r#async;
pub mod env;
pub mod js_native_api; pub mod js_native_api;
pub mod threadsafe_functions; pub mod node_api;
pub mod util; pub mod util;

1020
cli/napi/node_api.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -20,17 +20,12 @@ pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream {
let name = &func.sig.ident; let name = &func.sig.ident;
assert!( assert!(
exports.symbols.contains(&name.to_string()), exports.symbols.contains(&name.to_string()),
"tools/napi/sym/symbol_exports.json is out of sync!" "cli/napi/sym/symbol_exports.json is out of sync!"
); );
let block = &func.block;
let inputs = &func.sig.inputs;
let generics = &func.sig.generics;
TokenStream::from(quote! { TokenStream::from(quote! {
// SAFETY: it's an NAPI function. crate::napi_wrap! {
#[no_mangle] #func
pub unsafe extern "C" fn #name #generics (#inputs) -> napi_status { }
#block
}
}) })
} }

View file

@ -36,6 +36,7 @@
"napi_get_threadsafe_function_context", "napi_get_threadsafe_function_context",
"napi_get_null", "napi_get_null",
"napi_create_string_utf16", "napi_create_string_utf16",
"node_api_create_external_string_utf16",
"napi_get_value_bigint_uint64", "napi_get_value_bigint_uint64",
"napi_module_register", "napi_module_register",
"napi_is_typedarray", "napi_is_typedarray",
@ -76,6 +77,7 @@
"napi_release_threadsafe_function", "napi_release_threadsafe_function",
"napi_delete_async_work", "napi_delete_async_work",
"napi_create_string_latin1", "napi_create_string_latin1",
"node_api_create_external_string_latin1",
"napi_is_array", "napi_is_array",
"napi_unref_threadsafe_function", "napi_unref_threadsafe_function",
"napi_throw_error", "napi_throw_error",
@ -139,10 +141,17 @@
"napi_has_element", "napi_has_element",
"napi_create_external_arraybuffer", "napi_create_external_arraybuffer",
"napi_create_symbol", "napi_create_symbol",
"node_api_symbol_for",
"napi_coerce_to_string", "napi_coerce_to_string",
"napi_create_type_error", "napi_create_type_error",
"napi_fatal_exception", "napi_fatal_exception",
"napi_create_async_work", "napi_create_async_work",
"napi_async_init" "napi_async_init",
"node_api_create_property_key_utf16",
"napi_type_tag_object",
"napi_check_object_type_tag",
"node_api_post_finalizer",
"napi_add_async_cleanup_hook",
"napi_remove_async_cleanup_hook"
] ]
} }

View file

@ -1,290 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::futures::channel::mpsc;
use deno_core::V8CrossThreadTaskSpawner;
use deno_runtime::deno_napi::*;
use once_cell::sync::Lazy;
use std::mem::forget;
use std::ptr::NonNull;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
#[repr(transparent)]
pub struct SendPtr<T>(pub *const T);
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}
static TS_FN_ID_COUNTER: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
pub struct TsFn {
pub id: usize,
pub env: *mut Env,
pub maybe_func: Option<v8::Global<v8::Function>>,
pub maybe_call_js_cb: Option<napi_threadsafe_function_call_js>,
pub context: *mut c_void,
pub thread_counter: usize,
pub ref_counter: Arc<AtomicUsize>,
finalizer: Option<napi_finalize>,
finalizer_data: *mut c_void,
sender: V8CrossThreadTaskSpawner,
tsfn_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>,
}
impl Drop for TsFn {
fn drop(&mut self) {
let env = unsafe { self.env.as_mut().unwrap() };
env.remove_threadsafe_function_ref_counter(self.id);
if let Some(finalizer) = self.finalizer {
unsafe {
(finalizer)(self.env as _, self.finalizer_data, ptr::null_mut());
}
}
}
}
impl TsFn {
pub fn acquire(&mut self) -> napi_status {
self.thread_counter += 1;
napi_ok
}
pub fn release(mut self) -> napi_status {
self.thread_counter -= 1;
if self.thread_counter == 0 {
if self
.tsfn_sender
.unbounded_send(ThreadSafeFunctionStatus::Dead)
.is_err()
{
return napi_generic_failure;
}
drop(self);
} else {
forget(self);
}
napi_ok
}
pub fn ref_(&mut self) -> napi_status {
self
.ref_counter
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
napi_ok
}
pub fn unref(&mut self) -> napi_status {
let _ = self.ref_counter.fetch_update(
std::sync::atomic::Ordering::SeqCst,
std::sync::atomic::Ordering::SeqCst,
|x| {
if x == 0 {
None
} else {
Some(x - 1)
}
},
);
napi_ok
}
pub fn call(&self, data: *mut c_void, is_blocking: bool) {
let js_func = self.maybe_func.clone();
let env = SendPtr(self.env);
let context = SendPtr(self.context);
let data = SendPtr(data);
#[inline(always)]
fn spawn(
sender: &V8CrossThreadTaskSpawner,
is_blocking: bool,
f: impl FnOnce(&mut v8::HandleScope) + Send + 'static,
) {
if is_blocking {
sender.spawn_blocking(f);
} else {
sender.spawn(f);
}
}
if let Some(call_js_cb) = self.maybe_call_js_cb {
if let Some(func) = js_func {
let func = SendPtr(func.into_raw().as_ptr());
#[inline(always)]
fn call(
scope: &mut v8::HandleScope,
call_js_cb: napi_threadsafe_function_call_js,
func: SendPtr<v8::Function>,
env: SendPtr<Env>,
context: SendPtr<c_void>,
data: SendPtr<c_void>,
) {
// SAFETY: This is a valid global from above
let func: v8::Global<v8::Function> = unsafe {
v8::Global::<v8::Function>::from_raw(
scope,
NonNull::new_unchecked(func.0 as _),
)
};
let func: v8::Local<v8::Value> =
func.open(scope).to_object(scope).unwrap().into();
// SAFETY: env is valid for the duration of the callback.
// data lifetime is users responsibility.
unsafe {
call_js_cb(env.0 as _, func.into(), context.0 as _, data.0 as _)
}
}
spawn(&self.sender, is_blocking, move |scope| {
call(scope, call_js_cb, func, env, context, data);
});
} else {
#[inline(always)]
fn call(
call_js_cb: napi_threadsafe_function_call_js,
env: SendPtr<Env>,
context: SendPtr<c_void>,
data: SendPtr<c_void>,
) {
// SAFETY: env is valid for the duration of the callback.
// data lifetime is users responsibility.
unsafe {
call_js_cb(
env.0 as _,
std::mem::zeroed(),
context.0 as _,
data.0 as _,
)
}
}
spawn(&self.sender, is_blocking, move |_| {
call(call_js_cb, env, context, data);
});
}
} else {
spawn(&self.sender, is_blocking, |_| {
// TODO: func.call
});
};
}
}
#[napi_sym::napi_sym]
fn napi_create_threadsafe_function(
env: *mut Env,
func: napi_value,
_async_resource: napi_value,
_async_resource_name: napi_value,
_max_queue_size: usize,
initial_thread_count: usize,
thread_finalize_data: *mut c_void,
thread_finalize_cb: Option<napi_finalize>,
context: *mut c_void,
maybe_call_js_cb: Option<napi_threadsafe_function_call_js>,
result: *mut napi_threadsafe_function,
) -> napi_status {
let Some(env_ref) = env.as_mut() else {
return napi_generic_failure;
};
if initial_thread_count == 0 {
return napi_invalid_arg;
}
let mut maybe_func = None;
if let Some(value) = *func {
let Ok(func) = v8::Local::<v8::Function>::try_from(value) else {
return napi_function_expected;
};
maybe_func = Some(v8::Global::new(&mut env_ref.scope(), func));
}
let id = TS_FN_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let tsfn = TsFn {
id,
maybe_func,
maybe_call_js_cb,
context,
thread_counter: initial_thread_count,
sender: env_ref.async_work_sender.clone(),
finalizer: thread_finalize_cb,
finalizer_data: thread_finalize_data,
tsfn_sender: env_ref.threadsafe_function_sender.clone(),
ref_counter: Arc::new(AtomicUsize::new(1)),
env,
};
env_ref
.add_threadsafe_function_ref_counter(tsfn.id, tsfn.ref_counter.clone());
if env_ref
.threadsafe_function_sender
.unbounded_send(ThreadSafeFunctionStatus::Alive)
.is_err()
{
return napi_generic_failure;
}
*result = transmute::<Box<TsFn>, _>(Box::new(tsfn));
napi_ok
}
#[napi_sym::napi_sym]
fn napi_acquire_threadsafe_function(
tsfn: napi_threadsafe_function,
_mode: napi_threadsafe_function_release_mode,
) -> napi_status {
let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn);
tsfn.acquire()
}
#[napi_sym::napi_sym]
fn napi_unref_threadsafe_function(
_env: &mut Env,
tsfn: napi_threadsafe_function,
) -> napi_status {
let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn);
tsfn.unref()
}
/// Maybe called from any thread.
#[napi_sym::napi_sym]
pub fn napi_get_threadsafe_function_context(
func: napi_threadsafe_function,
result: *mut *const c_void,
) -> napi_status {
let tsfn: &TsFn = &*(func as *const TsFn);
*result = tsfn.context;
napi_ok
}
#[napi_sym::napi_sym]
fn napi_call_threadsafe_function(
func: napi_threadsafe_function,
data: *mut c_void,
is_blocking: napi_threadsafe_function_call_mode,
) -> napi_status {
let tsfn: &TsFn = &*(func as *const TsFn);
tsfn.call(data, is_blocking != 0);
napi_ok
}
#[napi_sym::napi_sym]
fn napi_ref_threadsafe_function(
_env: &mut Env,
func: napi_threadsafe_function,
) -> napi_status {
let tsfn: &mut TsFn = &mut *(func as *mut TsFn);
tsfn.ref_()
}
#[napi_sym::napi_sym]
fn napi_release_threadsafe_function(
tsfn: napi_threadsafe_function,
_mode: napi_threadsafe_function_release_mode,
) -> napi_status {
let tsfn: Box<TsFn> = Box::from_raw(tsfn as *mut TsFn);
tsfn.release()
}

View file

@ -1,8 +1,292 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_runtime::deno_napi::*; use deno_runtime::deno_napi::*;
use libc::INT_MAX;
#[repr(transparent)]
pub struct SendPtr<T>(pub *const T);
impl<T> SendPtr<T> {
// silly function to get around `clippy::redundant_locals`
pub fn take(self) -> *const T {
self.0
}
}
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}
pub fn get_array_buffer_ptr(ab: v8::Local<v8::ArrayBuffer>) -> *mut u8 { pub fn get_array_buffer_ptr(ab: v8::Local<v8::ArrayBuffer>) -> *mut u8 {
// SAFETY: Thanks to the null pointer optimization, NonNull<T> and Option<NonNull<T>> are guaranteed // SAFETY: Thanks to the null pointer optimization, NonNull<T> and Option<NonNull<T>> are guaranteed
// to have the same size and alignment. // to have the same size and alignment.
unsafe { std::mem::transmute(ab.data()) } unsafe { std::mem::transmute(ab.data()) }
} }
struct BufferFinalizer {
env: *mut Env,
finalize_cb: napi_finalize,
finalize_data: *mut c_void,
finalize_hint: *mut c_void,
}
impl Drop for BufferFinalizer {
fn drop(&mut self) {
unsafe {
(self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint);
}
}
}
pub extern "C" fn backing_store_deleter_callback(
data: *mut c_void,
_byte_length: usize,
deleter_data: *mut c_void,
) {
let mut finalizer =
unsafe { Box::<BufferFinalizer>::from_raw(deleter_data as _) };
finalizer.finalize_data = data;
drop(finalizer);
}
pub fn make_external_backing_store(
env: *mut Env,
data: *mut c_void,
byte_length: usize,
finalize_data: *mut c_void,
finalize_cb: napi_finalize,
finalize_hint: *mut c_void,
) -> v8::UniqueRef<v8::BackingStore> {
let finalizer = Box::new(BufferFinalizer {
env,
finalize_data,
finalize_cb,
finalize_hint,
});
unsafe {
v8::ArrayBuffer::new_backing_store_from_ptr(
data,
byte_length,
backing_store_deleter_callback,
Box::into_raw(finalizer) as _,
)
}
}
#[macro_export]
macro_rules! check_env {
($env: expr) => {{
let env = $env;
if env.is_null() {
return napi_invalid_arg;
}
unsafe { &mut *env }
}};
}
#[macro_export]
macro_rules! return_error_status_if_false {
($env: expr, $condition: expr, $status: ident) => {
if !$condition {
return Err(
$crate::napi::util::napi_set_last_error($env, $status).into(),
);
}
};
}
#[macro_export]
macro_rules! return_status_if_false {
($env: expr, $condition: expr, $status: ident) => {
if !$condition {
return $crate::napi::util::napi_set_last_error($env, $status);
}
};
}
pub(crate) unsafe fn check_new_from_utf8_len<'s>(
env: *mut Env,
str_: *const c_char,
len: usize,
) -> Result<v8::Local<'s, v8::String>, napi_status> {
let env = unsafe { &mut *env };
return_error_status_if_false!(
env,
(len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _,
napi_invalid_arg
);
return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg);
let string = if len == NAPI_AUTO_LENGTH {
let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str();
return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
result.unwrap()
} else {
let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) };
let result = std::str::from_utf8(string);
return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
result.unwrap()
};
let result = {
let env = unsafe { &mut *(env as *mut Env) };
v8::String::new(&mut env.scope(), string)
};
return_error_status_if_false!(env, result.is_some(), napi_generic_failure);
Ok(result.unwrap())
}
#[inline]
pub(crate) unsafe fn check_new_from_utf8<'s>(
env: *mut Env,
str_: *const c_char,
) -> Result<v8::Local<'s, v8::String>, napi_status> {
unsafe { check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) }
}
pub(crate) unsafe fn v8_name_from_property_descriptor<'s>(
env: *mut Env,
p: &'s napi_property_descriptor,
) -> Result<v8::Local<'s, v8::Name>, napi_status> {
if !p.utf8name.is_null() {
unsafe { check_new_from_utf8(env, p.utf8name).map(|v| v.into()) }
} else {
match *p.name {
Some(v) => match v.try_into() {
Ok(name) => Ok(name),
Err(_) => Err(napi_name_expected),
},
None => Err(napi_name_expected),
}
}
}
pub(crate) fn napi_clear_last_error(env: *mut Env) -> napi_status {
let env = unsafe { &mut *env };
env.last_error.error_code = napi_ok;
env.last_error.engine_error_code = 0;
env.last_error.engine_reserved = std::ptr::null_mut();
env.last_error.error_message = std::ptr::null_mut();
napi_ok
}
pub(crate) fn napi_set_last_error(
env: *mut Env,
error_code: napi_status,
) -> napi_status {
let env = unsafe { &mut *env };
env.last_error.error_code = error_code;
error_code
}
#[macro_export]
macro_rules! status_call {
($call: expr) => {
let status = $call;
if status != napi_ok {
return status;
}
};
}
pub trait Nullable {
fn is_null(&self) -> bool;
}
impl<T> Nullable for *mut T {
fn is_null(&self) -> bool {
(*self).is_null()
}
}
impl<T> Nullable for *const T {
fn is_null(&self) -> bool {
(*self).is_null()
}
}
impl<T> Nullable for Option<T> {
fn is_null(&self) -> bool {
self.is_none()
}
}
impl<'s> Nullable for napi_value<'s> {
fn is_null(&self) -> bool {
self.is_none()
}
}
// TODO: replace Nullable with some sort of "CheckedUnwrap" trait
// *mut T -> &mut MaybeUninit<T>
// Option<T> -> T
// napi_value -> Local<Value>
#[macro_export]
macro_rules! check_arg {
($env: expr, $ptr: expr) => {
$crate::return_status_if_false!(
$env,
!$crate::napi::util::Nullable::is_null(&$ptr),
napi_invalid_arg
);
};
}
#[macro_export]
macro_rules! napi_wrap {
( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $env:ident : & $( $lt:lifetime )? mut Env $( , $ident:ident : $ty:ty )* $(,)? ) -> napi_status $body:block ) => {
$( # $attr )*
#[no_mangle]
pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( env_ptr : *mut Env , $( $ident : $ty ),* ) -> napi_status {
let env: & $( $lt )? mut Env = $crate::check_env!(env_ptr);
if env.last_exception.is_some() {
return napi_pending_exception;
}
$crate::napi::util::napi_clear_last_error(env);
let scope_env = unsafe { &mut *env_ptr };
let scope = &mut scope_env.scope();
let try_catch = &mut v8::TryCatch::new(scope);
#[inline(always)]
fn inner $( < $( $x ),* > )? ( $env: & $( $lt )? mut Env , $( $ident : $ty ),* ) -> napi_status $body
log::trace!("NAPI ENTER: {}", stringify!($name));
let result = inner( env, $( $ident ),* );
log::trace!("NAPI EXIT: {} {}", stringify!($name), result);
if let Some(exception) = try_catch.exception() {
let env = unsafe { &mut *env_ptr };
let global = v8::Global::new(env.isolate(), exception);
env.last_exception = Some(global);
return $crate::napi::util::napi_set_last_error(env_ptr, napi_pending_exception);
}
if result != napi_ok {
return $crate::napi::util::napi_set_last_error(env_ptr, result);
}
return result;
}
};
( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $( $ident:ident : $ty:ty ),* $(,)? ) -> napi_status $body:block ) => {
$( # $attr )*
#[no_mangle]
pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status {
#[inline(always)]
fn inner $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status $body
log::trace!("NAPI ENTER: {}", stringify!($name));
let result = inner( $( $ident ),* );
log::trace!("NAPI EXIT: {} {}", stringify!($name), result);
result
}
};
}

View file

@ -27,9 +27,10 @@ impl CallbackInfo {
} }
extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) {
let info = unsafe { &*info }; let callback_info = unsafe { &*info };
let args = v8::FunctionCallbackArguments::from_function_callback_info(info); let args =
let mut rv = v8::ReturnValue::from_function_callback_info(info); 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. // SAFETY: create_function guarantees that the data is a CallbackInfo external.
let info_ptr: *mut CallbackInfo = unsafe { let info_ptr: *mut CallbackInfo = unsafe {
let external_value = v8::Local::<v8::External>::cast(args.data()); 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 { if let Some(f) = info.cb {
// SAFETY: calling user provided function pointer. // SAFETY: calling user provided function pointer.
let value = unsafe { f(info.env, info_ptr as *mut _) }; let value = unsafe { f(info.env, info_ptr as *mut _) };
// SAFETY: napi_value is represented as v8::Local<v8::Value> internally. if let Some(exc) = unsafe { &mut *(info.env as *mut Env) }
rv.set(unsafe { transmute::<napi_value, v8::Local<v8::Value>>(value) }); .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)] /// # Safety
pub fn create_function<'a>( /// env_ptr must be valid
pub unsafe fn create_function<'a>(
env_ptr: *mut Env, env_ptr: *mut Env,
name: Option<v8::Local<v8::String>>, name: Option<v8::Local<v8::String>>,
cb: napi_callback, cb: napi_callback,
cb_info: napi_callback_info, cb_info: napi_callback_info,
) -> v8::Local<'a, v8::Function> { ) -> 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 scope = &mut env.scope();
let external = v8::External::new( let external = v8::External::new(
@ -74,14 +85,15 @@ pub fn create_function<'a>(
function function
} }
#[allow(clippy::not_unsafe_ptr_arg_deref)] /// # Safety
pub fn create_function_template<'a>( /// env_ptr must be valid
pub unsafe fn create_function_template<'a>(
env_ptr: *mut Env, env_ptr: *mut Env,
name: Option<&str>, name: Option<v8::Local<v8::String>>,
cb: napi_callback, cb: napi_callback,
cb_info: napi_callback_info, cb_info: napi_callback_info,
) -> v8::Local<'a, v8::FunctionTemplate> { ) -> 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 scope = &mut env.scope();
let external = v8::External::new( let external = v8::External::new(
@ -92,8 +104,7 @@ pub fn create_function_template<'a>(
.data(external.into()) .data(external.into())
.build(scope); .build(scope);
if let Some(name) = name { if let Some(v8str) = name {
let v8str = v8::String::new(scope, name).unwrap();
function.set_class_name(v8str); function.set_class_name(v8str);
} }

View file

@ -8,18 +8,14 @@
use core::ptr::NonNull; use core::ptr::NonNull;
use deno_core::error::type_error; use deno_core::error::type_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::channel::mpsc;
use deno_core::op2; use deno_core::op2;
use deno_core::parking_lot::Mutex; use deno_core::ExternalOpsTracker;
use deno_core::OpState; use deno_core::OpState;
use deno_core::V8CrossThreadTaskSpawner; use deno_core::V8CrossThreadTaskSpawner;
use std::cell::RefCell; use std::cell::RefCell;
use std::ffi::CString;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use std::thread_local; use std::thread_local;
#[cfg(unix)] #[cfg(unix)]
@ -32,7 +28,6 @@ use libloading::os::windows::*;
// `use deno_napi::*` // `use deno_napi::*`
pub use deno_core::v8; pub use deno_core::v8;
pub use std::ffi::CStr; pub use std::ffi::CStr;
pub use std::mem::transmute;
pub use std::os::raw::c_char; pub use std::os::raw::c_char;
pub use std::os::raw::c_void; pub use std::os::raw::c_void;
pub use std::ptr; 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_escapable_handle_scope = *mut c_void;
pub type napi_async_cleanup_hook_handle = *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_work = *mut c_void;
pub type napi_async_context = *mut c_void;
pub const napi_ok: napi_status = 0; pub const napi_ok: napi_status = 0;
pub const napi_invalid_arg: napi_status = 1; 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_arraybuffer_expected: napi_status = 19;
pub const napi_detachable_arraybuffer_expected: napi_status = 20; pub const napi_detachable_arraybuffer_expected: napi_status = 20;
pub const napi_would_deadlock: napi_status = 21; 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; pub const NAPI_AUTO_LENGTH: usize = usize::MAX;
@ -83,7 +108,9 @@ thread_local! {
} }
type napi_addon_register_func = 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)] #[repr(C)]
#[derive(Clone)] #[derive(Clone)]
@ -113,7 +140,7 @@ pub const napi_bigint: napi_valuetype = 9;
pub type napi_threadsafe_function_release_mode = i32; pub type napi_threadsafe_function_release_mode = i32;
pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0; 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; 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_bigint64_array: napi_typedarray_type = 9;
pub const napi_biguint64_array: napi_typedarray_type = 10; pub const napi_biguint64_array: napi_typedarray_type = 10;
#[repr(C)]
#[derive(Clone, Copy, PartialEq)]
pub struct napi_type_tag { pub struct napi_type_tag {
pub lower: u64, pub lower: u64,
pub upper: 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 = pub type napi_async_cleanup_hook =
unsafe extern "C" fn(env: napi_env, data: *mut c_void); 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 type napi_property_attributes = i32;
pub const napi_default: napi_property_attributes = 0; pub const napi_default: napi_property_attributes = 0;
@ -233,17 +264,9 @@ pub struct napi_node_version {
pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {} pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {}
impl<T> PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {} impl<T> PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {}
pub type ThreadsafeFunctionRefCounters = Vec<(usize, Arc<AtomicUsize>)>;
pub struct NapiState { pub struct NapiState {
// Thread safe functions. // Thread safe functions.
pub active_threadsafe_functions: usize, pub env_cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
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>>,
} }
impl Drop for NapiState { impl Drop for NapiState {
@ -267,7 +290,10 @@ impl Drop for NapiState {
continue; continue;
} }
(hook.0)(hook.1); unsafe {
(hook.0)(hook.1);
}
{ {
self self
.env_cleanup_hooks .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)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
/// Env that is shared between all contexts in same native module. /// Env that is shared between all contexts in same native module.
pub struct EnvShared { pub struct EnvShared {
pub instance_data: *mut c_void, pub instance_data: Option<InstanceData>,
pub data_finalize: Option<napi_finalize>,
pub data_finalize_hint: *mut c_void,
pub napi_wrap: v8::Global<v8::Private>, pub napi_wrap: v8::Global<v8::Private>,
pub type_tag: v8::Global<v8::Private>,
pub finalize: Option<napi_finalize>, pub finalize: Option<napi_finalize>,
pub finalize_hint: *mut c_void, pub finalize_hint: *mut c_void,
pub filename: *const c_char, pub filename: String,
} }
impl EnvShared { 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 { Self {
instance_data: std::ptr::null_mut(), instance_data: None,
data_finalize: None,
data_finalize_hint: std::ptr::null_mut(),
napi_wrap, napi_wrap,
type_tag,
finalize: None, finalize: None,
finalize_hint: std::ptr::null_mut(), finalize_hint: std::ptr::null_mut(),
filename: std::ptr::null(), filename,
} }
} }
} }
pub enum ThreadSafeFunctionStatus {
Alive,
Dead,
}
#[repr(C)] #[repr(C)]
pub struct Env { pub struct Env {
context: NonNull<v8::Context>, context: NonNull<v8::Context>,
@ -316,46 +348,48 @@ pub struct Env {
pub open_handle_scopes: usize, pub open_handle_scopes: usize,
pub shared: *mut EnvShared, pub shared: *mut EnvShared,
pub async_work_sender: V8CrossThreadTaskSpawner, pub async_work_sender: V8CrossThreadTaskSpawner,
pub threadsafe_function_sender: pub cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
mpsc::UnboundedSender<ThreadSafeFunctionStatus>, pub external_ops_tracker: ExternalOpsTracker,
pub cleanup_hooks:
Rc<RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>>,
pub tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
pub last_error: napi_extended_error_info, pub last_error: napi_extended_error_info,
pub last_exception: Option<v8::Global<v8::Value>>,
pub global: NonNull<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 Send for Env {}
unsafe impl Sync for Env {} unsafe impl Sync for Env {}
impl Env { impl Env {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
isolate_ptr: *mut v8::OwnedIsolate, isolate_ptr: *mut v8::OwnedIsolate,
context: v8::Global<v8::Context>, context: v8::Global<v8::Context>,
global: v8::Global<v8::Value>, global: v8::Global<v8::Value>,
buffer_constructor: v8::Global<v8::Function>,
report_error: v8::Global<v8::Function>,
sender: V8CrossThreadTaskSpawner, sender: V8CrossThreadTaskSpawner,
threadsafe_function_sender: mpsc::UnboundedSender<ThreadSafeFunctionStatus>, cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
cleanup_hooks: Rc< external_ops_tracker: ExternalOpsTracker,
RefCell<Vec<(extern "C" fn(*const c_void), *const c_void)>>,
>,
tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
) -> Self { ) -> Self {
Self { Self {
isolate_ptr, isolate_ptr,
context: context.into_raw(), context: context.into_raw(),
global: global.into_raw(), global: global.into_raw(),
buffer_constructor: buffer_constructor.into_raw(),
report_error: report_error.into_raw(),
shared: std::ptr::null_mut(), shared: std::ptr::null_mut(),
open_handle_scopes: 0, open_handle_scopes: 0,
async_work_sender: sender, async_work_sender: sender,
threadsafe_function_sender,
cleanup_hooks, cleanup_hooks,
tsfn_ref_counters, external_ops_tracker,
last_error: napi_extended_error_info { last_error: napi_extended_error_info {
error_message: std::ptr::null(), error_message: std::ptr::null(),
engine_reserved: std::ptr::null_mut(), engine_reserved: std::ptr::null_mut(),
engine_error_code: 0, engine_error_code: 0,
error_code: napi_ok, error_code: napi_ok,
}, },
last_exception: None,
} }
} }
@ -384,7 +418,9 @@ impl Env {
// SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is // SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is
// already on the stack, but we don't have access to it. // already on the stack, but we don't have access to it.
let context = unsafe { 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 // 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` // 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) } unsafe { v8::CallbackScope::new(context) }
} }
pub fn add_threadsafe_function_ref_counter( pub fn threadsafe_function_ref(&mut self) {
&mut self, self.external_ops_tracker.ref_op();
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 remove_threadsafe_function_ref_counter(&mut self, id: usize) { pub fn threadsafe_function_unref(&mut self) {
let mut counters = self.tsfn_ref_counters.lock(); self.external_ops_tracker.unref_op();
let index = counters.iter().position(|(i, _)| *i == id).unwrap();
counters.remove(index);
} }
} }
@ -415,14 +443,8 @@ deno_core::extension!(deno_napi,
op_napi_open<P> op_napi_open<P>
], ],
state = |state| { state = |state| {
let (threadsafe_function_sender, threadsafe_function_receiver) =
mpsc::unbounded::<ThreadSafeFunctionStatus>();
state.put(NapiState { state.put(NapiState {
threadsafe_function_sender,
threadsafe_function_receiver,
active_threadsafe_functions: 0,
env_cleanup_hooks: Rc::new(RefCell::new(vec![])), 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 #[op2(reentrant)]
///
/// 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]
fn op_napi_open<NP, 'scope>( fn op_napi_open<NP, 'scope>(
scope: &mut v8::HandleScope<'scope>, scope: &mut v8::HandleScope<'scope>,
op_state: Rc<RefCell<OpState>>, op_state: Rc<RefCell<OpState>>,
#[string] path: String, #[string] path: String,
global: v8::Local<'scope, v8::Value>, 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> ) -> std::result::Result<v8::Local<'scope, v8::Value>, AnyError>
where where
NP: NapiPermissions + 'static, NP: NapiPermissions + 'static,
{ {
// We must limit the OpState borrow because this function can trigger a // We must limit the OpState borrow because this function can trigger a
// re-borrow through the NAPI module. // re-borrow through the NAPI module.
let ( let (async_work_sender, isolate_ptr, cleanup_hooks, external_ops_tracker) = {
async_work_sender,
tsfn_sender,
isolate_ptr,
cleanup_hooks,
tsfn_ref_counters,
) = {
let mut op_state = op_state.borrow_mut(); let mut op_state = op_state.borrow_mut();
let permissions = op_state.borrow_mut::<NP>(); let permissions = op_state.borrow_mut::<NP>();
permissions.check(Some(&PathBuf::from(&path)))?; permissions.check(Some(&PathBuf::from(&path)))?;
@ -511,10 +485,9 @@ where
let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>(); let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>();
( (
op_state.borrow::<V8CrossThreadTaskSpawner>().clone(), op_state.borrow::<V8CrossThreadTaskSpawner>().clone(),
napi_state.threadsafe_function_sender.clone(),
*isolate_ptr, *isolate_ptr,
napi_state.env_cleanup_hooks.clone(), 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::Private::new(scope, Some(napi_wrap_name));
let napi_wrap = v8::Global::new(scope, napi_wrap); 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. // The `module.exports` object.
let exports = v8::Object::new(scope); let exports = v8::Object::new(scope);
let mut env_shared = EnvShared::new(napi_wrap); let env_shared = EnvShared::new(napi_wrap, type_tag, path.clone());
let cstr = CString::new(&*path).unwrap();
env_shared.filename = cstr.as_ptr();
std::mem::forget(cstr);
let ctx = scope.get_current_context(); let ctx = scope.get_current_context();
let mut env = Env::new( let mut env = Env::new(
isolate_ptr, isolate_ptr,
v8::Global::new(scope, ctx), v8::Global::new(scope, ctx),
v8::Global::new(scope, global), v8::Global::new(scope, global),
v8::Global::new(scope, buffer_constructor),
v8::Global::new(scope, report_error),
async_work_sender, async_work_sender,
tsfn_sender,
cleanup_hooks, cleanup_hooks,
tsfn_ref_counters, external_ops_tracker,
); );
env.shared = Box::into_raw(Box::new(env_shared)); env.shared = Box::into_raw(Box::new(env_shared));
let env_ptr = Box::into_raw(Box::new(env)) as _; let env_ptr = Box::into_raw(Box::new(env)) as _;
@ -567,63 +542,30 @@ where
slot.take() 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. // SAFETY: napi_register_module guarantees that `module_to_register` is valid.
let nm = unsafe { &*module_to_register }; let nm = unsafe { &*module_to_register };
assert_eq!(nm.nm_version, 1); assert_eq!(nm.nm_version, 1);
// SAFETY: we are going blind, calling the register function on the other side. // SAFETY: we are going blind, calling the register function on the other side.
let maybe_exports = unsafe { unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
(nm.nm_register_func)( } else if let Ok(init) = unsafe {
env_ptr, library.get::<napi_register_module_v1>(b"napi_register_module_v1")
std::mem::transmute::<v8::Local<v8::Value>, napi_value>(exports.into()), } {
) // Initializer callback.
}; // SAFETY: we are going blind, calling the register function on the other side.
unsafe { init(env_ptr, 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)
}
} else { } 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 // NAPI addons can't be unloaded, so we're going to "forget" the library
// object so it lives till the program exit. // object so it lives till the program exit.
std::mem::forget(library); std::mem::forget(library);
Ok(exports) Ok(exports)
} }

View file

@ -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 _: () = { const _: () = {
assert!( assert!(
std::mem::size_of::<napi_value>() == std::mem::size_of::<*mut c_void>() std::mem::size_of::<napi_value>() == std::mem::size_of::<*mut c_void>()

View file

@ -1103,7 +1103,12 @@ Module._extensions[".node"] = function (module, filename) {
if (filename.endsWith("fsevents.node")) { if (filename.endsWith("fsevents.node")) {
throw new Error("Using fsevents module is currently not supported"); throw new Error("Using fsevents module is currently not supported");
} }
module.exports = op_napi_open(filename, globalThis); module.exports = op_napi_open(
filename,
globalThis,
nodeGlobals.Buffer,
reportError,
);
}; };
function createRequireFromPath(filename) { function createRequireFromPath(filename) {

View file

@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { Buffer } from "node:buffer";
import { assert, libSuffix } from "./common.js"; import { assert, libSuffix } from "./common.js";
const ops = Deno[Deno.internal].core.ops; const ops = Deno[Deno.internal].core.ops;
@ -8,7 +9,7 @@ Deno.test("ctr initialization (napi_module_register)", {
ignore: Deno.build.os == "windows", ignore: Deno.build.os == "windows",
}, function () { }, function () {
const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname; const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname;
const obj = ops.op_napi_open(path, {}); const obj = ops.op_napi_open(path, {}, Buffer, reportError);
assert(obj != null); assert(obj != null);
assert(typeof obj === "object"); assert(typeof obj === "object");
}); });

View file

@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { Buffer } from "node:buffer";
import { assert, assertEquals, loadTestLibrary } from "./common.js"; import { assert, assertEquals, loadTestLibrary } from "./common.js";
const objectWrap = loadTestLibrary(); const objectWrap = loadTestLibrary();
@ -30,7 +31,7 @@ Deno.test("napi external finalizer", function () {
Deno.test("napi external buffer", function () { Deno.test("napi external buffer", function () {
let buf = objectWrap.test_external_buffer(); let buf = objectWrap.test_external_buffer();
assertEquals(buf, new Uint8Array([1, 2, 3])); assertEquals(buf, new Buffer([1, 2, 3]));
buf = null; buf = null;
}); });

View file

@ -11,7 +11,6 @@ use std::ptr;
pub struct NapiObject { pub struct NapiObject {
counter: i32, counter: i32,
_wrapper: napi_ref,
} }
impl NapiObject { impl NapiObject {
@ -33,18 +32,14 @@ impl NapiObject {
assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value)); assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value));
let mut wrapper: napi_ref = ptr::null_mut(); let obj = Box::new(Self { counter: value });
let obj = Box::new(Self {
counter: value,
_wrapper: wrapper,
});
assert_napi_ok!(napi_wrap( assert_napi_ok!(napi_wrap(
env, env,
this, this,
Box::into_raw(obj) as *mut c_void, Box::into_raw(obj) as *mut c_void,
None, None,
ptr::null_mut(), ptr::null_mut(),
&mut wrapper, ptr::null_mut(),
)); ));
return this; return this;

View file

@ -31,11 +31,17 @@ async function getFilesFromGit(baseDir, args) {
throw new Error("gitLsFiles failed"); throw new Error("gitLsFiles failed");
} }
const files = output.split("\0").filter((line) => line.length > 0).map( const files = output
(filePath) => { .split("\0")
return Deno.realPathSync(join(baseDir, filePath)); .filter((line) => line.length > 0)
}, .map((filePath) => {
); try {
return Deno.realPathSync(join(baseDir, filePath));
} catch {
return null;
}
})
.filter((filePath) => filePath !== null);
return files; return files;
} }