summaryrefslogtreecommitdiff
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
parent7c5dbd5d54770dba5e56442b633e9597403ef5da (diff)
fix: Rewrite Node-API (#24101)
Phase 1 node-api rewrite
-rw-r--r--cli/napi/README.md7
-rw-r--r--cli/napi/async.rs102
-rw-r--r--cli/napi/env.rs174
-rw-r--r--cli/napi/generated_symbol_exports_list_linux.def2
-rw-r--r--cli/napi/generated_symbol_exports_list_macos.def11
-rw-r--r--cli/napi/generated_symbol_exports_list_windows.def11
-rw-r--r--cli/napi/js_native_api.rs4933
-rw-r--r--cli/napi/mod.rs4
-rw-r--r--cli/napi/node_api.rs1020
-rw-r--r--cli/napi/sym/lib.rs13
-rw-r--r--cli/napi/sym/symbol_exports.json11
-rw-r--r--cli/napi/threadsafe_functions.rs290
-rw-r--r--cli/napi/util.rs284
-rw-r--r--ext/napi/function.rs39
-rw-r--r--ext/napi/lib.rs284
-rw-r--r--ext/napi/value.rs13
-rw-r--r--ext/node/polyfills/01_require.js7
-rw-r--r--tests/napi/init_test.js3
-rw-r--r--tests/napi/object_wrap_test.js3
-rw-r--r--tests/napi/src/object_wrap.rs9
-rw-r--r--tools/util.js16
21 files changed, 4358 insertions, 2878 deletions
diff --git a/cli/napi/README.md b/cli/napi/README.md
index ec637e1bd..7b359ac6e 100644
--- a/cli/napi/README.md
+++ b/cli/napi/README.md
@@ -3,11 +3,8 @@
This directory contains source for Deno's Node-API implementation. It depends on
`napi_sym` and `deno_napi`.
-- [`async.rs`](./async.rs) - Asynchronous work related functions.
-- [`env.rs`](./env.rs) - Environment related functions.
-- [`js_native_api.rs`](./js_native_api.rs) - V8/JS related functions.
-- [`thread_safe_function.rs`](./threadsafe_functions.rs) - Thread safe function
- related functions.
+Files are generally organized the same as in Node.js's implementation to ease in
+ensuring compatibility.
## Adding a new function
diff --git a/cli/napi/async.rs b/cli/napi/async.rs
deleted file mode 100644
index 115aa742d..000000000
--- a/cli/napi/async.rs
+++ /dev/null
@@ -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
-}
diff --git a/cli/napi/env.rs b/cli/napi/env.rs
deleted file mode 100644
index 34948cde3..000000000
--- a/cli/napi/env.rs
+++ /dev/null
@@ -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
-}
diff --git a/cli/napi/generated_symbol_exports_list_linux.def b/cli/napi/generated_symbol_exports_list_linux.def
index eceac0a43..06e94f05b 100644
--- a/cli/napi/generated_symbol_exports_list_linux.def
+++ b/cli/napi/generated_symbol_exports_list_linux.def
@@ -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"; }; \ No newline at end of file
+{ "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"; }; \ No newline at end of file
diff --git a/cli/napi/generated_symbol_exports_list_macos.def b/cli/napi/generated_symbol_exports_list_macos.def
index be69d297f..cac7100c6 100644
--- a/cli/napi/generated_symbol_exports_list_macos.def
+++ b/cli/napi/generated_symbol_exports_list_macos.def
@@ -34,6 +34,7 @@ _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
@@ -74,6 +75,7 @@ _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
@@ -137,8 +139,15 @@ _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 \ No newline at end of file
+_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 \ No newline at end of file
diff --git a/cli/napi/generated_symbol_exports_list_windows.def b/cli/napi/generated_symbol_exports_list_windows.def
index 45f5d3aab..5386b46e5 100644
--- a/cli/napi/generated_symbol_exports_list_windows.def
+++ b/cli/napi/generated_symbol_exports_list_windows.def
@@ -36,6 +36,7 @@ EXPORTS
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
@@ -76,6 +77,7 @@ EXPORTS
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
@@ -139,8 +141,15 @@ EXPORTS
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 \ No newline at end of file
+ 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 \ No newline at end of file
diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs
index 428c4a04a..cbce113dc 100644
--- a/cli/napi/js_native_api.rs
+++ b/cli/napi/js_native_api.rs
@@ -1,2016 +1,3046 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
#![allow(non_upper_case_globals)]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+const NAPI_VERSION: u32 = 9;
use deno_runtime::deno_napi::*;
use libc::INT_MAX;
-use v8::BackingStore;
-use v8::UniqueRef;
+use super::util::check_new_from_utf8;
+use super::util::check_new_from_utf8_len;
use super::util::get_array_buffer_ptr;
+use super::util::make_external_backing_store;
+use super::util::napi_clear_last_error;
+use super::util::napi_set_last_error;
+use super::util::v8_name_from_property_descriptor;
+use crate::check_arg;
+use crate::check_env;
use deno_runtime::deno_napi::function::create_function;
use deno_runtime::deno_napi::function::create_function_template;
use deno_runtime::deno_napi::function::CallbackInfo;
+use napi_sym::napi_sym;
use std::ptr::NonNull;
-#[macro_export]
-macro_rules! check_env {
- ($env: expr) => {
- if $env.is_null() {
- return napi_invalid_arg;
- }
- };
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum ReferenceOwnership {
+ Runtime,
+ Userland,
}
-#[inline]
-unsafe fn napi_value_unchecked(val: napi_value) -> v8::Local<v8::Value> {
- transmute::<napi_value, v8::Local<v8::Value>>(val)
+enum ReferenceState {
+ Strong(v8::Global<v8::Value>),
+ Weak(v8::Weak<v8::Value>),
}
-#[macro_export]
-macro_rules! return_error_status_if_false {
- ($env: expr, $condition: expr, $status: ident) => {
- if !$condition {
- return Err(
- $crate::napi::js_native_api::napi_set_last_error(
- $env,
- $status,
- 0,
- std::ptr::null_mut(),
- )
- .into(),
- );
- }
- };
+struct Reference {
+ env: *mut Env,
+ state: ReferenceState,
+ ref_count: u32,
+ ownership: ReferenceOwnership,
+ finalize_cb: Option<napi_finalize>,
+ finalize_data: *mut c_void,
+ finalize_hint: *mut c_void,
}
-#[macro_export]
-macro_rules! return_status_if_false {
- ($env: expr, $condition: expr, $status: ident) => {
- if !$condition {
- return $crate::napi::js_native_api::napi_set_last_error(
- $env,
- $status,
- 0,
- std::ptr::null_mut(),
- );
+impl Reference {
+ fn new(
+ env: *mut Env,
+ value: v8::Local<v8::Value>,
+ initial_ref_count: u32,
+ ownership: ReferenceOwnership,
+ finalize_cb: Option<napi_finalize>,
+ finalize_data: *mut c_void,
+ finalize_hint: *mut c_void,
+ ) -> Box<Self> {
+ let isolate = unsafe { &mut *(*env).isolate_ptr };
+
+ let mut reference = Box::new(Reference {
+ env,
+ state: ReferenceState::Strong(v8::Global::new(isolate, value)),
+ ref_count: initial_ref_count,
+ ownership,
+ finalize_cb,
+ finalize_data,
+ finalize_hint,
+ });
+
+ if initial_ref_count == 0 {
+ reference.set_weak();
}
- };
-}
-fn check_new_from_utf8_len<'s>(
- env: *mut Env,
- str_: *const c_char,
- len: usize,
-) -> Result<v8::Local<'s, v8::String>, napi_status> {
- 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 };
- v8::String::new(&mut env.scope(), string)
- };
- return_error_status_if_false!(env, result.is_some(), napi_generic_failure);
- Ok(result.unwrap())
-}
+ reference
+ }
-#[inline]
-fn check_new_from_utf8<'s>(
- env: *mut Env,
- str_: *const c_char,
-) -> Result<v8::Local<'s, v8::String>, napi_status> {
- check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH)
-}
+ fn ref_(&mut self) -> u32 {
+ self.ref_count += 1;
+ if self.ref_count == 1 {
+ self.set_strong();
+ }
+ self.ref_count
+ }
-#[macro_export]
-macro_rules! status_call {
- ($call: expr) => {
- let status = $call;
- if status != napi_ok {
- return status;
+ fn unref(&mut self) -> u32 {
+ let old_ref_count = self.ref_count;
+ if self.ref_count > 0 {
+ self.ref_count -= 1;
}
- };
-}
+ if old_ref_count == 1 && self.ref_count == 0 {
+ self.set_weak();
+ }
+ self.ref_count
+ }
-// Macro to check napi arguments.
-// If nullptr, return napi_invalid_arg.
-#[macro_export]
-macro_rules! check_arg {
- ($env: expr, $ptr: expr) => {
- $crate::return_status_if_false!($env, !$ptr.is_null(), napi_invalid_arg);
- };
-}
+ fn reset(&mut self) {
+ self.finalize_cb = None;
+ self.finalize_data = std::ptr::null_mut();
+ self.finalize_hint = std::ptr::null_mut();
+ }
-macro_rules! check_arg_option {
- ($env: expr, $opt: expr) => {
- $crate::return_status_if_false!($env, $opt.is_some(), napi_invalid_arg);
- };
-}
+ fn set_strong(&mut self) {
+ if let ReferenceState::Weak(w) = &self.state {
+ let isolate = unsafe { &mut *(*self.env).isolate_ptr };
+ if let Some(g) = w.to_global(isolate) {
+ self.state = ReferenceState::Strong(g);
+ }
+ }
+ }
-fn napi_clear_last_error(env: *mut Env) {
- 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();
-}
+ fn set_weak(&mut self) {
+ let reference = self as *mut Reference;
+ if let ReferenceState::Strong(g) = &self.state {
+ let cb = Box::new(move |_: &mut v8::Isolate| {
+ Reference::weak_callback(reference)
+ });
+ let isolate = unsafe { &mut *(*self.env).isolate_ptr };
+ self.state =
+ ReferenceState::Weak(v8::Weak::with_finalizer(isolate, g, cb));
+ }
+ }
-pub(crate) fn napi_set_last_error(
- env: *mut Env,
- error_code: napi_status,
- engine_error_code: i32,
- engine_reserved: *mut c_void,
-) -> napi_status {
- let env = unsafe { &mut *env };
- env.last_error.error_code = error_code;
- env.last_error.engine_error_code = engine_error_code;
- env.last_error.engine_reserved = engine_reserved;
- error_code
-}
+ fn weak_callback(reference: *mut Reference) {
+ let reference = unsafe { &mut *reference };
-/// Returns napi_value that represents a new JavaScript Array.
-#[napi_sym::napi_sym]
-fn napi_create_array(env: *mut Env, result: *mut napi_value) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Array::new(&mut env.scope(), 0).into();
- napi_ok
-}
+ let finalize_cb = reference.finalize_cb;
+ let finalize_data = reference.finalize_data;
+ let finalize_hint = reference.finalize_hint;
+ reference.reset();
-#[napi_sym::napi_sym]
-fn napi_create_array_with_length(
- env: *mut Env,
- len: i32,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Array::new(&mut env.scope(), len).into();
- napi_ok
-}
+ if let Some(finalize_cb) = finalize_cb {
+ unsafe {
+ finalize_cb(reference.env as _, finalize_data, finalize_hint);
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_create_arraybuffer(
- env: *mut Env,
- len: usize,
- data: *mut *mut u8,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
+ if reference.ownership == ReferenceOwnership::Runtime {
+ unsafe { drop(Reference::from_raw(reference)) }
+ }
+ }
- let value = v8::ArrayBuffer::new(&mut env.scope(), len);
- if !data.is_null() {
- *data = get_array_buffer_ptr(value);
+ fn into_raw(r: Box<Reference>) -> *mut Reference {
+ Box::into_raw(r)
}
- *result = value.into();
- napi_ok
+ unsafe fn from_raw(r: *mut Reference) -> Box<Reference> {
+ unsafe { Box::from_raw(r) }
+ }
+
+ unsafe fn remove(r: *mut Reference) {
+ let r = unsafe { &mut *r };
+ if r.ownership == ReferenceOwnership::Userland {
+ r.reset();
+ } else {
+ unsafe { drop(Reference::from_raw(r)) }
+ }
+ }
}
-#[napi_sym::napi_sym]
-fn napi_create_bigint_int64(
+#[napi_sym]
+fn napi_get_last_error_info(
env: *mut Env,
- value: i64,
- result: *mut napi_value,
+ result: *mut *const napi_extended_error_info,
) -> napi_status {
- check_env!(env);
+ let env = check_env!(env);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into();
+
+ if env.last_error.error_code == napi_ok {
+ napi_clear_last_error(env);
+ } else {
+ env.last_error.error_message =
+ ERROR_MESSAGES[env.last_error.error_code as usize].as_ptr();
+ }
+
+ unsafe {
+ *result = &env.last_error;
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_bigint_uint64(
- env: *mut Env,
- value: u64,
+#[napi_sym]
+fn napi_create_function(
+ env: &mut Env,
+ name: *const c_char,
+ length: usize,
+ cb: napi_callback,
+ cb_info: napi_callback_info,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into();
+ check_arg!(env, cb);
+
+ let name = if !name.is_null() {
+ match unsafe { check_new_from_utf8_len(env, name, length) } {
+ Ok(s) => Some(s),
+ Err(status) => return status,
+ }
+ } else {
+ None
+ };
+
+ unsafe {
+ *result = create_function(env, name, cb, cb_info).into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_bigint_words(
- env: *mut Env,
- sign_bit: bool,
- word_count: usize,
- words: *const u64,
- result: *mut napi_value,
+#[napi_sym]
+#[allow(clippy::too_many_arguments)]
+fn napi_define_class<'s>(
+ env: &'s mut Env,
+ utf8name: *const c_char,
+ length: usize,
+ constructor: napi_callback,
+ callback_data: *mut c_void,
+ property_count: usize,
+ properties: *const napi_property_descriptor,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- check_arg!(env, words);
- let env = unsafe { &mut *env };
check_arg!(env, result);
+ check_arg!(env, constructor);
- if word_count > INT_MAX as _ {
- return napi_invalid_arg;
+ if property_count > 0 {
+ check_arg!(env, properties);
}
- match v8::BigInt::new_from_words(
- &mut env.scope(),
- sign_bit,
- std::slice::from_raw_parts(words, word_count),
- ) {
- Some(value) => {
- *result = value.into();
+ let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } {
+ Ok(string) => string,
+ Err(status) => return status,
+ };
+
+ let tpl = unsafe {
+ create_function_template(env, Some(name), constructor, callback_data)
+ };
+
+ let napi_properties: &[napi_property_descriptor] = if property_count > 0 {
+ unsafe { std::slice::from_raw_parts(properties, property_count) }
+ } else {
+ &[]
+ };
+ let mut static_property_count = 0;
+
+ for p in napi_properties {
+ if p.attributes & napi_static != 0 {
+ // Will be handled below
+ static_property_count += 1;
+ continue;
}
- None => {
- return napi_invalid_arg;
+
+ let name = match unsafe { v8_name_from_property_descriptor(env, p) } {
+ Ok(name) => name,
+ Err(status) => return status,
+ };
+
+ let method = p.method;
+ let getter = p.getter;
+ let setter = p.setter;
+
+ if getter.is_some() || setter.is_some() {
+ let getter: Option<v8::Local<v8::FunctionTemplate>> = if getter.is_some()
+ {
+ Some(unsafe { create_function_template(env, None, p.getter, p.data) })
+ } else {
+ None
+ };
+ let setter: Option<v8::Local<v8::FunctionTemplate>> = if setter.is_some()
+ {
+ Some(unsafe { create_function_template(env, None, p.setter, p.data) })
+ } else {
+ None
+ };
+
+ let mut accessor_property = v8::PropertyAttribute::NONE;
+ if getter.is_some()
+ && setter.is_some()
+ && (p.attributes & napi_writable) == 0
+ {
+ accessor_property =
+ accessor_property | v8::PropertyAttribute::READ_ONLY;
+ }
+ if p.attributes & napi_enumerable == 0 {
+ accessor_property =
+ accessor_property | v8::PropertyAttribute::DONT_ENUM;
+ }
+ if p.attributes & napi_configurable == 0 {
+ accessor_property =
+ accessor_property | v8::PropertyAttribute::DONT_DELETE;
+ }
+
+ let proto = tpl.prototype_template(&mut env.scope());
+ proto.set_accessor_property(name, getter, setter, accessor_property);
+ } else if method.is_some() {
+ let function =
+ unsafe { create_function_template(env, None, p.method, p.data) };
+ let proto = tpl.prototype_template(&mut env.scope());
+ proto.set(name, function.into());
+ } else {
+ let proto = tpl.prototype_template(&mut env.scope());
+ proto.set(name, p.value.unwrap().into());
}
}
+ let env_ptr = env as *mut Env;
+ let value: v8::Local<v8::Value> =
+ tpl.get_function(&mut env.scope()).unwrap().into();
+
+ unsafe {
+ *result = value.into();
+ }
+
+ if static_property_count > 0 {
+ let mut static_descriptors = Vec::with_capacity(static_property_count);
+
+ for p in napi_properties {
+ if p.attributes & napi_static != 0 {
+ static_descriptors.push(*p);
+ }
+ }
+
+ crate::status_call!(unsafe {
+ napi_define_properties(
+ env_ptr,
+ *result,
+ static_descriptors.len(),
+ static_descriptors.as_ptr(),
+ )
+ });
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_buffer(
+#[napi_sym]
+fn napi_get_property_names(
env: *mut Env,
- len: usize,
- data: *mut *mut u8,
+ object: napi_value,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = v8::ArrayBuffer::new(&mut env.scope(), len);
- if !data.is_null() {
- *data = get_array_buffer_ptr(value);
+ unsafe {
+ napi_get_all_property_names(
+ env,
+ object,
+ napi_key_include_prototypes,
+ napi_key_enumerable | napi_key_skip_symbols,
+ napi_key_numbers_to_strings,
+ result,
+ )
}
- let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap();
- let value: v8::Local<v8::Value> = value.into();
- *result = value.into();
- napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_buffer_copy(
- env: *mut Env,
- len: usize,
- data: *mut u8,
- result_data: *mut *mut u8,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_get_all_property_names<'s>(
+ env: &'s mut Env,
+ object: napi_value,
+ key_mode: napi_key_collection_mode,
+ key_filter: napi_key_filter,
+ key_conversion: napi_key_conversion,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = v8::ArrayBuffer::new(&mut env.scope(), len);
- let ptr = get_array_buffer_ptr(value);
- std::ptr::copy(data, ptr, len);
- if !result_data.is_null() {
- *result_data = ptr;
- }
- let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap();
- let value: v8::Local<v8::Value> = value.into();
- *result = value.into();
+ check_arg!(env, result);
+
+ let scope = &mut env.scope();
+
+ let Some(obj) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let mut filter = v8::PropertyFilter::ALL_PROPERTIES;
+
+ if key_filter & napi_key_writable != 0 {
+ filter = filter | v8::PropertyFilter::ONLY_WRITABLE;
+ }
+ if key_filter & napi_key_enumerable != 0 {
+ filter = filter | v8::PropertyFilter::ONLY_ENUMERABLE;
+ }
+ if key_filter & napi_key_configurable != 0 {
+ filter = filter | v8::PropertyFilter::ONLY_CONFIGURABLE;
+ }
+ if key_filter & napi_key_skip_strings != 0 {
+ filter = filter | v8::PropertyFilter::SKIP_STRINGS;
+ }
+ if key_filter & napi_key_skip_symbols != 0 {
+ filter = filter | v8::PropertyFilter::SKIP_SYMBOLS;
+ }
+
+ let key_mode = match key_mode {
+ napi_key_include_prototypes => v8::KeyCollectionMode::IncludePrototypes,
+ napi_key_own_only => v8::KeyCollectionMode::OwnOnly,
+ _ => return napi_invalid_arg,
+ };
+
+ let key_conversion = match key_conversion {
+ napi_key_keep_numbers => v8::KeyConversionMode::KeepNumbers,
+ napi_key_numbers_to_strings => v8::KeyConversionMode::ConvertToString,
+ _ => return napi_invalid_arg,
+ };
+
+ let filter = v8::GetPropertyNamesArgsBuilder::new()
+ .mode(key_mode)
+ .property_filter(filter)
+ .index_filter(v8::IndexFilter::IncludeIndices)
+ .key_conversion(key_conversion)
+ .build();
+
+ let property_names = match obj.get_property_names(scope, filter) {
+ Some(n) => n,
+ None => return napi_generic_failure,
+ };
+
+ unsafe {
+ *result = property_names.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_coerce_to_bool(
- env: *mut Env,
+#[napi_sym]
+fn napi_set_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
value: napi_value,
- result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let coerced = value.to_boolean(&mut env.scope());
- let value: v8::Local<v8::Value> = coerced.into();
- *result = value.into();
+ check_arg!(env, key);
+ check_arg!(env, value);
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ if object.set(scope, key.unwrap(), value.unwrap()).is_none() {
+ return napi_generic_failure;
+ };
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_coerce_to_number(
- env: *mut Env,
- value: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_has_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let Some(coerced) = value.to_number(&mut env.scope()) else {
- return napi_number_expected;
+ check_arg!(env, key);
+ check_arg!(env, result);
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
};
- let value: v8::Local<v8::Value> = coerced.into();
- *result = value.into();
+
+ let Some(has) = object.has(scope, key.unwrap()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = has;
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_coerce_to_object(
- env: *mut Env,
- value: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_get_property<'s>(
+ env: &'s mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let coerced = value.to_object(&mut env.scope()).unwrap();
- let value: v8::Local<v8::Value> = coerced.into();
- *result = value.into();
+ check_arg!(env, key);
+ check_arg!(env, result);
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let Some(value) = object.get(scope, key.unwrap()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = value.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_coerce_to_string(
- env: *mut Env,
- value: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_delete_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let coerced = value.to_string(&mut env.scope()).unwrap();
- let value: v8::Local<v8::Value> = coerced.into();
- *result = value.into();
+ check_arg!(env, key);
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let Some(deleted) = object.delete(scope, key.unwrap()) else {
+ return napi_generic_failure;
+ };
+
+ if !result.is_null() {
+ unsafe {
+ *result = deleted;
+ }
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_dataview(
- env: *mut Env,
- len: usize,
- data: *mut *mut u8,
- byte_offset: usize,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_has_own_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- check_arg!(env, data);
- let env = unsafe { &mut *env };
+ check_arg!(env, key);
check_arg!(env, result);
- let value = v8::ArrayBuffer::new(&mut env.scope(), len);
- if !data.is_null() {
- *data = get_array_buffer_ptr(value);
- }
- let context = &mut env.scope().get_current_context();
- let global = context.global(&mut env.scope());
- let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap();
- let data_view = global.get(&mut env.scope(), data_view_name.into()).unwrap();
- let Ok(data_view) = v8::Local::<v8::Function>::try_from(data_view) else {
- return napi_function_expected;
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
};
- let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64);
- let byte_length = v8::Number::new(&mut env.scope(), len as f64);
- let value = data_view
- .new_instance(
- &mut env.scope(),
- &[value.into(), byte_offset.into(), byte_length.into()],
- )
- .unwrap();
- let value: v8::Local<v8::Value> = value.into();
- *result = value.into();
- napi_ok
-}
-#[napi_sym::napi_sym]
-fn napi_create_date(
- env: *mut Env,
- time: f64,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value: v8::Local<v8::Value> =
- v8::Date::new(&mut env.scope(), time).unwrap().into();
- *result = value.into();
+ let Ok(key) = v8::Local::<v8::Name>::try_from(key.unwrap()) else {
+ return napi_name_expected;
+ };
+
+ let Some(has_own) = object.has_own_property(scope, key) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = has_own;
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_double(
- env: *mut Env,
- value: f64,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_has_named_property<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ utf8name: *const c_char,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
+ let env_ptr = env as *mut Env;
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Number::new(&mut env.scope(), value).into();
+
+ let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else {
+ return napi_object_expected;
+ };
+
+ let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } {
+ Ok(key) => key,
+ Err(status) => return status,
+ };
+
+ let Some(has_property) = object.has(&mut env.scope(), key.into()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = has_property;
+ }
+
napi_ok
}
-fn set_error_code(
- env: *mut Env,
- error: v8::Local<v8::Value>,
- code: napi_value,
- code_cstring: *const c_char,
+#[napi_sym]
+fn napi_set_named_property<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ utf8name: *const c_char,
+ value: napi_value<'s>,
) -> napi_status {
- if code.is_some() || !code_cstring.is_null() {
- let err_object: v8::Local<v8::Object> = error.try_into().unwrap();
+ check_arg!(env, value);
+ let env_ptr = env as *mut Env;
- let code_value: v8::Local<v8::Value> = if code.is_some() {
- let mut code_value = unsafe { napi_value_unchecked(code) };
- return_status_if_false!(
- env,
- code_value.is_string(),
- napi_string_expected
- );
- code_value
- } else {
- let name = match check_new_from_utf8(env, code_cstring) {
- Ok(s) => s,
- Err(status) => return status,
- };
- name.into()
- };
+ let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else {
+ return napi_object_expected;
+ };
+
+ let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } {
+ Ok(key) => key,
+ Err(status) => return status,
+ };
- let mut scope = unsafe { &mut *env }.scope();
- let code_key = v8::String::new(&mut scope, "code").unwrap();
+ let value = value.unwrap();
- if err_object
- .set(&mut scope, code_key.into(), code_value)
- .is_none()
- {
- return napi_generic_failure;
- }
+ if !object
+ .set(&mut env.scope(), key.into(), value)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
}
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_error(
- env: *mut Env,
- code: napi_value,
- msg: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_get_named_property<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ utf8name: *const c_char,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- check_arg_option!(env, msg);
check_arg!(env, result);
- let mut message_value = napi_value_unchecked(msg);
- return_status_if_false!(env, message_value.is_string(), napi_string_expected);
- let error_obj = v8::Exception::error(
- &mut unsafe { &mut *env }.scope(),
- message_value.try_into().unwrap(),
- );
- status_call!(set_error_code(env, error_obj, code, std::ptr::null()));
- *result = error_obj.into();
- napi_clear_last_error(env);
+ let env_ptr = env as *mut Env;
+
+ let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else {
+ return napi_object_expected;
+ };
+
+ let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } {
+ Ok(key) => key,
+ Err(status) => return status,
+ };
+
+ let Some(value) = object.get(&mut env.scope(), key.into()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = value.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_type_error(
- env: *mut Env,
- code: napi_value,
- msg: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_set_element<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ index: u32,
+ value: napi_value<'s>,
) -> napi_status {
- check_env!(env);
- check_arg_option!(env, msg);
- check_arg!(env, result);
- let mut message_value = napi_value_unchecked(msg);
- return_status_if_false!(env, message_value.is_string(), napi_string_expected);
- let error_obj = v8::Exception::type_error(
- &mut unsafe { &mut *env }.scope(),
- message_value.try_into().unwrap(),
- );
- status_call!(set_error_code(env, error_obj, code, std::ptr::null()));
- *result = error_obj.into();
- napi_clear_last_error(env);
+ check_arg!(env, value);
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ if !object
+ .set_index(scope, index, value.unwrap())
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_range_error(
- env: *mut Env,
- code: napi_value,
- msg: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_has_element(
+ env: &mut Env,
+ object: napi_value,
+ index: u32,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- check_arg_option!(env, msg);
check_arg!(env, result);
- let mut message_value = napi_value_unchecked(msg);
- return_status_if_false!(env, message_value.is_string(), napi_string_expected);
- let error_obj = v8::Exception::range_error(
- &mut unsafe { &mut *env }.scope(),
- message_value.try_into().unwrap(),
- );
- status_call!(set_error_code(env, error_obj, code, std::ptr::null()));
- *result = error_obj.into();
- napi_clear_last_error(env);
- napi_ok
-}
-#[napi_sym::napi_sym]
-fn napi_create_external(
- env_ptr: *mut Env,
- value: *mut c_void,
- finalize_cb: napi_finalize,
- finalize_hint: *mut c_void,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env_ptr);
- let env = unsafe { &mut *env_ptr };
- let external: v8::Local<v8::Value> =
- v8::External::new(&mut env.scope(), value).into();
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let Some(has) = object.has_index(scope, index) else {
+ return napi_generic_failure;
+ };
- let value = weak_local(env_ptr, external, value, finalize_cb, finalize_hint);
+ unsafe {
+ *result = has;
+ }
- *result = transmute(value);
napi_ok
}
-pub type BackingStoreDeleterCallback = unsafe extern "C" fn(
- data: *mut c_void,
- byte_length: usize,
- deleter_data: *mut c_void,
-);
+#[napi_sym]
+fn napi_get_element<'s>(
+ env: &'s mut Env,
+ object: napi_value,
+ index: u32,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, result);
-extern "C" {
- fn v8__ArrayBuffer__NewBackingStore__with_data(
- data: *mut c_void,
- byte_length: usize,
- deleter: BackingStoreDeleterCallback,
- deleter_data: *mut c_void,
- ) -> *mut BackingStore;
-}
+ let scope = &mut env.scope();
-struct BufferFinalizer {
- env: *mut Env,
- finalize_cb: napi_finalize,
- finalize_data: *mut c_void,
- finalize_hint: *mut c_void,
-}
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
-impl BufferFinalizer {
- fn into_raw(self) -> *mut BufferFinalizer {
- Box::into_raw(Box::new(self))
+ let Some(value) = object.get_index(scope, index) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = value.into();
}
+
+ napi_ok
}
-impl Drop for BufferFinalizer {
- fn drop(&mut self) {
+#[napi_sym]
+fn napi_delete_element(
+ env: &mut Env,
+ object: napi_value,
+ index: u32,
+ result: *mut bool,
+) -> napi_status {
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let Some(deleted) = object.delete_index(scope, index) else {
+ return napi_generic_failure;
+ };
+
+ if !result.is_null() {
unsafe {
- (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint);
+ *result = deleted;
}
}
-}
-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::from_raw(deleter_data as *mut BufferFinalizer) };
-
- finalizer.finalize_data = data;
+ napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_external_arraybuffer(
- env_ptr: *mut Env,
- data: *mut c_void,
- byte_length: usize,
- finalize_cb: napi_finalize,
- finalize_hint: *mut c_void,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_define_properties(
+ env: &mut Env,
+ object: napi_value,
+ property_count: usize,
+ properties: *const napi_property_descriptor,
) -> napi_status {
- check_env!(env_ptr);
- let env = unsafe { &mut *env_ptr };
+ let env_ptr = env as *mut Env;
- let finalizer = BufferFinalizer {
- env: env_ptr,
- finalize_data: ptr::null_mut(),
- finalize_cb,
- finalize_hint,
+ if property_count > 0 {
+ check_arg!(env, properties);
+ }
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
};
- let store: UniqueRef<BackingStore> =
- transmute(v8__ArrayBuffer__NewBackingStore__with_data(
- data,
- byte_length,
- backing_store_deleter_callback,
- finalizer.into_raw() as _,
- ));
+ let properties = if property_count == 0 {
+ &[]
+ } else {
+ unsafe { std::slice::from_raw_parts(properties, property_count) }
+ };
+ for property in properties {
+ let property_name =
+ match unsafe { v8_name_from_property_descriptor(env_ptr, property) } {
+ Ok(name) => name,
+ Err(status) => return status,
+ };
- let ab =
- v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
- let value: v8::Local<v8::Value> = ab.into();
+ let writable = property.attributes & napi_writable != 0;
+ let enumerable = property.attributes & napi_enumerable != 0;
+ let configurable = property.attributes & napi_configurable != 0;
+
+ if property.getter.is_some() || property.setter.is_some() {
+ let local_getter: v8::Local<v8::Value> = if property.getter.is_some() {
+ unsafe {
+ create_function(env_ptr, None, property.getter, property.data).into()
+ }
+ } else {
+ v8::undefined(scope).into()
+ };
+ let local_setter: v8::Local<v8::Value> = if property.setter.is_some() {
+ unsafe {
+ create_function(env_ptr, None, property.setter, property.data).into()
+ }
+ } else {
+ v8::undefined(scope).into()
+ };
+
+ let mut desc =
+ v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter);
+ desc.set_enumerable(enumerable);
+ desc.set_configurable(configurable);
+
+ if !object
+ .define_property(scope, property_name, &desc)
+ .unwrap_or(false)
+ {
+ return napi_invalid_arg;
+ }
+ } else if property.method.is_some() {
+ let method: v8::Local<v8::Value> = {
+ let function = unsafe {
+ create_function(env_ptr, None, property.method, property.data)
+ };
+ function.into()
+ };
+
+ let mut desc =
+ v8::PropertyDescriptor::new_from_value_writable(method, writable);
+ desc.set_enumerable(enumerable);
+ desc.set_configurable(configurable);
+
+ if !object
+ .define_property(scope, property_name, &desc)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+ } else {
+ let value = property.value.unwrap();
+
+ if enumerable & writable & configurable {
+ if !object
+ .create_data_property(scope, property_name, value)
+ .unwrap_or(false)
+ {
+ return napi_invalid_arg;
+ }
+ } else {
+ let mut desc =
+ v8::PropertyDescriptor::new_from_value_writable(value, writable);
+ desc.set_enumerable(enumerable);
+ desc.set_configurable(configurable);
+
+ if !object
+ .define_property(scope, property_name, &desc)
+ .unwrap_or(false)
+ {
+ return napi_invalid_arg;
+ }
+ }
+ }
+ }
- *result = value.into();
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_external_buffer(
- env_ptr: *mut Env,
- byte_length: usize,
- data: *mut c_void,
- finalize_cb: napi_finalize,
- finalize_hint: *mut c_void,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env_ptr);
- let env = unsafe { &mut *env_ptr };
- let finalizer = BufferFinalizer {
- env: env_ptr,
- finalize_data: ptr::null_mut(),
- finalize_cb,
- finalize_hint,
+#[napi_sym]
+fn napi_object_freeze(env: &mut Env, object: napi_value) -> napi_status {
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
};
- let store: UniqueRef<BackingStore> =
- transmute(v8__ArrayBuffer__NewBackingStore__with_data(
- data,
- byte_length,
- backing_store_deleter_callback,
- finalizer.into_raw() as _,
- ));
+ if !object
+ .set_integrity_level(scope, v8::IntegrityLevel::Frozen)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
- let ab =
- v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
- let value =
- v8::Uint8Array::new(&mut env.scope(), ab, 0, byte_length).unwrap();
- let value: v8::Local<v8::Value> = value.into();
- *result = value.into();
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_function(
+#[napi_sym]
+fn napi_object_seal(env: &mut Env, object: napi_value) -> napi_status {
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ if !object
+ .set_integrity_level(scope, v8::IntegrityLevel::Sealed)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_array(
env: *mut Env,
- name: *const c_char,
- length: usize,
- cb: napi_callback,
- cb_info: napi_callback_info,
- result: *mut napi_value,
+ value: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
+ let env = check_env!(env);
+ check_arg!(env, value);
check_arg!(env, result);
- check_arg_option!(env, cb);
- let name = if let Some(name) = name.as_ref() {
- match check_new_from_utf8_len(env, name, length) {
- Ok(s) => Some(s),
- Err(status) => return status,
+ let value = value.unwrap();
+
+ unsafe {
+ *result = value.is_array();
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_array_length(
+ env: &mut Env,
+ value: napi_value,
+ result: *mut u32,
+) -> napi_status {
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let value = value.unwrap();
+
+ match v8::Local::<v8::Array>::try_from(value) {
+ Ok(array) => {
+ unsafe {
+ *result = array.length();
+ }
+ napi_ok
}
- } else {
- None
- };
+ Err(_) => napi_array_expected,
+ }
+}
+
+#[napi_sym]
+fn napi_strict_equals(
+ env: &mut Env,
+ lhs: napi_value,
+ rhs: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ check_arg!(env, lhs);
+ check_arg!(env, rhs);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = lhs.unwrap().strict_equals(rhs.unwrap());
+ }
- *result = create_function(env, name, cb, cb_info).into();
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_int32(
- env: *mut Env,
- value: i32,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_get_prototype<'s>(
+ env: &'s mut Env,
+ object: napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Integer::new(&mut env.scope(), value).into();
+
+ let scope = &mut env.scope();
+
+ let Some(object) = object.and_then(|o| o.to_object(scope)) else {
+ return napi_object_expected;
+ };
+
+ let Some(proto) = object.get_prototype(scope) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = proto.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_uint32(
- env: *mut Env,
- value: u32,
+#[napi_sym]
+fn napi_create_object(
+ env_ptr: *mut Env,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
+ let env = check_env!(env_ptr);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into();
- napi_ok
+
+ unsafe {
+ *result = v8::Object::new(&mut env.scope()).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_int64(
- env: *mut Env,
- value: i64,
+#[napi_sym]
+fn napi_create_array(
+ env_ptr: *mut Env,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
+ let env = check_env!(env_ptr);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Number::new(&mut env.scope(), value as f64).into();
- napi_ok
-}
-#[napi_sym::napi_sym]
-fn napi_create_object(env: *mut Env, result: *mut napi_value) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = v8::Object::new(&mut env.scope());
- *result = object.into();
- napi_ok
+ unsafe {
+ *result = v8::Array::new(&mut env.scope(), 0).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_promise(
- env: *mut Env,
- deferred: *mut napi_deferred,
- promise_out: *mut napi_value,
+#[napi_sym]
+fn napi_create_array_with_length(
+ env_ptr: *mut Env,
+ length: usize,
+ result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap();
- let mut global = v8::Global::new(&mut env.scope(), resolver);
- let mut global_ptr = global.into_raw();
- let promise = resolver.get_promise(&mut env.scope());
- *deferred = global_ptr.as_mut() as *mut _ as napi_deferred;
- *promise_out = promise.into();
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
- napi_ok
+ unsafe {
+ *result = v8::Array::new(&mut env.scope(), length as _).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_reference(
- env: *mut Env,
- value: napi_value,
- _initial_refcount: u32,
- result: *mut napi_ref,
+#[napi_sym]
+fn napi_create_string_latin1(
+ env_ptr: *mut Env,
+ string: *const c_char,
+ length: usize,
+ result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ let env = check_env!(env_ptr);
+ if length > 0 {
+ check_arg!(env, string);
+ }
+ crate::return_status_if_false!(
+ env,
+ (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _,
+ napi_invalid_arg
+ );
- let value = napi_value_unchecked(value);
- let global = v8::Global::new(&mut env.scope(), value);
- let mut global_ptr = global.into_raw();
- *result = transmute::<NonNull<v8::Value>, napi_ref>(global_ptr);
- napi_ok
+ let buffer = if length > 0 {
+ unsafe {
+ std::slice::from_raw_parts(
+ string as _,
+ if length == NAPI_AUTO_LENGTH {
+ std::ffi::CStr::from_ptr(string).to_bytes().len()
+ } else {
+ length
+ },
+ )
+ }
+ } else {
+ &[]
+ };
+
+ let Some(string) = v8::String::new_from_one_byte(
+ &mut env.scope(),
+ buffer,
+ v8::NewStringType::Normal,
+ ) else {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+ };
+
+ unsafe {
+ *result = string.into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_string_latin1(
- env: *mut Env,
- string: *const u8,
+#[napi_sym]
+fn napi_create_string_utf8(
+ env_ptr: *mut Env,
+ string: *const c_char,
length: usize,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ let env = check_env!(env_ptr);
if length > 0 {
check_arg!(env, string);
}
- check_arg!(env, result);
- return_status_if_false!(
+ crate::return_status_if_false!(
env,
(length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _,
napi_invalid_arg
);
- let string = if length == NAPI_AUTO_LENGTH {
- std::ffi::CStr::from_ptr(string as *const _)
- .to_str()
- .unwrap()
- .as_bytes()
+ let buffer = if length > 0 {
+ unsafe {
+ std::slice::from_raw_parts(
+ string as _,
+ if length == NAPI_AUTO_LENGTH {
+ std::ffi::CStr::from_ptr(string).to_bytes().len()
+ } else {
+ length
+ },
+ )
+ }
} else {
- std::slice::from_raw_parts(string, length)
+ &[]
};
- let Some(v8str) = v8::String::new_from_one_byte(
+
+ let Some(string) = v8::String::new_from_utf8(
&mut env.scope(),
- string,
+ buffer,
v8::NewStringType::Normal,
) else {
- return napi_generic_failure;
+ return napi_set_last_error(env_ptr, napi_generic_failure);
};
- *result = v8str.into();
- napi_ok
+ unsafe {
+ *result = string.into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_create_string_utf16(
- env: *mut Env,
+ env_ptr: *mut Env,
string: *const u16,
length: usize,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ let env = check_env!(env_ptr);
if length > 0 {
check_arg!(env, string);
}
- check_arg!(env, result);
- return_status_if_false!(
+ crate::return_status_if_false!(
env,
(length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _,
napi_invalid_arg
);
- let string = if length == NAPI_AUTO_LENGTH {
- let s = std::ffi::CStr::from_ptr(string as *const _)
- .to_str()
- .unwrap();
- std::slice::from_raw_parts(s.as_ptr() as *const u16, s.len())
+ let buffer = if length > 0 {
+ unsafe {
+ std::slice::from_raw_parts(
+ string,
+ if length == NAPI_AUTO_LENGTH {
+ let mut length = 0;
+ while *(string.add(length)) != 0 {
+ length += 1;
+ }
+ length
+ } else {
+ length
+ },
+ )
+ }
} else {
- std::slice::from_raw_parts(string, length)
+ &[]
};
- match v8::String::new_from_two_byte(
+ let Some(string) = v8::String::new_from_two_byte(
&mut env.scope(),
- string,
+ buffer,
v8::NewStringType::Normal,
- ) {
- Some(v8str) => {
- *result = v8str.into();
- }
- None => return napi_generic_failure,
+ ) else {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+ };
+
+ unsafe {
+ *result = string.into();
}
- napi_ok
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_string_utf8(
- env: *mut Env,
- string: *const u8,
+#[napi_sym]
+fn node_api_create_external_string_latin1(
+ env_ptr: *mut Env,
+ _string: *const c_char,
+ _length: usize,
+ _nogc_finalize_callback: napi_finalize,
+ _finalize_hint: *mut c_void,
+ _result: *mut napi_value,
+ _copied: *mut bool,
+) -> napi_status {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+}
+
+#[napi_sym]
+fn node_api_create_external_string_utf16(
+ env_ptr: *mut Env,
+ _string: *const u16,
+ _length: usize,
+ _nogc_finalize_callback: napi_finalize,
+ _finalize_hint: *mut c_void,
+ _result: *mut napi_value,
+ _copied: *mut bool,
+) -> napi_status {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+}
+
+#[napi_sym]
+fn node_api_create_property_key_utf16(
+ env_ptr: *mut Env,
+ string: *const u16,
length: usize,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ let env = check_env!(env_ptr);
if length > 0 {
check_arg!(env, string);
}
- check_arg!(env, result);
- return_status_if_false!(
+ crate::return_status_if_false!(
env,
(length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _,
napi_invalid_arg
);
- let string = if length == NAPI_AUTO_LENGTH {
- std::ffi::CStr::from_ptr(string as *const _)
- .to_str()
- .unwrap()
+ let buffer = if length > 0 {
+ unsafe {
+ std::slice::from_raw_parts(
+ string,
+ if length == NAPI_AUTO_LENGTH {
+ let mut length = 0;
+ while *(string.add(length)) != 0 {
+ length += 1;
+ }
+ length
+ } else {
+ length
+ },
+ )
+ }
} else {
- let string = std::slice::from_raw_parts(string, length);
- std::str::from_utf8(string).unwrap()
+ &[]
};
- let v8str = v8::String::new(&mut env.scope(), string).unwrap();
- *result = v8str.into();
+
+ let Some(string) = v8::String::new_from_two_byte(
+ &mut env.scope(),
+ buffer,
+ v8::NewStringType::Internalized,
+ ) else {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+ };
+
+ unsafe {
+ *result = string.into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_create_double(
+ env_ptr: *mut Env,
+ value: f64,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Number::new(&mut env.scope(), value).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_int32(
+ env_ptr: *mut Env,
+ value: i32,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Integer::new(&mut env.scope(), value).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_uint32(
+ env_ptr: *mut Env,
+ value: u32,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_int64(
+ env_ptr: *mut Env,
+ value: i64,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Number::new(&mut env.scope(), value as _).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_bigint_int64(
+ env_ptr: *mut Env,
+ value: i64,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_bigint_uint64(
+ env_ptr: *mut Env,
+ value: u64,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into();
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_bigint_words<'s>(
+ env: &'s mut Env,
+ sign_bit: bool,
+ word_count: usize,
+ words: *const u64,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, words);
+ check_arg!(env, result);
+
+ if word_count > INT_MAX as _ {
+ return napi_invalid_arg;
+ }
+
+ match v8::BigInt::new_from_words(&mut env.scope(), sign_bit, unsafe {
+ std::slice::from_raw_parts(words, word_count)
+ }) {
+ Some(value) => unsafe {
+ *result = value.into();
+ },
+ None => {
+ return napi_generic_failure;
+ }
+ }
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_create_symbol(
+#[napi_sym]
+fn napi_get_boolean(
env: *mut Env,
+ value: bool,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Boolean::new(env.isolate(), value).into();
+ }
+
+ return napi_clear_last_error(env);
+}
+
+#[napi_sym]
+fn napi_create_symbol(
+ env_ptr: *mut Env,
description: napi_value,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
+ let env = check_env!(env_ptr);
check_arg!(env, result);
- let env = unsafe { &mut *env };
- let scope = &mut env.scope();
let description = if let Some(d) = *description {
- let Some(d) = d.to_string(scope) else {
- return napi_string_expected;
+ let Some(d) = d.to_string(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_string_expected);
};
Some(d)
} else {
None
};
- *result = v8::Symbol::new(scope, description).into();
- napi_ok
+
+ unsafe {
+ *result = v8::Symbol::new(&mut env.scope(), description).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_create_typedarray(
+#[napi_sym]
+fn node_api_symbol_for(
env: *mut Env,
- ty: napi_typedarray_type,
+ utf8description: *const c_char,
length: usize,
- arraybuffer: napi_value,
- byte_offset: usize,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let ab = napi_value_unchecked(arraybuffer);
- let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(ab) else {
- return napi_arraybuffer_expected;
- };
- let typedarray: v8::Local<v8::Value> = match ty {
- napi_uint8_array => {
- v8::Uint8Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_uint8_clamped_array => {
- v8::Uint8ClampedArray::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_int8_array => {
- v8::Int8Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_uint16_array => {
- v8::Uint16Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_int16_array => {
- v8::Int16Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_uint32_array => {
- v8::Uint32Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_int32_array => {
- v8::Int32Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_float32_array => {
- v8::Float32Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_float64_array => {
- v8::Float64Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_bigint64_array => {
- v8::BigInt64Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
- }
- napi_biguint64_array => {
- v8::BigUint64Array::new(&mut env.scope(), ab, byte_offset, length)
- .unwrap()
- .into()
+ {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ let description_string =
+ match unsafe { check_new_from_utf8_len(env, utf8description, length) } {
+ Ok(s) => s,
+ Err(status) => return napi_set_last_error(env, status),
+ };
+
+ unsafe {
+ *result =
+ v8::Symbol::for_key(&mut env.scope(), description_string).into();
}
- _ => {
- return napi_invalid_arg;
+ }
+
+ napi_clear_last_error(env)
+}
+
+macro_rules! napi_create_error_impl {
+ ($env_ptr:ident, $code:ident, $msg:ident, $result:ident, $error:ident) => {{
+ let env_ptr = $env_ptr;
+ let code = $code;
+ let msg = $msg;
+ let result = $result;
+
+ let env = check_env!(env_ptr);
+ check_arg!(env, msg);
+ check_arg!(env, result);
+
+ let Some(message) =
+ msg.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_string_expected);
+ };
+
+ let error = v8::Exception::$error(&mut env.scope(), message);
+
+ if let Some(code) = *code {
+ let error_obj: v8::Local<v8::Object> = error.try_into().unwrap();
+ let code_key = v8::String::new(&mut env.scope(), "code").unwrap();
+ if !error_obj
+ .set(&mut env.scope(), code_key.into(), code)
+ .unwrap_or(false)
+ {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+ }
+ }
+
+ unsafe {
+ *result = error.into();
}
+
+ return napi_clear_last_error(env_ptr);
+ }};
+}
+
+#[napi_sym]
+fn napi_create_error(
+ env_ptr: *mut Env,
+ code: napi_value,
+ msg: napi_value,
+ result: *mut napi_value,
+) -> napi_status {
+ napi_create_error_impl!(env_ptr, code, msg, result, error)
+}
+
+#[napi_sym]
+fn napi_create_type_error(
+ env_ptr: *mut Env,
+ code: napi_value,
+ msg: napi_value,
+ result: *mut napi_value,
+) -> napi_status {
+ napi_create_error_impl!(env_ptr, code, msg, result, type_error)
+}
+
+#[napi_sym]
+fn napi_create_range_error(
+ env_ptr: *mut Env,
+ code: napi_value,
+ msg: napi_value,
+ result: *mut napi_value,
+) -> napi_status {
+ napi_create_error_impl!(env_ptr, code, msg, result, range_error)
+}
+
+#[napi_sym]
+fn node_api_create_syntax_error(
+ env_ptr: *mut Env,
+ code: napi_value,
+ msg: napi_value,
+ result: *mut napi_value,
+) -> napi_status {
+ napi_create_error_impl!(env_ptr, code, msg, result, syntax_error)
+}
+
+pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> {
+ if value.is_undefined() {
+ Some(napi_undefined)
+ } else if value.is_null() {
+ Some(napi_null)
+ } else if value.is_external() {
+ Some(napi_external)
+ } else if value.is_boolean() {
+ Some(napi_boolean)
+ } else if value.is_number() {
+ Some(napi_number)
+ } else if value.is_big_int() {
+ Some(napi_bigint)
+ } else if value.is_string() {
+ Some(napi_string)
+ } else if value.is_symbol() {
+ Some(napi_symbol)
+ } else if value.is_function() {
+ Some(napi_function)
+ } else if value.is_object() {
+ Some(napi_object)
+ } else {
+ None
+ }
+}
+
+#[napi_sym]
+fn napi_typeof(
+ env: *mut Env,
+ value: napi_value,
+ result: *mut napi_valuetype,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(ty) = get_value_type(value.unwrap()) else {
+ return napi_set_last_error(env, napi_invalid_arg);
};
- *result = typedarray.into();
+
+ unsafe {
+ *result = ty;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::undefined(&mut env.scope()).into();
+ }
+
+ return napi_clear_last_error(env);
+}
+
+#[napi_sym]
+fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::null(&mut env.scope()).into();
+ }
+
+ return napi_clear_last_error(env);
+}
+
+#[napi_sym]
+fn napi_get_cb_info(
+ env: *mut Env,
+ cbinfo: napi_callback_info,
+ argc: *mut i32,
+ argv: *mut napi_value,
+ this_arg: *mut napi_value,
+ data: *mut *mut c_void,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, cbinfo);
+
+ let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) };
+ let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) };
+
+ if !argv.is_null() {
+ check_arg!(env, argc);
+ let argc = unsafe { *argc as usize };
+ for i in 0..argc {
+ let mut arg = args.get(i as _);
+ unsafe {
+ *argv.add(i) = arg.into();
+ }
+ }
+ }
+
+ if !argc.is_null() {
+ unsafe {
+ *argc = args.length();
+ }
+ }
+
+ if !this_arg.is_null() {
+ unsafe {
+ *this_arg = args.this().into();
+ }
+ }
+
+ if !data.is_null() {
+ unsafe {
+ *data = cbinfo.cb_info;
+ }
+ }
+
+ napi_clear_last_error(env);
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_make_callback(
+#[napi_sym]
+fn napi_get_new_target(
env: *mut Env,
- async_context: *mut c_void,
+ cbinfo: napi_callback_info,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, cbinfo);
+ check_arg!(env, result);
+
+ let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) };
+ let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) };
+
+ unsafe {
+ *result = args.new_target().into();
+ }
+
+ return napi_clear_last_error(env);
+}
+
+#[napi_sym]
+fn napi_call_function(
+ env_ptr: *mut Env,
recv: napi_value,
func: napi_value,
- argc: isize,
+ argc: usize,
argv: *const napi_value,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, recv);
- if argc > 0 {
+ let env = check_env!(env_ptr);
+ check_arg!(env, recv);
+ let args = if argc > 0 {
check_arg!(env, argv);
+ unsafe {
+ std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc)
+ }
+ } else {
+ &[]
+ };
+
+ let Some(func) =
+ func.and_then(|f| v8::Local::<v8::Function>::try_from(f).ok())
+ else {
+ return napi_set_last_error(env, napi_function_expected);
+ };
+
+ let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else {
+ return napi_set_last_error(env_ptr, napi_generic_failure);
+ };
+
+ if !result.is_null() {
+ unsafe {
+ *result = v.into();
+ }
}
- if !async_context.is_null() {
- log::info!("napi_make_callback: async_context is not supported");
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = std::mem::transmute::<NonNull<v8::Value>, v8::Local<v8::Value>>(
+ env.global,
+ )
+ .into();
}
- let recv = napi_value_unchecked(recv);
- let func = napi_value_unchecked(func);
+ return napi_clear_last_error(env);
+}
- let Ok(func) = v8::Local::<v8::Function>::try_from(func) else {
- return napi_function_expected;
+#[napi_sym]
+fn napi_throw(env: *mut Env, error: napi_value) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, error);
+
+ if env.last_exception.is_some() {
+ return napi_pending_exception;
+ }
+
+ let error = error.unwrap();
+ env.scope().throw_exception(error);
+ let error = v8::Global::new(&mut env.scope(), error);
+ env.last_exception = Some(error);
+
+ napi_clear_last_error(env)
+}
+
+macro_rules! napi_throw_error_impl {
+ ($env:ident, $code:ident, $msg:ident, $error:ident) => {{
+ let env = check_env!($env);
+ let env_ptr = env as *mut Env;
+ let code = $code;
+ let msg = $msg;
+
+ if env.last_exception.is_some() {
+ return napi_pending_exception;
+ }
+
+ let str_ = match unsafe { check_new_from_utf8(env, msg) } {
+ Ok(s) => s,
+ Err(status) => return status,
+ };
+
+ let error = v8::Exception::$error(&mut env.scope(), str_);
+
+ if !code.is_null() {
+ let error_obj: v8::Local<v8::Object> = error.try_into().unwrap();
+ let code = match unsafe { check_new_from_utf8(env_ptr, code) } {
+ Ok(s) => s,
+ Err(status) => return napi_set_last_error(env, status),
+ };
+ let code_key = v8::String::new(&mut env.scope(), "code").unwrap();
+ if !error_obj
+ .set(&mut env.scope(), code_key.into(), code.into())
+ .unwrap_or(false)
+ {
+ return napi_set_last_error(env, napi_generic_failure);
+ }
+ }
+
+ env.scope().throw_exception(error);
+ let error = v8::Global::new(&mut env.scope(), error);
+ env.last_exception = Some(error);
+
+ napi_clear_last_error(env)
+ }};
+}
+
+#[napi_sym]
+fn napi_throw_error(
+ env: *mut Env,
+ code: *const c_char,
+ msg: *const c_char,
+) -> napi_status {
+ napi_throw_error_impl!(env, code, msg, error)
+}
+
+#[napi_sym]
+fn napi_throw_type_error(
+ env: *mut Env,
+ code: *const c_char,
+ msg: *const c_char,
+) -> napi_status {
+ napi_throw_error_impl!(env, code, msg, type_error)
+}
+
+#[napi_sym]
+fn napi_throw_range_error(
+ env: *mut Env,
+ code: *const c_char,
+ msg: *const c_char,
+) -> napi_status {
+ napi_throw_error_impl!(env, code, msg, range_error)
+}
+
+#[napi_sym]
+fn node_api_throw_syntax_error(
+ env: *mut Env,
+ code: *const c_char,
+ msg: *const c_char,
+) -> napi_status {
+ napi_throw_error_impl!(env, code, msg, syntax_error)
+}
+
+#[napi_sym]
+fn napi_is_error(
+ env: *mut Env,
+ value: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = value.unwrap().is_native_error();
+ }
+
+ return napi_clear_last_error(env);
+}
+
+#[napi_sym]
+fn napi_get_value_double(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut f64,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(number) =
+ value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_number_expected);
};
- let argv: &[v8::Local<v8::Value>] =
- transmute(std::slice::from_raw_parts(argv, argc as usize));
- let ret = func.call(&mut env.scope(), recv, argv);
- *result = transmute::<Option<v8::Local<v8::Value>>, napi_value>(ret);
- napi_ok
+
+ unsafe {
+ *result = number.value();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_int32(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut i32,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(value) = value.unwrap().int32_value(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_number_expected);
+ };
+
+ unsafe {
+ *result = value;
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_uint32(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut u32,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(value) = value.unwrap().uint32_value(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_number_expected);
+ };
+
+ unsafe {
+ *result = value;
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_int64(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut i64,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(number) =
+ value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_number_expected);
+ };
+
+ let value = number.value();
+
+ unsafe {
+ if value.is_finite() {
+ *result = value as _;
+ } else {
+ *result = 0;
+ }
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_bigint_int64(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
result: *mut i64,
lossless: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let bigint = value.to_big_int(&mut env.scope()).unwrap();
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+ check_arg!(env, lossless);
+
+ let Some(bigint) =
+ value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_bigint_expected);
+ };
+
let (result_, lossless_) = bigint.i64_value();
- *result = result_;
- *lossless = lossless_;
- // TODO(bartlomieju):
- // napi_clear_last_error()
- napi_ok
+
+ unsafe {
+ *result = result_;
+ *lossless = lossless_;
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_bigint_uint64(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
result: *mut u64,
lossless: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let bigint = value.to_big_int(&mut env.scope()).unwrap();
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+ check_arg!(env, lossless);
+
+ let Some(bigint) =
+ value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_bigint_expected);
+ };
+
let (result_, lossless_) = bigint.u64_value();
- *result = result_;
- *lossless = lossless_;
- // TODO(bartlomieju):
- // napi_clear_last_error()
- napi_ok
+
+ unsafe {
+ *result = result_;
+ *lossless = lossless_;
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_bigint_words(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
sign_bit: *mut i32,
word_count: *mut usize,
words: *mut u64,
) -> napi_status {
- check_env!(env);
- // TODO(bartlomieju):
- // check_arg!(env, value);
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
check_arg!(env, word_count);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let big = match value.to_big_int(&mut env.scope()) {
- Some(b) => b,
- None => return napi_bigint_expected,
+ let Some(bigint) =
+ value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_bigint_expected);
};
+
let word_count_int;
if sign_bit.is_null() && words.is_null() {
- word_count_int = big.word_count();
+ word_count_int = bigint.word_count();
} else {
check_arg!(env, sign_bit);
check_arg!(env, words);
- let out_words = std::slice::from_raw_parts_mut(words, *word_count);
- let (sign, slice_) = big.to_words_array(out_words);
+ let out_words =
+ unsafe { std::slice::from_raw_parts_mut(words, *word_count) };
+ let (sign, slice_) = bigint.to_words_array(out_words);
word_count_int = slice_.len();
- *sign_bit = sign as i32;
+ unsafe {
+ *sign_bit = sign as i32;
+ }
}
- *word_count = word_count_int;
- // TODO(bartlomieju):
- // napi_clear_last_error()
- napi_ok
+ unsafe {
+ *word_count = word_count_int;
+ }
+
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_bool(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- *result = value.boolean_value(&mut env.scope());
- napi_ok
-}
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
-#[napi_sym::napi_sym]
-fn napi_get_value_double(
- env: *mut Env,
- value: napi_value,
- result: *mut f64,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- return_status_if_false!(env, value.is_number(), napi_number_expected);
- *result = value.number_value(&mut env.scope()).unwrap();
- napi_ok
-}
+ let Some(boolean) =
+ value.and_then(|v| v8::Local::<v8::Boolean>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_boolean_expected);
+ };
-#[napi_sym::napi_sym]
-fn napi_get_value_external(
- _env: *mut Env,
- value: napi_value,
- result: *mut *mut c_void,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- let ext = v8::Local::<v8::External>::try_from(value).unwrap();
- *result = ext.value();
- napi_ok
-}
+ unsafe {
+ *result = boolean.is_true();
+ }
-#[napi_sym::napi_sym]
-fn napi_get_value_int32(
- env: *mut Env,
- value: napi_value,
- result: *mut i32,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- *result = value.int32_value(&mut env.scope()).unwrap();
- napi_ok
+ return napi_clear_last_error(env_ptr);
}
-#[napi_sym::napi_sym]
-fn napi_get_value_int64(
- env: *mut Env,
- value: napi_value,
- result: *mut i64,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- *result = value.integer_value(&mut env.scope()).unwrap();
- napi_ok
-}
-
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_string_latin1(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
- buf: *mut u8,
+ buf: *mut c_char,
bufsize: usize,
result: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
-
- let value = napi_value_unchecked(value);
-
- if !value.is_string() && !value.is_string_object() {
- return napi_string_expected;
- }
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
- let v8str = value.to_string(&mut env.scope()).unwrap();
- let string_len = v8str.utf8_length(&mut env.scope());
+ let Some(value) =
+ value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_string_expected);
+ };
if buf.is_null() {
- *result = string_len;
+ check_arg!(env, result);
+ unsafe {
+ *result = value.length();
+ }
} else if bufsize != 0 {
- let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1);
- let copied = v8str.write_one_byte(
+ let buffer =
+ unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) };
+ let copied = value.write_one_byte(
&mut env.scope(),
buffer,
0,
v8::WriteOptions::NO_NULL_TERMINATION,
);
- buf.add(copied).write(0);
+ unsafe {
+ buf.add(copied).write(0);
+ }
if !result.is_null() {
- *result = copied;
+ unsafe {
+ *result = copied;
+ }
}
} else if !result.is_null() {
- *result = string_len;
+ unsafe {
+ *result = 0;
+ }
}
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_string_utf8(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
buf: *mut u8,
bufsize: usize,
result: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
-
- let value = napi_value_unchecked(value);
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
- if !value.is_string() && !value.is_string_object() {
- return napi_string_expected;
- }
-
- let v8str = value.to_string(&mut env.scope()).unwrap();
- let string_len = v8str.utf8_length(&mut env.scope());
+ let Some(value) =
+ value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_string_expected);
+ };
if buf.is_null() {
- *result = string_len;
+ check_arg!(env, result);
+ unsafe {
+ *result = value.length();
+ }
} else if bufsize != 0 {
- let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1);
- let copied = v8str.write_utf8(
+ let buffer =
+ unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) };
+ let copied = value.write_utf8(
&mut env.scope(),
buffer,
None,
- v8::WriteOptions::NO_NULL_TERMINATION
- | v8::WriteOptions::REPLACE_INVALID_UTF8,
+ v8::WriteOptions::REPLACE_INVALID_UTF8
+ | v8::WriteOptions::NO_NULL_TERMINATION,
);
- buf.add(copied).write(0);
+ unsafe {
+ buf.add(copied).write(0);
+ }
if !result.is_null() {
- *result = copied;
+ unsafe {
+ *result = copied;
+ }
}
} else if !result.is_null() {
- *result = string_len;
+ unsafe {
+ *result = 0;
+ }
}
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_value_string_utf16(
- env: *mut Env,
+ env_ptr: *mut Env,
value: napi_value,
buf: *mut u16,
bufsize: usize,
result: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
-
- let value = napi_value_unchecked(value);
-
- if !value.is_string() && !value.is_string_object() {
- return napi_string_expected;
- }
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
- let v8str = value.to_string(&mut env.scope()).unwrap();
- let string_len = v8str.length();
+ let Some(value) =
+ value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_string_expected);
+ };
if buf.is_null() {
- *result = string_len;
+ check_arg!(env, result);
+ unsafe {
+ *result = value.length();
+ }
} else if bufsize != 0 {
- let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1);
- let copied = v8str.write(
+ let buffer =
+ unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) };
+ let copied = value.write(
&mut env.scope(),
buffer,
0,
v8::WriteOptions::NO_NULL_TERMINATION,
);
- buf.add(copied).write(0);
+ unsafe {
+ buf.add(copied).write(0);
+ }
if !result.is_null() {
- *result = copied;
+ unsafe {
+ *result = copied;
+ }
}
} else if !result.is_null() {
- *result = string_len;
+ unsafe {
+ *result = 0;
+ }
}
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_get_value_uint32(
- env: *mut Env,
+#[napi_sym]
+fn napi_coerce_to_bool<'s>(
+ env: &'s mut Env,
value: napi_value,
- result: *mut u32,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- *result = value.uint32_value(&mut env.scope()).unwrap();
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let coerced = value.unwrap().to_boolean(&mut env.scope());
+
+ unsafe {
+ *result = coerced.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_add_finalizer(
- env_ptr: *mut Env,
- js_object: napi_value,
- native_object: *mut c_void,
- finalize_cb: napi_finalize,
- finalize_hint: *mut c_void,
- result: *mut napi_ref,
+#[napi_sym]
+fn napi_coerce_to_number<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
- let value = napi_value_unchecked(js_object);
- let value =
- weak_local(env_ptr, value, native_object, finalize_cb, finalize_hint);
+ let Some(coerced) = value.unwrap().to_number(&mut env.scope()) else {
+ return napi_number_expected;
+ };
- if !result.is_null() {
- *result = transmute(value);
+ unsafe {
+ *result = coerced.into();
}
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_adjust_external_memory(
- env: *mut Env,
- change_in_bytes: i64,
- adjusted_value: *mut i64,
+#[napi_sym]
+fn napi_coerce_to_object<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- check_arg!(env, adjusted_value);
+ check_arg!(env, value);
+ check_arg!(env, result);
- let env = unsafe { &mut *env };
- let isolate = &mut *env.isolate_ptr;
- *adjusted_value =
- isolate.adjust_amount_of_external_allocated_memory(change_in_bytes);
+ let Some(coerced) = value.unwrap().to_object(&mut env.scope()) else {
+ return napi_object_expected;
+ };
+
+ unsafe {
+ *result = coerced.into();
+ }
- napi_clear_last_error(env);
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_call_function(
- env: *mut Env,
- recv: napi_value,
- func: napi_value,
- argc: usize,
- argv: *const napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_coerce_to_string<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let recv = napi_value_unchecked(recv);
- let func = napi_value_unchecked(func);
- let Ok(func) = v8::Local::<v8::Function>::try_from(func) else {
- return napi_function_expected;
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(coerced) = value.unwrap().to_string(&mut env.scope()) else {
+ return napi_string_expected;
};
- let argv: &[v8::Local<v8::Value>] =
- transmute(std::slice::from_raw_parts(argv, argc));
- let ret = func.call(&mut env.scope(), recv, argv);
- if !result.is_null() {
- *result = transmute::<Option<v8::Local<v8::Value>>, napi_value>(ret);
+ unsafe {
+ *result = coerced.into();
}
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_close_escapable_handle_scope(
- _env: *mut Env,
- _scope: napi_escapable_handle_scope,
+#[napi_sym]
+fn napi_wrap(
+ env: &mut Env,
+ js_object: napi_value,
+ native_object: *mut c_void,
+ finalize_cb: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+ result: *mut napi_ref,
) -> napi_status {
- // TODO: do this properly
- napi_ok
-}
+ check_arg!(env, js_object);
+ let env_ptr = env as *mut Env;
-#[napi_sym::napi_sym]
-fn napi_close_handle_scope(
- env: *mut Env,
- _scope: napi_handle_scope,
-) -> napi_status {
- let env = &mut *env;
- if env.open_handle_scopes == 0 {
- return napi_handle_scope_mismatch;
+ let Some(obj) =
+ js_object.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
+ };
+
+ let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap);
+
+ if obj
+ .has_private(&mut env.scope(), napi_wrap)
+ .unwrap_or(false)
+ {
+ return napi_invalid_arg;
+ }
+
+ if !result.is_null() {
+ check_arg!(env, finalize_cb);
}
- // TODO: We are not opening a handle scope, therefore we cannot close it
- // TODO: this is also not implemented in napi_open_handle_scope
- // let _scope = &mut *(scope as *mut v8::HandleScope);
- env.open_handle_scopes -= 1;
+
+ let ownership = if result.is_null() {
+ ReferenceOwnership::Runtime
+ } else {
+ ReferenceOwnership::Userland
+ };
+ let reference = Reference::new(
+ env_ptr,
+ obj.into(),
+ 0,
+ ownership,
+ finalize_cb,
+ native_object,
+ finalize_hint,
+ );
+
+ let reference = Reference::into_raw(reference) as *mut c_void;
+
+ if !result.is_null() {
+ check_arg!(env, finalize_cb);
+ unsafe {
+ *result = reference;
+ }
+ }
+
+ let external = v8::External::new(&mut env.scope(), reference);
+ assert!(obj
+ .set_private(&mut env.scope(), napi_wrap, external.into())
+ .unwrap());
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_define_class(
- env_ptr: *mut Env,
- name: *const c_char,
- length: isize,
- constructor: napi_callback,
- callback_data: *mut c_void,
- property_count: usize,
- properties: *const napi_property_descriptor,
- result: *mut napi_value,
+fn unwrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+ keep: bool,
) -> napi_status {
- check_env!(env_ptr);
- let env = unsafe { &mut *env_ptr };
- check_arg!(env, result);
- check_arg_option!(env, constructor);
-
- if property_count > 0 {
- check_arg!(env, properties);
+ check_arg!(env, obj);
+ if keep {
+ check_arg!(env, result);
}
- let name = if length == -1 {
- let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() else {
- return napi_invalid_arg;
- };
- s
- } else {
- let slice = std::slice::from_raw_parts(name as *const u8, length as usize);
- std::str::from_utf8(slice).unwrap()
+ let Some(obj) = obj.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
+ };
+
+ let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap);
+ let Some(val) = obj.get_private(&mut env.scope(), napi_wrap) else {
+ return napi_invalid_arg;
};
- let tpl =
- create_function_template(env_ptr, Some(name), constructor, callback_data);
+ let Ok(external) = v8::Local::<v8::External>::try_from(val) else {
+ return napi_invalid_arg;
+ };
- let scope = &mut env.scope();
- let napi_properties: &[napi_property_descriptor] =
- std::slice::from_raw_parts(properties, property_count);
- let mut static_property_count = 0;
+ let reference = external.value() as *mut Reference;
+ let reference = unsafe { &mut *reference };
- for p in napi_properties {
- if p.attributes & napi_static != 0 {
- // Will be handled below
- static_property_count += 1;
- continue;
+ if !result.is_null() {
+ unsafe {
+ *result = reference.finalize_data;
}
+ }
- let name = if !p.utf8name.is_null() {
- let name_str = CStr::from_ptr(p.utf8name).to_str().unwrap();
- v8::String::new(scope, name_str).unwrap()
- } else {
- transmute::<napi_value, v8::Local<v8::String>>(p.name)
- };
+ if !keep {
+ assert!(obj
+ .delete_private(&mut env.scope(), napi_wrap)
+ .unwrap_or(false));
+ unsafe { Reference::remove(reference) };
+ }
- let method = p.method;
- let getter = p.getter;
- let setter = p.setter;
+ napi_ok
+}
- if getter.is_some() || setter.is_some() {
- let getter: Option<v8::Local<v8::FunctionTemplate>> = if getter.is_some()
- {
- Some(create_function_template(env_ptr, None, p.getter, p.data))
- } else {
- None
- };
- let setter: Option<v8::Local<v8::FunctionTemplate>> = if setter.is_some()
- {
- Some(create_function_template(env_ptr, None, p.setter, p.data))
- } else {
- None
- };
+#[napi_sym]
+fn napi_unwrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+) -> napi_status {
+ unwrap(env, obj, result, true)
+}
- let mut accessor_property = v8::PropertyAttribute::NONE;
- if getter.is_some()
- && setter.is_some()
- && (p.attributes & napi_writable) == 0
- {
- accessor_property =
- accessor_property | v8::PropertyAttribute::READ_ONLY;
- }
- if p.attributes & napi_enumerable == 0 {
- accessor_property =
- accessor_property | v8::PropertyAttribute::DONT_ENUM;
- }
- if p.attributes & napi_configurable == 0 {
- accessor_property =
- accessor_property | v8::PropertyAttribute::DONT_DELETE;
- }
+#[napi_sym]
+fn napi_remove_wrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+) -> napi_status {
+ unwrap(env, obj, result, false)
+}
- let proto = tpl.prototype_template(scope);
- proto.set_accessor_property(
- name.into(),
- getter,
- setter,
- accessor_property,
- );
- } else if method.is_some() {
- let function = create_function_template(env_ptr, None, p.method, p.data);
- let proto = tpl.prototype_template(scope);
- proto.set(name.into(), function.into());
- } else {
- let proto = tpl.prototype_template(scope);
- proto.set(
- name.into(),
- transmute::<napi_value, v8::Local<v8::Data>>(p.value),
- );
- }
- }
+struct ExternalWrapper {
+ data: *mut c_void,
+ type_tag: Option<napi_type_tag>,
+}
- let value: v8::Local<v8::Value> = tpl.get_function(scope).unwrap().into();
- *result = value.into();
+#[napi_sym]
+fn napi_create_external<'s>(
+ env: &'s mut Env,
+ data: *mut c_void,
+ finalize_cb: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ let env_ptr = env as *mut Env;
+ check_arg!(env, result);
- if static_property_count > 0 {
- let mut static_descriptors = Vec::with_capacity(static_property_count);
+ let wrapper = Box::new(ExternalWrapper {
+ data,
+ type_tag: None,
+ });
- for p in napi_properties {
- if p.attributes & napi_static != 0 {
- static_descriptors.push(*p);
- }
- }
+ let wrapper = Box::into_raw(wrapper);
+ let external = v8::External::new(&mut env.scope(), wrapper as _);
- status_call!(napi_define_properties(
+ if let Some(finalize_cb) = finalize_cb {
+ Reference::into_raw(Reference::new(
env_ptr,
- *result,
- static_descriptors.len(),
- static_descriptors.as_ptr(),
+ external.into(),
+ 0,
+ ReferenceOwnership::Runtime,
+ Some(finalize_cb),
+ data,
+ finalize_hint,
));
}
+ unsafe {
+ *result = external.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_define_properties(
- env_ptr: *mut Env,
- obj: napi_value,
- property_count: usize,
- properties: *const napi_property_descriptor,
+#[napi_sym]
+fn napi_type_tag_object(
+ env: &mut Env,
+ object_or_external: napi_value,
+ type_tag: *const napi_type_tag,
) -> napi_status {
- check_env!(env_ptr);
- let env = unsafe { &mut *env_ptr };
- if property_count > 0 {
- check_arg!(env, properties);
+ check_arg!(env, object_or_external);
+ check_arg!(env, type_tag);
+
+ let val = object_or_external.unwrap();
+
+ if let Ok(external) = v8::Local::<v8::External>::try_from(val) {
+ let wrapper_ptr = external.value() as *mut ExternalWrapper;
+ let wrapper = unsafe { &mut *wrapper_ptr };
+ if wrapper.type_tag.is_some() {
+ return napi_invalid_arg;
+ }
+ wrapper.type_tag = Some(unsafe { *type_tag });
+ return napi_ok;
}
- let scope = &mut env.scope();
+ let Some(object) = val.to_object(&mut env.scope()) else {
+ return napi_object_expected;
+ };
+
+ let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag);
- let Ok(object) = v8::Local::<v8::Object>::try_from(napi_value_unchecked(obj))
+ if object.has_private(&mut env.scope(), key).unwrap_or(false) {
+ return napi_invalid_arg;
+ }
+
+ let slice = unsafe { std::slice::from_raw_parts(type_tag as *const u64, 2) };
+ let Some(tag) = v8::BigInt::new_from_words(&mut env.scope(), false, slice)
else {
- return napi_object_expected;
+ return napi_generic_failure;
};
- let properties = std::slice::from_raw_parts(properties, property_count);
- for property in properties {
- let name = if !property.utf8name.is_null() {
- let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap();
- let Some(name_v8_str) = v8::String::new(scope, name_str) else {
- return napi_generic_failure;
- };
- name_v8_str.into()
- } else {
- let property_value = napi_value_unchecked(property.name);
- let Ok(prop) = v8::Local::<v8::Name>::try_from(property_value) else {
- return napi_name_expected;
+ if !object
+ .set_private(&mut env.scope(), key, tag.into())
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_check_object_type_tag(
+ env: &mut Env,
+ object_or_external: napi_value,
+ type_tag: *const napi_type_tag,
+ result: *mut bool,
+) -> napi_status {
+ check_arg!(env, object_or_external);
+ check_arg!(env, type_tag);
+ check_arg!(env, result);
+
+ let type_tag = unsafe { *type_tag };
+
+ let val = object_or_external.unwrap();
+
+ if let Ok(external) = v8::Local::<v8::External>::try_from(val) {
+ let wrapper_ptr = external.value() as *mut ExternalWrapper;
+ let wrapper = unsafe { &mut *wrapper_ptr };
+ unsafe {
+ *result = match wrapper.type_tag {
+ Some(t) => t == type_tag,
+ None => false,
};
- prop
};
+ return napi_ok;
+ }
- if property.getter.is_some() || property.setter.is_some() {
- let local_getter: v8::Local<v8::Value> = if property.getter.is_some() {
- create_function(env_ptr, None, property.getter, property.data).into()
- } else {
- v8::undefined(scope).into()
- };
- let local_setter: v8::Local<v8::Value> = if property.setter.is_some() {
- create_function(env_ptr, None, property.setter, property.data).into()
- } else {
- v8::undefined(scope).into()
- };
+ let Some(object) = val.to_object(&mut env.scope()) else {
+ return napi_object_expected;
+ };
- let mut desc =
- v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter);
- desc.set_enumerable(property.attributes & napi_enumerable != 0);
- desc.set_configurable(property.attributes & napi_configurable != 0);
+ let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag);
- let define_maybe = object.define_property(scope, name, &desc);
- return_status_if_false!(
- env_ptr,
- define_maybe.is_some(),
- napi_invalid_arg
- );
- } else if property.method.is_some() {
- let value: v8::Local<v8::Value> = {
- let function =
- create_function(env_ptr, None, property.method, property.data);
- function.into()
+ let Some(val) = object.get_private(&mut env.scope(), key) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = false;
+ }
+
+ if let Ok(bigint) = v8::Local::<v8::BigInt>::try_from(val) {
+ let mut words = [0u64; 2];
+ let (sign, words) = bigint.to_words_array(&mut words);
+ if !sign {
+ let pass = if words.len() == 2 {
+ type_tag.lower == words[0] && type_tag.upper == words[1]
+ } else if words.len() == 1 {
+ type_tag.lower == words[0] && type_tag.upper == 0
+ } else if words.is_empty() {
+ type_tag.lower == 0 && type_tag.upper == 0
+ } else {
+ false
};
- return_status_if_false!(
- env_ptr,
- object.set(scope, name.into(), value).is_some(),
- napi_invalid_arg
- );
- } else {
- let value = napi_value_unchecked(property.value);
- return_status_if_false!(
- env_ptr,
- object.set(scope, name.into(), value).is_some(),
- napi_invalid_arg
- );
+ unsafe {
+ *result = pass;
+ }
}
}
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_delete_element(
+#[napi_sym]
+fn napi_get_value_external(
env: *mut Env,
value: napi_value,
- index: u32,
- result: *mut bool,
+ result: *mut *mut c_void,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- *result = obj.delete_index(&mut env.scope(), index).unwrap_or(false);
- napi_ok
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(external) =
+ value.and_then(|v| v8::Local::<v8::External>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_invalid_arg);
+ };
+
+ let wrapper_ptr = external.value() as *const ExternalWrapper;
+ let wrapper = unsafe { &*wrapper_ptr };
+
+ unsafe {
+ *result = wrapper.data;
+ }
+
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_delete_property(
+#[napi_sym]
+fn napi_create_reference(
env: *mut Env,
- object: napi_value,
- key: napi_value,
- result: *mut bool,
+ value: napi_value,
+ initial_refcount: u32,
+ result: *mut napi_ref,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, key);
+ let env = check_env!(env);
+ check_arg!(env, value);
check_arg!(env, result);
- let scope = &mut env.scope();
- let Some(object) = object.map(|o| o.to_object(scope)).flatten() else {
- return napi_invalid_arg;
- };
+ let value = value.unwrap();
- let Some(deleted) = object.delete(scope, key.unwrap_unchecked()) else {
- return napi_generic_failure;
- };
+ let reference = Reference::new(
+ env,
+ value,
+ initial_refcount,
+ ReferenceOwnership::Userland,
+ None,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ );
- *result = deleted;
- napi_ok
+ let ptr = Reference::into_raw(reference);
+
+ unsafe {
+ *result = ptr as _;
+ }
+
+ napi_clear_last_error(env)
}
-// TODO: properly implement ref counting stuff
-#[napi_sym::napi_sym]
-fn napi_delete_reference(_env: *mut Env, _nref: napi_ref) -> napi_status {
- napi_ok
+#[napi_sym]
+fn napi_delete_reference(env: *mut Env, ref_: napi_ref) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, ref_);
+
+ let reference = unsafe { Reference::from_raw(ref_ as _) };
+
+ drop(reference);
+
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status {
- check_env!(env);
+#[napi_sym]
+fn napi_reference_ref(
+ env: *mut Env,
+ ref_: napi_ref,
+ result: *mut u32,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, ref_);
- let value = napi_value_unchecked(value);
- let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(value) else {
- return napi_arraybuffer_expected;
- };
+ let reference = unsafe { &mut *(ref_ as *mut Reference) };
- if !ab.is_detachable() {
- return napi_detachable_arraybuffer_expected;
- }
+ let count = reference.ref_();
- // Expected to crash for None.
- ab.detach(None).unwrap();
+ if !result.is_null() {
+ unsafe {
+ *result = count;
+ }
+ }
- napi_clear_last_error(env);
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_escape_handle<'s>(
- _env: *mut Env,
- _handle_scope: napi_escapable_handle_scope,
- escapee: napi_value<'s>,
- result: *mut napi_value<'s>,
+#[napi_sym]
+fn napi_reference_unref(
+ env: *mut Env,
+ ref_: napi_ref,
+ result: *mut u32,
) -> napi_status {
- // TODO
- *result = escapee;
- napi_ok
-}
+ let env = check_env!(env);
+ check_arg!(env, ref_);
-#[napi_sym::napi_sym]
-fn napi_get_all_property_names(_env: *mut Env) -> napi_status {
- // TODO
- napi_ok
+ let reference = unsafe { &mut *(ref_ as *mut Reference) };
+
+ if reference.ref_count == 0 {
+ return napi_set_last_error(env, napi_generic_failure);
+ }
+
+ let count = reference.unref();
+
+ if !result.is_null() {
+ unsafe {
+ *result = count;
+ }
+ }
+
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_and_clear_last_exception(
- env: *mut Env,
+#[napi_sym]
+fn napi_get_reference_value(
+ env_ptr: *mut Env,
+ ref_: napi_ref,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- // TODO: just return undefined for now we don't cache
- // exceptions in env.
- let value: v8::Local<v8::Value> = v8::undefined(&mut env.scope()).into();
- *result = value.into();
- napi_ok
+ let env = check_env!(env_ptr);
+ check_arg!(env, ref_);
+ check_arg!(env, result);
+
+ let reference = unsafe { &mut *(ref_ as *mut Reference) };
+
+ let value = match &reference.state {
+ ReferenceState::Strong(g) => Some(v8::Local::new(&mut env.scope(), g)),
+ ReferenceState::Weak(w) => w.to_local(&mut env.scope()),
+ };
+
+ unsafe {
+ *result = value.into();
+ }
+
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_get_array_length(
- _env: *mut Env,
- value: napi_value,
- result: *mut u32,
+#[napi_sym]
+fn napi_open_handle_scope(
+ env: *mut Env,
+ _result: *mut napi_handle_scope,
) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = v8::Local::<v8::Array>::try_from(value).unwrap().length();
- napi_ok
+ let env = check_env!(env);
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_arraybuffer_info(
- _env: *mut Env,
- value: napi_value,
- data: *mut *mut u8,
- length: *mut usize,
+#[napi_sym]
+fn napi_close_handle_scope(
+ env: *mut Env,
+ _scope: napi_handle_scope,
) -> napi_status {
- let value = napi_value_unchecked(value);
- let buf = v8::Local::<v8::ArrayBuffer>::try_from(value).unwrap();
- if !data.is_null() {
- *data = get_array_buffer_ptr(buf);
- }
- if !length.is_null() {
- *length = buf.byte_length();
- }
- napi_ok
+ let env = check_env!(env);
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_boolean(
+#[napi_sym]
+fn napi_open_escapable_handle_scope(
env: *mut Env,
- value: bool,
- result: *mut napi_value,
+ _result: *mut napi_escapable_handle_scope,
) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::Boolean::new(env.isolate(), value).into();
- napi_ok
+ let env = check_env!(env);
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_buffer_info(
+#[napi_sym]
+fn napi_close_escapable_handle_scope(
env: *mut Env,
- value: napi_value,
- data: *mut *mut u8,
- length: *mut usize,
+ _scope: napi_escapable_handle_scope,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let Ok(buf) = v8::Local::<v8::ArrayBufferView>::try_from(value) else {
- return napi_arraybuffer_expected;
- };
- let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap();
- let Ok(abuf) = v8::Local::<v8::ArrayBuffer>::try_from(
- buf.get(&mut env.scope(), buffer_name.into()).unwrap(),
- ) else {
- return napi_arraybuffer_expected;
- };
- if !data.is_null() {
- *data = get_array_buffer_ptr(abuf);
- }
- if !length.is_null() {
- *length = abuf.byte_length();
- }
- napi_ok
+ let env = check_env!(env);
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_cb_info(
+#[napi_sym]
+fn napi_escape_handle<'s>(
env: *mut Env,
- cbinfo: napi_callback_info,
- argc: *mut i32,
- argv: *mut napi_value,
- this_arg: *mut napi_value,
- data: *mut *mut c_void,
+ _scope: napi_escapable_handle_scope,
+ escapee: napi_value<'s>,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg!(env, cbinfo);
+ let env = check_env!(env);
- let cbinfo: &CallbackInfo = &*(cbinfo as *const CallbackInfo);
- let args = &*(cbinfo.args as *const v8::FunctionCallbackArguments);
-
- if !argv.is_null() {
- check_arg!(env, argc);
- let mut v_argv = std::slice::from_raw_parts_mut(argv, argc as usize);
- for i in 0..*argc {
- let mut arg = args.get(i);
- v_argv[i as usize] = arg.into();
- }
+ unsafe {
+ *result = escapee;
}
- if !argc.is_null() {
- *argc = args.length();
- }
+ napi_clear_last_error(env)
+}
- if !this_arg.is_null() {
- let mut this = args.this();
- *this_arg = this.into();
+#[napi_sym]
+fn napi_new_instance<'s>(
+ env: &'s mut Env,
+ constructor: napi_value,
+ argc: usize,
+ argv: *const napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, constructor);
+ if argc > 0 {
+ check_arg!(env, argv);
}
+ check_arg!(env, result);
- if !data.is_null() {
- *data = cbinfo.cb_info;
+ let Some(func) =
+ constructor.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
+ };
+
+ let args = if argc > 0 {
+ unsafe {
+ std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc)
+ }
+ } else {
+ &[]
+ };
+
+ let Some(value) = func.new_instance(&mut env.scope(), args) else {
+ return napi_pending_exception;
+ };
+
+ unsafe {
+ *result = value.into();
}
- napi_clear_last_error(env);
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_dataview_info(
- env: *mut Env,
- value: napi_value,
- data: *mut *mut u8,
- length: *mut usize,
+#[napi_sym]
+fn napi_instanceof(
+ env: &mut Env,
+ object: napi_value,
+ constructor: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let Ok(buf) = v8::Local::<v8::DataView>::try_from(value) else {
- return napi_invalid_arg;
+ check_arg!(env, object);
+ check_arg!(env, result);
+
+ let Some(ctor) = constructor.and_then(|v| v.to_object(&mut env.scope()))
+ else {
+ return napi_object_expected;
};
- let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap();
- let Ok(abuf) = v8::Local::<v8::ArrayBuffer>::try_from(
- buf.get(&mut env.scope(), buffer_name.into()).unwrap(),
- ) else {
- return napi_invalid_arg;
+
+ if !ctor.is_function() {
+ unsafe {
+ napi_throw_type_error(
+ env,
+ "ERR_NAPI_CONS_FUNCTION\0".as_ptr() as _,
+ "Constructor must be a function\0".as_ptr() as _,
+ );
+ }
+ return napi_function_expected;
+ }
+
+ let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else {
+ return napi_generic_failure;
};
- if !data.is_null() {
- *data = get_array_buffer_ptr(abuf);
+
+ unsafe {
+ *result = res;
}
- *length = abuf.byte_length();
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_date_value(
- env: *mut Env,
- value: napi_value,
- result: *mut f64,
+#[napi_sym]
+fn napi_is_exception_pending(
+ env_ptr: *mut Env,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let value = napi_value_unchecked(value);
- return_status_if_false!(env, value.is_date(), napi_date_expected);
- let env = unsafe { &mut *env };
- let Ok(date) = v8::Local::<v8::Date>::try_from(value) else {
- return napi_date_expected;
- };
- // TODO: should be value of
- *result = date.number_value(&mut env.scope()).unwrap();
- napi_ok
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = env.last_exception.is_some();
+ }
+
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_get_element(
- env: *mut Env,
- object: napi_value,
- index: u32,
+#[napi_sym]
+fn napi_get_and_clear_last_exception(
+ env_ptr: *mut Env,
result: *mut napi_value,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = napi_value_unchecked(object);
- let Ok(object) = v8::Local::<v8::Object>::try_from(object) else {
- return napi_invalid_arg;
- };
- let value: v8::Local<v8::Value> =
- object.get_index(&mut env.scope(), index).unwrap();
- *result = value.into();
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status {
- check_env!(env);
+ let env = check_env!(env_ptr);
check_arg!(env, result);
- let value: v8::Local<v8::Value> =
- transmute::<NonNull<v8::Value>, v8::Local<v8::Value>>((*env).global);
- *result = value.into();
- napi_clear_last_error(env);
- napi_ok
+ let ex: v8::Local<v8::Value> =
+ if let Some(last_exception) = env.last_exception.take() {
+ v8::Local::new(&mut env.scope(), last_exception)
+ } else {
+ v8::undefined(&mut env.scope()).into()
+ };
+
+ unsafe {
+ *result = ex.into();
+ }
+
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_get_instance_data(
+#[napi_sym]
+fn napi_is_arraybuffer(
env: *mut Env,
- result: *mut *mut c_void,
+ value: napi_value,
+ result: *mut bool,
) -> napi_status {
- let env = &mut *env;
- let shared = env.shared();
- *result = shared.instance_data;
- napi_ok
-}
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
-// TODO(bartlomieju): this function is broken
-#[napi_sym::napi_sym]
-fn napi_get_last_error_info(
- _env: *mut Env,
- error_code: *mut *const napi_extended_error_info,
-) -> napi_status {
- let err_info = Box::new(napi_extended_error_info {
- error_message: std::ptr::null(),
- engine_reserved: std::ptr::null_mut(),
- engine_error_code: 0,
- error_code: napi_ok,
- });
+ unsafe {
+ *result = value.unwrap().is_array_buffer();
+ }
- *error_code = Box::into_raw(err_info);
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_get_named_property(
- env: *mut Env,
- object: napi_value,
- utf8_name: *const c_char,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_create_arraybuffer<'s>(
+ env: &'s mut Env,
+ len: usize,
+ data: *mut *mut c_void,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = napi_value_unchecked(object);
- let utf8_name = std::ffi::CStr::from_ptr(utf8_name);
- let name =
- v8::String::new(&mut env.scope(), &utf8_name.to_string_lossy()).unwrap();
- let value: v8::Local<v8::Value> = object
- .to_object(&mut env.scope())
- .unwrap()
- .get(&mut env.scope(), name.into())
- .unwrap();
- *result = value.into();
- napi_ok
-}
+ check_arg!(env, result);
-#[napi_sym::napi_sym]
-fn napi_get_new_target(
- _env: &mut Env,
- cbinfo: &CallbackInfo,
- result: &mut v8::Local<v8::Value>,
-) -> napi_status {
- let info = &*(cbinfo.args as *const v8::FunctionCallbackArguments);
- *result = info.new_target();
- napi_ok
-}
+ let buffer = v8::ArrayBuffer::new(&mut env.scope(), len);
+
+ if !data.is_null() {
+ unsafe {
+ *data = get_array_buffer_ptr(buffer) as _;
+ }
+ }
+
+ unsafe {
+ *result = buffer.into();
+ }
-#[napi_sym::napi_sym]
-fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::null(env.isolate()).into();
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_property(
- env: *mut Env,
- object: napi_value,
- key: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_create_external_arraybuffer<'s>(
+ env: &'s mut Env,
+ data: *mut c_void,
+ byte_length: usize,
+ finalize_cb: napi_finalize,
+ finalize_hint: *mut c_void,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = transmute::<napi_value, v8::Local<v8::Object>>(object);
- let key = napi_value_unchecked(key);
- let value: v8::Local<v8::Value> = object.get(&mut env.scope(), key).unwrap();
- *result = value.into();
+ check_arg!(env, result);
+
+ let store = make_external_backing_store(
+ env,
+ data,
+ byte_length,
+ std::ptr::null_mut(),
+ finalize_cb,
+ finalize_hint,
+ );
+
+ let ab =
+ v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
+ let value: v8::Local<v8::Value> = ab.into();
+
+ unsafe {
+ *result = value.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_property_names(
+#[napi_sym]
+fn napi_get_arraybuffer_info(
env: *mut Env,
- object: napi_value,
- result: *mut napi_value,
+ value: napi_value,
+ data: *mut *mut u8,
+ length: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = napi_value_unchecked(object);
- let array: v8::Local<v8::Array> = object
- .to_object(&mut env.scope())
- .unwrap()
- .get_property_names(&mut env.scope(), Default::default())
- .unwrap();
- let value: v8::Local<v8::Value> = array.into();
- *result = value.into();
+ let env = check_env!(env);
+ check_arg!(env, value);
+
+ let Some(buf) =
+ value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_invalid_arg);
+ };
+
+ if !data.is_null() {
+ unsafe {
+ *data = get_array_buffer_ptr(buf);
+ }
+ }
+
+ if !length.is_null() {
+ unsafe {
+ *length = buf.byte_length();
+ }
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_prototype(
+#[napi_sym]
+fn napi_is_typedarray(
env: *mut Env,
value: napi_value,
- result: *mut napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- let proto = obj.get_prototype(&mut env.scope()).unwrap();
- *result = proto.into();
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = value.unwrap().is_typed_array();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_get_reference_value(
- env: *mut Env,
- reference: napi_ref,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_create_typedarray<'s>(
+ env: &'s mut Env,
+ ty: napi_typedarray_type,
+ length: usize,
+ arraybuffer: napi_value,
+ byte_offset: usize,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let value = transmute::<napi_ref, v8::Local<v8::Value>>(reference);
- *result = value.into();
+ check_arg!(env, arraybuffer);
+ check_arg!(env, result);
+
+ let Some(ab) =
+ arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
+ return napi_arraybuffer_expected;
+ };
+
+ macro_rules! create {
+ ($TypedArray:ident, $size_of_element:expr) => {{
+ let soe = $size_of_element;
+ if soe > 1 && byte_offset % soe != 0 {
+ let message = v8::String::new(
+ &mut env.scope(),
+ format!(
+ "start offset of {} should be multiple of {}",
+ stringify!($TypedArray),
+ soe
+ )
+ .as_str(),
+ )
+ .unwrap();
+ let exc = v8::Exception::range_error(&mut env.scope(), message);
+ env.scope().throw_exception(exc);
+ return napi_pending_exception;
+ }
+
+ if length * soe + byte_offset > ab.byte_length() {
+ let message =
+ v8::String::new(&mut env.scope(), "Invalid typed array length")
+ .unwrap();
+ let exc = v8::Exception::range_error(&mut env.scope(), message);
+ env.scope().throw_exception(exc);
+ return napi_pending_exception;
+ }
+
+ let Some(ta) =
+ v8::$TypedArray::new(&mut env.scope(), ab, byte_offset, length)
+ else {
+ return napi_generic_failure;
+ };
+ ta.into()
+ }};
+ }
+
+ let typedarray: v8::Local<v8::Value> = match ty {
+ napi_uint8_array => create!(Uint8Array, 1),
+ napi_uint8_clamped_array => create!(Uint8ClampedArray, 1),
+ napi_int8_array => create!(Int8Array, 1),
+ napi_uint16_array => create!(Uint16Array, 2),
+ napi_int16_array => create!(Int16Array, 2),
+ napi_uint32_array => create!(Uint32Array, 4),
+ napi_int32_array => create!(Int32Array, 4),
+ napi_float32_array => create!(Float32Array, 4),
+ napi_float64_array => create!(Float64Array, 8),
+ napi_bigint64_array => create!(BigInt64Array, 8),
+ napi_biguint64_array => create!(BigUint64Array, 8),
+ _ => {
+ return napi_invalid_arg;
+ }
+ };
+
+ unsafe {
+ *result = typedarray.into();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
+#[napi_sym]
fn napi_get_typedarray_info(
- env: *mut Env,
+ env_ptr: *mut Env,
typedarray: napi_value,
type_: *mut napi_typedarray_type,
length: *mut usize,
@@ -2018,364 +3048,278 @@ fn napi_get_typedarray_info(
arraybuffer: *mut napi_value,
byte_offset: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(typedarray);
- let Ok(array) = v8::Local::<v8::TypedArray>::try_from(value) else {
- return napi_invalid_arg;
+ let env = check_env!(env_ptr);
+ check_arg!(env, typedarray);
+
+ let Some(array) =
+ typedarray.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env_ptr, napi_invalid_arg);
};
if !type_.is_null() {
- if value.is_int8_array() {
- *type_ = napi_int8_array;
- } else if value.is_uint8_array() {
- *type_ = napi_uint8_array;
- } else if value.is_uint8_clamped_array() {
- *type_ = napi_uint8_clamped_array;
- } else if value.is_int16_array() {
- *type_ = napi_int16_array;
- } else if value.is_uint16_array() {
- *type_ = napi_uint16_array;
- } else if value.is_int32_array() {
- *type_ = napi_int32_array;
- } else if value.is_uint32_array() {
- *type_ = napi_uint32_array;
- } else if value.is_float32_array() {
- *type_ = napi_float32_array;
- } else if value.is_float64_array() {
- *type_ = napi_float64_array;
+ let tatype = if array.is_int8_array() {
+ napi_int8_array
+ } else if array.is_uint8_array() {
+ napi_uint8_array
+ } else if array.is_uint8_clamped_array() {
+ napi_uint8_clamped_array
+ } else if array.is_int16_array() {
+ napi_int16_array
+ } else if array.is_uint16_array() {
+ napi_uint16_array
+ } else if array.is_int32_array() {
+ napi_int32_array
+ } else if array.is_uint32_array() {
+ napi_uint32_array
+ } else if array.is_float32_array() {
+ napi_float32_array
+ } else if array.is_float64_array() {
+ napi_float64_array
+ } else if array.is_big_int64_array() {
+ napi_bigint64_array
+ } else if array.is_big_uint64_array() {
+ napi_biguint64_array
+ } else {
+ unreachable!();
+ };
+
+ unsafe {
+ *type_ = tatype;
}
}
if !length.is_null() {
- *length = array.length();
+ unsafe {
+ *length = array.length();
+ }
}
- if !data.is_null() || !arraybuffer.is_null() {
- let buf = array.buffer(&mut env.scope()).unwrap();
- if !data.is_null() {
- *data = get_array_buffer_ptr(buf) as *mut c_void;
+ if !data.is_null() {
+ unsafe {
+ *data = array.data();
}
- if !arraybuffer.is_null() {
+ }
+
+ if !arraybuffer.is_null() {
+ let buf = array.buffer(&mut env.scope()).unwrap();
+ unsafe {
*arraybuffer = buf.into();
}
}
if !byte_offset.is_null() {
- *byte_offset = array.byte_offset();
+ unsafe {
+ *byte_offset = array.byte_offset();
+ }
}
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status {
- check_env!(env);
- check_arg!(env, result);
- let env = unsafe { &mut *env };
- *result = v8::undefined(env.isolate()).into();
- napi_ok
-}
-
-pub const NAPI_VERSION: u32 = 8;
-
-#[napi_sym::napi_sym]
-fn napi_get_version(_: napi_env, version: *mut u32) -> napi_status {
- *version = NAPI_VERSION;
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_has_element(
- env: *mut Env,
- value: napi_value,
- index: u32,
- result: *mut bool,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- *result = obj.has_index(&mut env.scope(), index).unwrap_or(false);
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_has_named_property(
- env: *mut Env,
- value: napi_value,
- key: *const c_char,
- result: *mut bool,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- let key = CStr::from_ptr(key).to_str().unwrap();
- let key = v8::String::new(&mut env.scope(), key).unwrap();
- *result = obj.has(&mut env.scope(), key.into()).unwrap_or(false);
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_has_own_property(
- env: *mut Env,
- object: napi_value,
- key: napi_value,
- result: *mut bool,
+#[napi_sym]
+fn napi_create_dataview<'s>(
+ env: &'s mut Env,
+ byte_length: usize,
+ arraybuffer: napi_value,
+ byte_offset: usize,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, key);
+ check_arg!(env, arraybuffer);
check_arg!(env, result);
- let scope = &mut env.scope();
- let Some(object) = object.map(|o| o.to_object(scope)).flatten() else {
+ let Some(buffer) =
+ arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
return napi_invalid_arg;
};
- if key.is_none() {
- return napi_invalid_arg;
+ if byte_length + byte_offset > buffer.byte_length() {
+ unsafe {
+ return napi_throw_range_error(
+ env,
+ "ERR_NAPI_INVALID_DATAVIEW_ARGS\0".as_ptr() as _,
+ "byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in\0".as_ptr() as _,
+ );
+ }
}
- let Ok(key) = v8::Local::<v8::Name>::try_from(key.unwrap()) else {
- return napi_name_expected;
- };
- let Some(has_own) = object.has_own_property(scope, key) else {
- return napi_generic_failure;
+ // let dataview = v8::DataView::new(&mut env, buffer, byte_offset, byte_length);
+ let dataview = {
+ let context = &mut env.scope().get_current_context();
+ let global = context.global(&mut env.scope());
+ let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap();
+ let data_view =
+ global.get(&mut env.scope(), data_view_name.into()).unwrap();
+ let Ok(data_view) = v8::Local::<v8::Function>::try_from(data_view) else {
+ return napi_function_expected;
+ };
+ let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64);
+ let byte_length = v8::Number::new(&mut env.scope(), byte_length as f64);
+ let Some(dv) = data_view.new_instance(
+ &mut env.scope(),
+ &[buffer.into(), byte_offset.into(), byte_length.into()],
+ ) else {
+ return napi_generic_failure;
+ };
+ dv
};
- *result = has_own;
+ unsafe {
+ *result = dataview.into();
+ }
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_has_property(
+#[napi_sym]
+fn napi_is_dataview(
env: *mut Env,
- object: napi_value,
- key: napi_value,
+ value: napi_value,
result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, key);
+ let env = check_env!(env);
+ check_arg!(env, value);
check_arg!(env, result);
- let scope = &mut env.scope();
- let Some(object) = object.map(|o| o.to_object(scope)).flatten() else {
- return napi_invalid_arg;
- };
+ unsafe {
+ *result = value.unwrap().is_data_view();
+ }
- let Some(has) = object.has(scope, key.unwrap_unchecked()) else {
- return napi_generic_failure;
- };
- *result = has;
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_instanceof(
- env: *mut Env,
- value: napi_value,
- constructor: napi_value,
- result: *mut bool,
+#[napi_sym]
+fn napi_get_dataview_info(
+ env_ptr: *mut Env,
+ dataview: napi_value,
+ byte_length: *mut usize,
+ data: *mut *mut c_void,
+ arraybuffer: *mut napi_value,
+ byte_offset: *mut usize,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, constructor);
- check_arg_option!(env, value);
+ let env = check_env!(env_ptr);
+ check_arg!(env, dataview);
- let value = napi_value_unchecked(value);
- let constructor = napi_value_unchecked(constructor);
- let Some(ctor) = constructor.to_object(&mut env.scope()) else {
- return napi_object_expected;
- };
- if !ctor.is_function() {
- return napi_function_expected;
- }
- let Some(res) = value.instance_of(&mut env.scope(), ctor) else {
- return napi_generic_failure;
+ let Some(array) =
+ dataview.and_then(|v| v8::Local::<v8::DataView>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
};
- *result = res;
- napi_ok
-}
+ if !byte_length.is_null() {
+ unsafe {
+ *byte_length = array.byte_length();
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_is_array(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_array();
- napi_ok
-}
+ if !arraybuffer.is_null() {
+ let Some(buffer) = array.buffer(&mut env.scope()) else {
+ return napi_generic_failure;
+ };
-#[napi_sym::napi_sym]
-fn napi_is_arraybuffer(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_array_buffer();
- napi_ok
-}
+ unsafe {
+ *arraybuffer = buffer.into();
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_is_buffer(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- // TODO: should we assume Buffer as Uint8Array in Deno?
- // or use std/node polyfill?
- *result = value.is_typed_array();
- napi_ok
-}
+ if !data.is_null() {
+ unsafe {
+ *data = array.data();
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_is_dataview(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_data_view();
- napi_ok
-}
+ if !byte_offset.is_null() {
+ unsafe {
+ *byte_offset = array.byte_offset();
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_is_date(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_date();
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_is_detached_arraybuffer(
- env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- check_env!(env);
+#[napi_sym]
+fn napi_get_version(env: *mut Env, result: *mut u32) -> napi_status {
+ let env = check_env!(env);
check_arg!(env, result);
- let value = napi_value_unchecked(value);
-
- *result = match v8::Local::<v8::ArrayBuffer>::try_from(value) {
- Ok(array_buffer) => array_buffer.was_detached(),
- Err(_) => false,
- };
-
- napi_clear_last_error(env);
+ unsafe {
+ *result = NAPI_VERSION;
+ }
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_is_error(
- env: *mut Env,
- value: napi_value,
- result: *mut bool,
+#[napi_sym]
+fn napi_create_promise<'s>(
+ env: &'s mut Env,
+ deferred: *mut napi_deferred,
+ promise: *mut napi_value<'s>,
) -> napi_status {
- {
- check_env!(env);
- if value.is_none() {
- return napi_invalid_arg;
- }
- check_arg!(env, result);
+ check_arg!(env, deferred);
+ check_arg!(env, promise);
- let value = napi_value_unchecked(value);
- *result = value.is_native_error();
- }
- napi_clear_last_error(env);
- napi_ok
-}
+ let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap();
-#[napi_sym::napi_sym]
-fn napi_is_exception_pending(_env: *mut Env, result: *mut bool) -> napi_status {
- // TODO
- *result = false;
- napi_ok
-}
+ let global = v8::Global::new(&mut env.scope(), resolver);
+ let global_ptr = global.into_raw().as_ptr() as napi_deferred;
-#[napi_sym::napi_sym]
-fn napi_is_promise(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_promise();
- napi_ok
-}
+ let p = resolver.get_promise(&mut env.scope());
-#[napi_sym::napi_sym]
-fn napi_is_typedarray(
- _env: *mut Env,
- value: napi_value,
- result: *mut bool,
-) -> napi_status {
- let value = napi_value_unchecked(value);
- *result = value.is_typed_array();
- napi_ok
-}
+ unsafe {
+ *deferred = global_ptr;
+ }
+
+ unsafe {
+ *promise = p.into();
+ }
-#[napi_sym::napi_sym]
-fn napi_new_instance(
- env: *mut Env,
- constructor: napi_value,
- argc: usize,
- argv: *const napi_value,
- result: *mut napi_value,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let constructor = napi_value_unchecked(constructor);
- let Ok(constructor) = v8::Local::<v8::Function>::try_from(constructor) else {
- return napi_function_expected;
- };
- let args: &[v8::Local<v8::Value>] =
- transmute(std::slice::from_raw_parts(argv, argc));
- let inst = constructor.new_instance(&mut env.scope(), args).unwrap();
- let value: v8::Local<v8::Value> = inst.into();
- *result = value.into();
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_object_freeze(
+#[napi_sym]
+fn napi_resolve_deferred(
env: &mut Env,
- object: v8::Local<v8::Value>,
+ deferred: napi_deferred,
+ result: napi_value,
) -> napi_status {
- let object = object.to_object(&mut env.scope()).unwrap();
- if object
- .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Frozen)
- .is_none()
+ check_arg!(env, result);
+ check_arg!(env, deferred);
+
+ let isolate = unsafe { &mut *env.isolate_ptr };
+ let deferred_ptr =
+ unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) };
+ let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) };
+ let resolver = v8::Local::new(&mut env.scope(), global);
+
+ if !resolver
+ .resolve(&mut env.scope(), result.unwrap())
+ .unwrap_or(false)
{
return napi_generic_failure;
- };
+ }
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_object_seal(
+#[napi_sym]
+fn napi_reject_deferred(
env: &mut Env,
- object: v8::Local<v8::Value>,
+ deferred: napi_deferred,
+ result: napi_value,
) -> napi_status {
- let object = object.to_object(&mut env.scope()).unwrap();
- if object
- .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Sealed)
- .is_none()
+ check_arg!(env, result);
+ check_arg!(env, deferred);
+
+ let isolate = unsafe { &mut *env.isolate_ptr };
+ let deferred_ptr =
+ unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) };
+ let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) };
+ let resolver = v8::Local::new(&mut env.scope(), global);
+
+ if !resolver
+ .reject(&mut env.scope(), result.unwrap())
+ .unwrap_or(false)
{
return napi_generic_failure;
}
@@ -2383,453 +3327,260 @@ fn napi_object_seal(
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_open_escapable_handle_scope(
- _env: *mut Env,
- _result: *mut napi_escapable_handle_scope,
+#[napi_sym]
+fn napi_is_promise(
+ env: *mut Env,
+ value: napi_value,
+ is_promise: *mut bool,
) -> napi_status {
- // TODO: do this properly
- napi_ok
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, is_promise);
+
+ unsafe {
+ *is_promise = value.unwrap().is_promise();
+ }
+
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_open_handle_scope(
- env: *mut Env,
- _result: *mut napi_handle_scope,
+#[napi_sym]
+fn napi_create_date<'s>(
+ env: &'s mut Env,
+ time: f64,
+ result: *mut napi_value<'s>,
) -> napi_status {
- let env = &mut *env;
+ check_arg!(env, result);
- // TODO: this is also not implemented in napi_close_handle_scope
- // *result = &mut env.scope() as *mut _ as napi_handle_scope;
- env.open_handle_scopes += 1;
- napi_ok
-}
+ let Some(date) = v8::Date::new(&mut env.scope(), time) else {
+ return napi_generic_failure;
+ };
-#[napi_sym::napi_sym]
-fn napi_reference_ref() -> napi_status {
- // TODO
- napi_ok
-}
+ unsafe {
+ *result = date.into();
+ }
-#[napi_sym::napi_sym]
-fn napi_reference_unref() -> napi_status {
- // TODO
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_reject_deferred(
+#[napi_sym]
+fn napi_is_date(
env: *mut Env,
- deferred: napi_deferred,
- error: napi_value,
+ value: napi_value,
+ is_date: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, is_date);
- let deferred_ptr =
- NonNull::new_unchecked(deferred as *mut v8::PromiseResolver);
- // TODO(@littledivy): Use Global::from_raw instead casting to local.
- // SAFETY: Isolate is still alive unless the module is doing something weird,
- // global data is the size of a pointer.
- // Global pointer is obtained from napi_create_promise
- let resolver = transmute::<
- NonNull<v8::PromiseResolver>,
- v8::Local<v8::PromiseResolver>,
- >(deferred_ptr);
- resolver
- .reject(&mut env.scope(), napi_value_unchecked(error))
- .unwrap();
- napi_ok
-}
+ unsafe {
+ *is_date = value.unwrap().is_date();
+ }
-#[napi_sym::napi_sym]
-fn napi_remove_wrap(env: *mut Env, value: napi_value) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- let shared = &*(env.shared as *const EnvShared);
- let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap);
- obj.delete_private(&mut env.scope(), napi_wrap).unwrap();
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_resolve_deferred(
- env: *mut Env,
- deferred: napi_deferred,
- result: napi_value,
+#[napi_sym]
+fn napi_get_date_value(
+ env: &mut Env,
+ value: napi_value,
+ result: *mut f64,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let deferred_ptr =
- NonNull::new_unchecked(deferred as *mut v8::PromiseResolver);
- // TODO(@littledivy): Use Global::from_raw instead casting to local.
- // SAFETY: Isolate is still alive unless the module is doing something weird,
- // global data is the size of a pointer.
- // Global pointer is obtained from napi_create_promise
- let resolver = transmute::<
- NonNull<v8::PromiseResolver>,
- v8::Local<v8::PromiseResolver>,
- >(deferred_ptr);
- resolver
- .resolve(&mut env.scope(), napi_value_unchecked(result))
- .unwrap();
+ check_arg!(env, result);
+
+ let Some(date) = value.and_then(|v| v8::Local::<v8::Date>::try_from(v).ok())
+ else {
+ return napi_date_expected;
+ };
+
+ unsafe {
+ *result = date.value_of();
+ }
+
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_run_script(
- env: *mut Env,
+#[napi_sym]
+fn napi_run_script<'s>(
+ env: &'s mut Env,
script: napi_value,
- result: *mut napi_value,
+ result: *mut napi_value<'s>,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+ check_arg!(env, script);
+ check_arg!(env, result);
- let script = napi_value_unchecked(script);
- if !script.is_string() {
- // TODO:
- // napi_set_last_error
+ let Some(script) =
+ script.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
return napi_string_expected;
- }
- let script = script.to_string(&mut env.scope()).unwrap();
+ };
- let script = v8::Script::compile(&mut env.scope(), script, None);
- if script.is_none() {
+ let Some(script) = v8::Script::compile(&mut env.scope(), script, None) else {
return napi_generic_failure;
- }
- let script = script.unwrap();
- let rv = script.run(&mut env.scope());
+ };
- if let Some(rv) = rv {
- *result = rv.into();
- } else {
+ let Some(rv) = script.run(&mut env.scope()) else {
return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = rv.into();
}
napi_ok
}
-#[napi_sym::napi_sym]
-fn napi_set_element(
- env: *mut Env,
- object: napi_value,
- index: u32,
+#[napi_sym]
+fn napi_add_finalizer(
+ env_ptr: *mut Env,
value: napi_value,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let object = napi_value_unchecked(object);
- let Ok(object) = v8::Local::<v8::Object>::try_from(object) else {
- return napi_invalid_arg;
- };
- let value = napi_value_unchecked(value);
- object.set_index(&mut env.scope(), index, value).unwrap();
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_set_instance_data(
- env: *mut Env,
- data: *mut c_void,
+ finalize_data: *mut c_void,
finalize_cb: Option<napi_finalize>,
finalize_hint: *mut c_void,
+ result: *mut napi_ref,
) -> napi_status {
- let env = &mut *env;
- let shared = env.shared_mut();
- shared.instance_data = data;
- shared.data_finalize = if finalize_cb.is_some() {
- finalize_cb
- } else {
- None
- };
- shared.data_finalize_hint = finalize_hint;
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_set_named_property(
- env: *mut Env,
- object: napi_value,
- name: *const c_char,
- value: napi_value,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let name = CStr::from_ptr(name).to_str().unwrap();
- let object = transmute::<napi_value, v8::Local<v8::Object>>(object);
- let value = napi_value_unchecked(value);
- let name = v8::String::new(&mut env.scope(), name).unwrap();
- object.set(&mut env.scope(), name.into(), value).unwrap();
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_set_property(
- env: *mut Env,
- object: napi_value,
- key: napi_value,
- value: napi_value,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, key);
- check_arg_option!(env, value);
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, finalize_cb);
- let scope = &mut env.scope();
- let Some(object) = object.map(|o| o.to_object(scope)).flatten() else {
- return napi_invalid_arg;
+ let Some(value) =
+ value.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_invalid_arg);
};
- if object
- .set(scope, key.unwrap_unchecked(), value.unwrap_unchecked())
- .is_none()
- {
- return napi_generic_failure;
+ let ownership = if result.is_null() {
+ ReferenceOwnership::Runtime
+ } else {
+ ReferenceOwnership::Userland
};
- napi_ok
-}
-
-#[napi_sym::napi_sym]
-fn napi_strict_equals(
- env: *mut Env,
- lhs: napi_value,
- rhs: napi_value,
- result: *mut bool,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- check_arg_option!(env, lhs);
- check_arg_option!(env, rhs);
+ let reference = Reference::new(
+ env,
+ value.into(),
+ 0,
+ ownership,
+ finalize_cb,
+ finalize_data,
+ finalize_hint,
+ );
- *result = lhs.unwrap_unchecked().strict_equals(rhs.unwrap_unchecked());
- napi_ok
-}
+ if !result.is_null() {
+ unsafe {
+ *result = Reference::into_raw(reference) as _;
+ }
+ }
-#[napi_sym::napi_sym]
-fn napi_throw(env: *mut Env, error: napi_value) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let error = napi_value_unchecked(error);
- env.scope().throw_exception(error);
- napi_ok
+ napi_clear_last_error(env_ptr)
}
-#[napi_sym::napi_sym]
-fn napi_throw_error(
+#[napi_sym]
+fn node_api_post_finalizer(
env: *mut Env,
- code: *const c_char,
- msg: *const c_char,
+ _finalize_cb: napi_finalize,
+ _finalize_data: *mut c_void,
+ _finalize_hint: *mut c_void,
) -> napi_status {
- // TODO: add preamble here
-
- {
- check_env!(env);
- let str_ = match check_new_from_utf8(env, msg) {
- Ok(s) => s,
- Err(status) => return status,
- };
-
- let error = {
- let env = unsafe { &mut *env };
- let scope = &mut env.scope();
- v8::Exception::error(scope, str_)
- };
- status_call!(set_error_code(
- env,
- error,
- transmute::<*mut (), napi_value>(std::ptr::null_mut()),
- code,
- ));
-
- unsafe { &mut *env }.scope().throw_exception(error);
- }
- napi_clear_last_error(env);
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_throw_range_error(
+#[napi_sym]
+fn napi_adjust_external_memory(
env: *mut Env,
- code: *const c_char,
- msg: *const c_char,
+ change_in_bytes: i64,
+ adjusted_value: *mut i64,
) -> napi_status {
- // TODO: add preamble here
+ let env = check_env!(env);
+ check_arg!(env, adjusted_value);
- {
- check_env!(env);
- let str_ = match check_new_from_utf8(env, msg) {
- Ok(s) => s,
- Err(status) => return status,
- };
- let error = {
- let env = unsafe { &mut *env };
- let scope = &mut env.scope();
- v8::Exception::range_error(scope, str_)
- };
- status_call!(set_error_code(
- env,
- error,
- transmute::<*mut (), napi_value>(std::ptr::null_mut()),
- code,
- ));
- unsafe { &mut *env }.scope().throw_exception(error);
+ let isolate = unsafe { &mut *env.isolate_ptr };
+
+ unsafe {
+ *adjusted_value =
+ isolate.adjust_amount_of_external_allocated_memory(change_in_bytes);
}
- napi_clear_last_error(env);
- napi_ok
+
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_throw_type_error(
+#[napi_sym]
+fn napi_set_instance_data(
env: *mut Env,
- code: *const c_char,
- msg: *const c_char,
+ data: *mut c_void,
+ finalize_cb: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
) -> napi_status {
- // TODO: add preamble here
+ let env = check_env!(env);
- {
- check_env!(env);
- let str_ = match check_new_from_utf8(env, msg) {
- Ok(s) => s,
- Err(status) => return status,
- };
- let error = {
- let env = unsafe { &mut *env };
- let scope = &mut env.scope();
- v8::Exception::type_error(scope, str_)
- };
- status_call!(set_error_code(
- env,
- error,
- transmute::<*mut (), napi_value>(std::ptr::null_mut()),
- code,
- ));
- unsafe { &mut *env }.scope().throw_exception(error);
- }
- napi_clear_last_error(env);
- napi_ok
-}
+ env.shared_mut().instance_data = Some(InstanceData {
+ data,
+ finalize_cb,
+ finalize_hint,
+ });
-pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> {
- if value.is_undefined() {
- Some(napi_undefined)
- } else if value.is_null() {
- Some(napi_null)
- } else if value.is_external() {
- Some(napi_external)
- } else if value.is_boolean() {
- Some(napi_boolean)
- } else if value.is_number() {
- Some(napi_number)
- } else if value.is_big_int() {
- Some(napi_bigint)
- } else if value.is_string() {
- Some(napi_string)
- } else if value.is_symbol() {
- Some(napi_symbol)
- } else if value.is_function() {
- Some(napi_function)
- } else if value.is_object() {
- Some(napi_object)
- } else {
- None
- }
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn napi_typeof(
+#[napi_sym]
+fn napi_get_instance_data(
env: *mut Env,
- value: napi_value,
- result: *mut napi_valuetype,
+ data: *mut *mut c_void,
) -> napi_status {
- check_env!(env);
- check_arg_option!(env, value);
- check_arg!(env, result);
+ let env = check_env!(env);
+ check_arg!(env, data);
- let Some(ty) = get_value_type(value.unwrap()) else {
- return napi_invalid_arg;
+ let instance_data = match &env.shared().instance_data {
+ Some(v) => v.data,
+ None => std::ptr::null_mut(),
};
- *result = ty;
- napi_ok
-}
-#[napi_sym::napi_sym]
-fn napi_unwrap(
- env: *mut Env,
- value: napi_value,
- result: *mut *mut c_void,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- let shared = &*(env.shared as *const EnvShared);
- let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap);
- let ext = obj.get_private(&mut env.scope(), napi_wrap).unwrap();
- let Some(ext) = v8::Local::<v8::External>::try_from(ext).ok() else {
- return napi_invalid_arg;
- };
- *result = ext.value();
- napi_ok
-}
+ unsafe { *data = instance_data };
-#[napi_sym::napi_sym]
-fn napi_wrap(
- env: *mut Env,
- value: napi_value,
- native_object: *mut c_void,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
- let value = napi_value_unchecked(value);
- let obj = value.to_object(&mut env.scope()).unwrap();
- let shared = &*(env.shared as *const EnvShared);
- let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap);
- let ext = v8::External::new(&mut env.scope(), native_object);
- obj.set_private(&mut env.scope(), napi_wrap, ext.into());
- napi_ok
+ napi_clear_last_error(env)
}
-#[napi_sym::napi_sym]
-fn node_api_throw_syntax_error(
- env: *mut Env,
- _code: *const c_char,
- msg: *const c_char,
-) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
+#[napi_sym]
+fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
- // let code = CStr::from_ptr(code).to_str().unwrap();
- let msg = CStr::from_ptr(msg).to_str().unwrap();
+ let Some(ab) =
+ value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_arraybuffer_expected);
+ };
- // let code = v8::String::new(&mut env.scope(), code).unwrap();
- let msg = v8::String::new(&mut env.scope(), msg).unwrap();
+ if !ab.is_detachable() {
+ return napi_set_last_error(env, napi_detachable_arraybuffer_expected);
+ }
- let error = v8::Exception::syntax_error(&mut env.scope(), msg);
- env.scope().throw_exception(error);
+ // Expected to crash for None.
+ ab.detach(None).unwrap();
+ napi_clear_last_error(env);
napi_ok
}
-#[napi_sym::napi_sym]
-fn node_api_create_syntax_error(
- env: *mut Env,
- _code: napi_value,
- msg: napi_value,
- result: *mut napi_value,
+#[napi_sym]
+fn napi_is_detached_arraybuffer(
+ env_ptr: *mut Env,
+ arraybuffer: napi_value,
+ result: *mut bool,
) -> napi_status {
- check_env!(env);
- let env = unsafe { &mut *env };
-
- // let code = napi_value_unchecked(code);
- let msg = napi_value_unchecked(msg);
+ let env = check_env!(env_ptr);
+ check_arg!(env, arraybuffer);
+ check_arg!(env, result);
- let msg = msg.to_string(&mut env.scope()).unwrap();
+ let is_detached = match arraybuffer
+ .and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ {
+ Some(ab) => ab.was_detached(),
+ None => false,
+ };
- let error = v8::Exception::syntax_error(&mut env.scope(), msg);
- *result = error.into();
+ unsafe {
+ *result = is_detached;
+ }
- napi_ok
+ napi_clear_last_error(env)
}
diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs
index 697ec06e3..122d2ff06 100644
--- a/cli/napi/mod.rs
+++ b/cli/napi/mod.rs
@@ -15,8 +15,6 @@
//! 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`.
-pub mod r#async;
-pub mod env;
pub mod js_native_api;
-pub mod threadsafe_functions;
+pub mod node_api;
pub mod util;
diff --git a/cli/napi/node_api.rs b/cli/napi/node_api.rs
new file mode 100644
index 000000000..28a11d614
--- /dev/null
+++ b/cli/napi/node_api.rs
@@ -0,0 +1,1020 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+#![deny(unsafe_op_in_unsafe_fn)]
+
+use super::util::get_array_buffer_ptr;
+use super::util::make_external_backing_store;
+use super::util::napi_clear_last_error;
+use super::util::napi_set_last_error;
+use super::util::SendPtr;
+use crate::check_arg;
+use crate::check_env;
+use deno_core::V8CrossThreadTaskSpawner;
+use deno_runtime::deno_napi::*;
+use napi_sym::napi_sym;
+use std::ptr::NonNull;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::AtomicU8;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+
+#[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]
+fn napi_add_env_cleanup_hook(
+ env: *mut Env,
+ fun: Option<napi_cleanup_hook>,
+ arg: *mut c_void,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, fun);
+
+ let fun = fun.unwrap();
+
+ let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut();
+ if env_cleanup_hooks
+ .iter()
+ .any(|pair| pair.0 == fun && pair.1 == arg)
+ {
+ panic!("Cleanup hook with this data already registered");
+ }
+ env_cleanup_hooks.push((fun, arg));
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_remove_env_cleanup_hook(
+ env: *mut Env,
+ fun: Option<napi_cleanup_hook>,
+ arg: *mut c_void,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, fun);
+
+ let fun = fun.unwrap();
+
+ 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 == fun && pair.1 == arg);
+
+ if let Some(index) = maybe_index {
+ env_cleanup_hooks.remove(index);
+ } else {
+ panic!("Cleanup hook with this data not found");
+ }
+
+ napi_ok
+}
+
+type AsyncCleanupHandle = (*mut Env, napi_async_cleanup_hook, *mut c_void);
+
+unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) {
+ unsafe {
+ let (env, hook, arg) = *Box::<AsyncCleanupHandle>::from_raw(arg as _);
+ hook(env as _, arg);
+ }
+}
+
+#[napi_sym]
+fn napi_add_async_cleanup_hook(
+ env: *mut Env,
+ hook: Option<napi_async_cleanup_hook>,
+ arg: *mut c_void,
+ remove_handle: *mut napi_async_cleanup_hook_handle,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, hook);
+
+ let hook = hook.unwrap();
+
+ let handle =
+ Box::into_raw(Box::<AsyncCleanupHandle>::new((env, hook, arg))) as _;
+
+ unsafe {
+ napi_add_env_cleanup_hook(env, Some(async_cleanup_handler), handle);
+ }
+
+ if !remove_handle.is_null() {
+ unsafe {
+ *remove_handle = handle;
+ }
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_remove_async_cleanup_hook(
+ remove_handle: napi_async_cleanup_hook_handle,
+) -> napi_status {
+ if remove_handle.is_null() {
+ return napi_invalid_arg;
+ }
+
+ let handle = unsafe { &*(remove_handle as *mut AsyncCleanupHandle) };
+
+ let env = unsafe { &mut *handle.0 };
+
+ unsafe {
+ napi_remove_env_cleanup_hook(
+ env,
+ Some(async_cleanup_handler),
+ remove_handle,
+ );
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_fatal_exception(env: &mut Env, err: napi_value) -> napi_status {
+ check_arg!(env, err);
+
+ let report_error = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.report_error,
+ )
+ };
+
+ let this = v8::undefined(&mut env.scope());
+ if report_error
+ .call(&mut env.scope(), this.into(), &[err.unwrap()])
+ .is_none()
+ {
+ return napi_generic_failure;
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+#[allow(clippy::print_stderr)]
+fn napi_fatal_error(
+ location: *const c_char,
+ location_len: usize,
+ message: *const c_char,
+ message_len: usize,
+) -> napi_status {
+ let location = if location.is_null() {
+ None
+ } else {
+ unsafe {
+ Some(if location_len == NAPI_AUTO_LENGTH {
+ 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 == NAPI_AUTO_LENGTH {
+ unsafe { std::ffi::CStr::from_ptr(message).to_str().unwrap() }
+ } else {
+ let slice = unsafe {
+ std::slice::from_raw_parts(message as *const u8, message_len as usize)
+ };
+ std::str::from_utf8(slice).unwrap()
+ };
+
+ if let Some(location) = location {
+ eprintln!("NODE API FATAL ERROR: {} {}", location, message);
+ } else {
+ eprintln!("NODE API FATAL ERROR: {}", message);
+ }
+
+ std::process::abort();
+}
+
+#[napi_sym]
+fn napi_open_callback_scope(
+ env: *mut Env,
+ _resource_object: napi_value,
+ _context: napi_value,
+ result: *mut napi_callback_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ // we open scope automatically when it's needed
+ unsafe {
+ *result = std::ptr::null_mut();
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_close_callback_scope(
+ env: *mut Env,
+ scope: napi_callback_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ // we close scope automatically when it's needed
+ assert!(scope.is_null());
+ napi_clear_last_error(env)
+}
+
+// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops.
+#[napi_sym]
+fn napi_async_init(
+ env: *mut Env,
+ _async_resource: napi_value,
+ _async_resource_name: napi_value,
+ result: *mut napi_async_context,
+) -> napi_status {
+ let env = check_env!(env);
+ unsafe {
+ *result = ptr::null_mut();
+ }
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_async_destroy(
+ env: *mut Env,
+ async_context: napi_async_context,
+) -> napi_status {
+ let env = check_env!(env);
+ assert!(async_context.is_null());
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_make_callback<'s>(
+ env: &'s mut Env,
+ _async_context: napi_async_context,
+ recv: napi_value,
+ func: napi_value,
+ argc: usize,
+ argv: *const napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, recv);
+ if argc > 0 {
+ check_arg!(env, argv);
+ }
+
+ let Some(recv) = recv.and_then(|v| v.to_object(&mut env.scope())) else {
+ return napi_object_expected;
+ };
+
+ let Some(func) =
+ func.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok())
+ else {
+ return napi_function_expected;
+ };
+
+ let args = if argc > 0 {
+ unsafe {
+ std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc)
+ }
+ } else {
+ &[]
+ };
+
+ // TODO: async_context
+
+ let Some(v) = func.call(&mut env.scope(), recv.into(), args) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = v.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_create_buffer<'s>(
+ env: &'s mut Env,
+ length: usize,
+ data: *mut *mut c_void,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, result);
+
+ let ab = v8::ArrayBuffer::new(&mut env.scope(), length);
+
+ let buffer_constructor = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.buffer_constructor,
+ )
+ };
+ let Some(buffer) =
+ buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
+ else {
+ return napi_generic_failure;
+ };
+
+ if !data.is_null() {
+ unsafe {
+ *data = get_array_buffer_ptr(ab) as _;
+ }
+ }
+
+ unsafe {
+ *result = buffer.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_create_external_buffer<'s>(
+ env: &'s mut Env,
+ length: usize,
+ data: *mut c_void,
+ finalize_cb: napi_finalize,
+ finalize_hint: *mut c_void,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, result);
+
+ let store = make_external_backing_store(
+ env,
+ data,
+ length,
+ ptr::null_mut(),
+ finalize_cb,
+ finalize_hint,
+ );
+
+ let ab =
+ v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared());
+
+ let buffer_constructor = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.buffer_constructor,
+ )
+ };
+ let Some(buffer) =
+ buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
+ else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = buffer.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_create_buffer_copy<'s>(
+ env: &'s mut Env,
+ length: usize,
+ data: *mut c_void,
+ result_data: *mut *mut c_void,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, result);
+
+ let ab = v8::ArrayBuffer::new(&mut env.scope(), length);
+
+ let buffer_constructor = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.buffer_constructor,
+ )
+ };
+ let Some(buffer) =
+ buffer_constructor.new_instance(&mut env.scope(), &[ab.into()])
+ else {
+ return napi_generic_failure;
+ };
+
+ let ptr = get_array_buffer_ptr(ab) as *mut c_void;
+ unsafe {
+ std::ptr::copy(data, ptr, length);
+ }
+
+ if !result_data.is_null() {
+ unsafe {
+ *result_data = ptr;
+ }
+ }
+
+ unsafe {
+ *result = buffer.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_buffer(
+ env: *mut Env,
+ value: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let buffer_constructor = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.buffer_constructor,
+ )
+ };
+
+ let Some(is_buffer) = value
+ .unwrap()
+ .instance_of(&mut env.scope(), buffer_constructor.into())
+ else {
+ return napi_set_last_error(env, napi_generic_failure);
+ };
+
+ unsafe {
+ *result = is_buffer;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_buffer_info(
+ env: *mut Env,
+ value: napi_value,
+ data: *mut *mut c_void,
+ length: *mut usize,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+
+ let Some(ta) =
+ value.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_invalid_arg);
+ };
+
+ let buffer_constructor = unsafe {
+ std::mem::transmute::<NonNull<v8::Function>, v8::Local<v8::Function>>(
+ env.buffer_constructor,
+ )
+ };
+
+ if !ta
+ .instance_of(&mut env.scope(), buffer_constructor.into())
+ .unwrap_or(false)
+ {
+ return napi_set_last_error(env, napi_invalid_arg);
+ }
+
+ if !data.is_null() {
+ unsafe {
+ *data = ta.data();
+ }
+ }
+
+ if !length.is_null() {
+ unsafe {
+ *length = ta.byte_length();
+ }
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_node_version(
+ env: *mut Env,
+ result: *mut *const napi_node_version,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ const NODE_VERSION: napi_node_version = napi_node_version {
+ major: 20,
+ minor: 11,
+ patch: 1,
+ release: c"Deno".as_ptr(),
+ };
+
+ unsafe {
+ *result = &NODE_VERSION as *const napi_node_version;
+ }
+
+ napi_clear_last_error(env)
+}
+
+struct AsyncWork {
+ state: AtomicU8,
+ env: *mut Env,
+ _async_resource: v8::Global<v8::Object>,
+ _async_resource_name: String,
+ execute: napi_async_execute_callback,
+ complete: Option<napi_async_complete_callback>,
+ data: *mut c_void,
+}
+
+impl AsyncWork {
+ const IDLE: u8 = 0;
+ const QUEUED: u8 = 1;
+ const RUNNING: u8 = 2;
+}
+
+#[napi_sym]
+fn napi_create_async_work(
+ env: *mut Env,
+ async_resource: napi_value,
+ async_resource_name: napi_value,
+ execute: Option<napi_async_execute_callback>,
+ complete: Option<napi_async_complete_callback>,
+ data: *mut c_void,
+ result: *mut napi_async_work,
+) -> napi_status {
+ let env_ptr = env;
+ let env = check_env!(env);
+ check_arg!(env, execute);
+ check_arg!(env, result);
+
+ let resource = if let Some(v) = *async_resource {
+ let Some(resource) = v.to_object(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_object_expected);
+ };
+ resource
+ } else {
+ v8::Object::new(&mut env.scope())
+ };
+
+ let Some(resource_name) =
+ async_resource_name.and_then(|v| v.to_string(&mut env.scope()))
+ else {
+ return napi_set_last_error(env, napi_string_expected);
+ };
+
+ let resource_name = resource_name.to_rust_string_lossy(&mut env.scope());
+
+ let work = Box::new(AsyncWork {
+ state: AtomicU8::new(AsyncWork::IDLE),
+ env: env_ptr,
+ _async_resource: v8::Global::new(&mut env.scope(), resource),
+ _async_resource_name: resource_name,
+ execute: execute.unwrap(),
+ complete,
+ data,
+ });
+
+ unsafe {
+ *result = Box::into_raw(work) as _;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_delete_async_work(env: *mut Env, work: napi_async_work) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, work);
+
+ drop(unsafe { Box::<AsyncWork>::from_raw(work as _) });
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_uv_event_loop(env: *mut Env, uv_loop: *mut *mut ()) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, uv_loop);
+ // There is no uv_loop in Deno
+ napi_set_last_error(env, napi_generic_failure)
+}
+
+#[napi_sym]
+fn napi_queue_async_work(env: *mut Env, work: napi_async_work) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, work);
+
+ let work = unsafe { &*(work as *mut AsyncWork) };
+
+ let result =
+ work
+ .state
+ .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
+ // allow queue if idle or if running, but not if already queued.
+ if state == AsyncWork::IDLE || state == AsyncWork::RUNNING {
+ Some(AsyncWork::QUEUED)
+ } else {
+ None
+ }
+ });
+
+ if result.is_err() {
+ return napi_clear_last_error(env);
+ }
+
+ let work = SendPtr(work);
+
+ env.add_async_work(move || {
+ let work = work.take();
+ let work = unsafe { &*work };
+
+ let state = work.state.compare_exchange(
+ AsyncWork::QUEUED,
+ AsyncWork::RUNNING,
+ Ordering::SeqCst,
+ Ordering::SeqCst,
+ );
+
+ if state.is_ok() {
+ unsafe {
+ (work.execute)(work.env as _, work.data);
+ }
+
+ // reset back to idle if its still marked as running
+ let _ = work.state.compare_exchange(
+ AsyncWork::RUNNING,
+ AsyncWork::IDLE,
+ Ordering::SeqCst,
+ Ordering::Relaxed,
+ );
+ }
+
+ if let Some(complete) = work.complete {
+ let status = if state.is_ok() {
+ napi_ok
+ } else if state == Err(AsyncWork::IDLE) {
+ napi_cancelled
+ } else {
+ napi_generic_failure
+ };
+
+ unsafe {
+ complete(work.env as _, status, work.data);
+ }
+ }
+
+ // `complete` probably deletes this `work`, so don't use it here.
+ });
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_cancel_async_work(env: *mut Env, work: napi_async_work) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, work);
+
+ let work = unsafe { &*(work as *mut AsyncWork) };
+
+ let _ = work.state.compare_exchange(
+ AsyncWork::QUEUED,
+ AsyncWork::IDLE,
+ Ordering::SeqCst,
+ Ordering::Relaxed,
+ );
+
+ napi_clear_last_error(env)
+}
+
+extern "C" fn default_call_js_cb(
+ env: napi_env,
+ js_callback: napi_value,
+ _context: *mut c_void,
+ _data: *mut c_void,
+) {
+ if let Some(js_callback) = *js_callback {
+ if let Ok(js_callback) = v8::Local::<v8::Function>::try_from(js_callback) {
+ let env = unsafe { &mut *(env as *mut Env) };
+ let scope = &mut env.scope();
+ let recv = v8::undefined(scope);
+ js_callback.call(scope, recv.into(), &[]);
+ }
+ }
+}
+
+struct TsFn {
+ env: *mut Env,
+ func: Option<v8::Global<v8::Function>>,
+ _max_queue_size: usize,
+ thread_count: AtomicUsize,
+ thread_finalize_data: *mut c_void,
+ thread_finalize_cb: Option<napi_finalize>,
+ context: *mut c_void,
+ call_js_cb: napi_threadsafe_function_call_js,
+ _resource: v8::Global<v8::Object>,
+ _resource_name: String,
+ is_closing: AtomicBool,
+ is_closed: Arc<AtomicBool>,
+ sender: V8CrossThreadTaskSpawner,
+ is_ref: AtomicBool,
+}
+
+impl Drop for TsFn {
+ fn drop(&mut self) {
+ assert!(self
+ .is_closed
+ .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
+ .is_ok());
+
+ self.unref();
+
+ if let Some(finalizer) = self.thread_finalize_cb {
+ unsafe {
+ (finalizer)(self.env as _, self.thread_finalize_data, ptr::null_mut());
+ }
+ }
+ }
+}
+
+impl TsFn {
+ pub fn acquire(&self) -> napi_status {
+ if self.is_closing.load(Ordering::SeqCst) {
+ return napi_closing;
+ }
+ self.thread_count.fetch_add(1, Ordering::Relaxed);
+ napi_ok
+ }
+
+ pub fn release(
+ tsfn: *mut TsFn,
+ mode: napi_threadsafe_function_release_mode,
+ ) -> napi_status {
+ let tsfn = unsafe { &mut *tsfn };
+
+ let result = tsfn.thread_count.fetch_update(
+ Ordering::Relaxed,
+ Ordering::Relaxed,
+ |x| {
+ if x == 0 {
+ None
+ } else {
+ Some(x - 1)
+ }
+ },
+ );
+
+ if result.is_err() {
+ return napi_invalid_arg;
+ }
+
+ if (result == Ok(1) || mode == napi_tsfn_abort)
+ && tsfn
+ .is_closing
+ .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
+ .is_ok()
+ {
+ let tsfnptr = SendPtr(tsfn);
+ // drop must be queued in order to preserve ordering consistent
+ // with Node.js and so that the finalizer runs on the main thread.
+ tsfn.sender.spawn(move |_| {
+ let tsfn = unsafe { Box::from_raw(tsfnptr.take() as *mut TsFn) };
+ drop(tsfn);
+ });
+ }
+
+ napi_ok
+ }
+
+ pub fn ref_(&self) -> napi_status {
+ if self
+ .is_ref
+ .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
+ .is_ok()
+ {
+ let env = unsafe { &mut *self.env };
+ env.threadsafe_function_ref();
+ }
+ napi_ok
+ }
+
+ pub fn unref(&self) -> napi_status {
+ if self
+ .is_ref
+ .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
+ .is_ok()
+ {
+ let env = unsafe { &mut *self.env };
+ env.threadsafe_function_unref();
+ }
+
+ napi_ok
+ }
+
+ pub fn call(
+ &self,
+ data: *mut c_void,
+ _mode: napi_threadsafe_function_call_mode,
+ ) -> napi_status {
+ // TODO:
+ // if self.max_queue_size > 0 && queued >= self.max_queue_size {
+ // if mode == napi_tsfn_blocking {
+ // wait somehow
+ // } else {
+ // return napi_queue_full;
+ // }
+ // }
+
+ if self.is_closing.load(Ordering::SeqCst) {
+ return napi_closing;
+ }
+
+ let is_closed = self.is_closed.clone();
+ let tsfn = SendPtr(self);
+ let data = SendPtr(data);
+ let context = SendPtr(self.context);
+ let call_js_cb = self.call_js_cb;
+ self.sender.spawn(move |scope: &mut v8::HandleScope| {
+ let data = data.take();
+
+ // if is_closed then tsfn is freed, don't read from it.
+ if is_closed.load(Ordering::Relaxed) {
+ unsafe {
+ call_js_cb(
+ std::ptr::null_mut(),
+ None::<v8::Local<v8::Value>>.into(),
+ context.take() as _,
+ data as _,
+ );
+ }
+ } else {
+ let tsfn = tsfn.take();
+
+ let tsfn = unsafe { &*tsfn };
+
+ let func = tsfn.func.as_ref().map(|f| v8::Local::new(scope, f));
+
+ unsafe {
+ (tsfn.call_js_cb)(
+ tsfn.env as _,
+ func.into(),
+ tsfn.context,
+ data as _,
+ );
+ }
+ }
+ });
+
+ napi_ok
+ }
+}
+
+#[napi_sym]
+#[allow(clippy::too_many_arguments)]
+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,
+ call_js_cb: Option<napi_threadsafe_function_call_js>,
+ result: *mut napi_threadsafe_function,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, async_resource_name);
+ if initial_thread_count == 0 {
+ return napi_set_last_error(env, napi_invalid_arg);
+ }
+ check_arg!(env, result);
+
+ let func = if let Some(value) = *func {
+ let Ok(func) = v8::Local::<v8::Function>::try_from(value) else {
+ return napi_set_last_error(env, napi_function_expected);
+ };
+ Some(v8::Global::new(&mut env.scope(), func))
+ } else {
+ check_arg!(env, call_js_cb);
+ None
+ };
+
+ let resource = if let Some(v) = *async_resource {
+ let Some(resource) = v.to_object(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_object_expected);
+ };
+ resource
+ } else {
+ v8::Object::new(&mut env.scope())
+ };
+ let resource = v8::Global::new(&mut env.scope(), resource);
+
+ let Some(resource_name) =
+ async_resource_name.and_then(|v| v.to_string(&mut env.scope()))
+ else {
+ return napi_set_last_error(env, napi_string_expected);
+ };
+ let resource_name = resource_name.to_rust_string_lossy(&mut env.scope());
+
+ let mut tsfn = Box::new(TsFn {
+ env,
+ func,
+ _max_queue_size: max_queue_size,
+ thread_count: AtomicUsize::new(initial_thread_count),
+ thread_finalize_data,
+ thread_finalize_cb,
+ context,
+ call_js_cb: call_js_cb.unwrap_or(default_call_js_cb),
+ _resource: resource,
+ _resource_name: resource_name,
+ is_closing: AtomicBool::new(false),
+ is_closed: Arc::new(AtomicBool::new(false)),
+ is_ref: AtomicBool::new(false),
+ sender: env.async_work_sender.clone(),
+ });
+
+ tsfn.ref_();
+
+ unsafe {
+ *result = Box::into_raw(tsfn) as _;
+ }
+
+ napi_clear_last_error(env)
+}
+
+/// Maybe called from any thread.
+#[napi_sym]
+fn napi_get_threadsafe_function_context(
+ func: napi_threadsafe_function,
+ result: *mut *const c_void,
+) -> napi_status {
+ assert!(!func.is_null());
+ let tsfn = unsafe { &*(func as *const TsFn) };
+ unsafe {
+ *result = tsfn.context;
+ }
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_call_threadsafe_function(
+ func: napi_threadsafe_function,
+ data: *mut c_void,
+ is_blocking: napi_threadsafe_function_call_mode,
+) -> napi_status {
+ assert!(!func.is_null());
+ let tsfn = unsafe { &*(func as *mut TsFn) };
+ tsfn.call(data, is_blocking)
+}
+
+#[napi_sym]
+fn napi_acquire_threadsafe_function(
+ tsfn: napi_threadsafe_function,
+) -> napi_status {
+ assert!(!tsfn.is_null());
+ let tsfn = unsafe { &*(tsfn as *mut TsFn) };
+ tsfn.acquire()
+}
+
+#[napi_sym]
+fn napi_release_threadsafe_function(
+ tsfn: napi_threadsafe_function,
+ mode: napi_threadsafe_function_release_mode,
+) -> napi_status {
+ assert!(!tsfn.is_null());
+ TsFn::release(tsfn as _, mode)
+}
+
+#[napi_sym]
+fn napi_unref_threadsafe_function(
+ _env: &mut Env,
+ func: napi_threadsafe_function,
+) -> napi_status {
+ assert!(!func.is_null());
+ let tsfn = unsafe { &*(func as *mut TsFn) };
+ tsfn.unref()
+}
+
+#[napi_sym]
+fn napi_ref_threadsafe_function(
+ _env: &mut Env,
+ func: napi_threadsafe_function,
+) -> napi_status {
+ assert!(!func.is_null());
+ let tsfn = unsafe { &*(func as *mut TsFn) };
+ tsfn.ref_()
+}
+
+#[napi_sym]
+fn node_api_get_module_file_name(
+ env: *mut Env,
+ result: *mut *const c_char,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = env.shared().filename.as_ptr() as _;
+ }
+
+ napi_clear_last_error(env)
+}
diff --git a/cli/napi/sym/lib.rs b/cli/napi/sym/lib.rs
index 33f039b5f..e2826306b 100644
--- a/cli/napi/sym/lib.rs
+++ b/cli/napi/sym/lib.rs
@@ -20,17 +20,12 @@ pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream {
let name = &func.sig.ident;
assert!(
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! {
- // SAFETY: it's an NAPI function.
- #[no_mangle]
- pub unsafe extern "C" fn #name #generics (#inputs) -> napi_status {
- #block
- }
+ crate::napi_wrap! {
+ #func
+ }
})
}
diff --git a/cli/napi/sym/symbol_exports.json b/cli/napi/sym/symbol_exports.json
index ba1bba67a..64b548d49 100644
--- a/cli/napi/sym/symbol_exports.json
+++ b/cli/napi/sym/symbol_exports.json
@@ -36,6 +36,7 @@
"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",
@@ -76,6 +77,7 @@
"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",
@@ -139,10 +141,17 @@
"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"
+ "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"
]
}
diff --git a/cli/napi/threadsafe_functions.rs b/cli/napi/threadsafe_functions.rs
deleted file mode 100644
index 2ab488627..000000000
--- a/cli/napi/threadsafe_functions.rs
+++ /dev/null
@@ -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()
-}
diff --git a/cli/napi/util.rs b/cli/napi/util.rs
index edf109460..cd3ef14a2 100644
--- a/cli/napi/util.rs
+++ b/cli/napi/util.rs
@@ -1,8 +1,292 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
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 {
// SAFETY: Thanks to the null pointer optimization, NonNull<T> and Option<NonNull<T>> are guaranteed
// to have the same size and alignment.
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
+ }
+ };
+}
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>()
diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js
index b0e4be89b..10e89b339 100644
--- a/ext/node/polyfills/01_require.js
+++ b/ext/node/polyfills/01_require.js
@@ -1103,7 +1103,12 @@ Module._extensions[".node"] = function (module, filename) {
if (filename.endsWith("fsevents.node")) {
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) {
diff --git a/tests/napi/init_test.js b/tests/napi/init_test.js
index 5f2507876..9db99d8a0 100644
--- a/tests/napi/init_test.js
+++ b/tests/napi/init_test.js
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { Buffer } from "node:buffer";
import { assert, libSuffix } from "./common.js";
const ops = Deno[Deno.internal].core.ops;
@@ -8,7 +9,7 @@ Deno.test("ctr initialization (napi_module_register)", {
ignore: Deno.build.os == "windows",
}, function () {
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(typeof obj === "object");
});
diff --git a/tests/napi/object_wrap_test.js b/tests/napi/object_wrap_test.js
index f79fd08f8..de6391fb1 100644
--- a/tests/napi/object_wrap_test.js
+++ b/tests/napi/object_wrap_test.js
@@ -1,5 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+import { Buffer } from "node:buffer";
import { assert, assertEquals, loadTestLibrary } from "./common.js";
const objectWrap = loadTestLibrary();
@@ -30,7 +31,7 @@ Deno.test("napi external finalizer", function () {
Deno.test("napi external buffer", function () {
let buf = objectWrap.test_external_buffer();
- assertEquals(buf, new Uint8Array([1, 2, 3]));
+ assertEquals(buf, new Buffer([1, 2, 3]));
buf = null;
});
diff --git a/tests/napi/src/object_wrap.rs b/tests/napi/src/object_wrap.rs
index d04107cf0..8c29caec5 100644
--- a/tests/napi/src/object_wrap.rs
+++ b/tests/napi/src/object_wrap.rs
@@ -11,7 +11,6 @@ use std::ptr;
pub struct NapiObject {
counter: i32,
- _wrapper: napi_ref,
}
impl NapiObject {
@@ -33,18 +32,14 @@ impl NapiObject {
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,
- _wrapper: wrapper,
- });
+ let obj = Box::new(Self { counter: value });
assert_napi_ok!(napi_wrap(
env,
this,
Box::into_raw(obj) as *mut c_void,
None,
ptr::null_mut(),
- &mut wrapper,
+ ptr::null_mut(),
));
return this;
diff --git a/tools/util.js b/tools/util.js
index 1497a2887..251aaa1fa 100644
--- a/tools/util.js
+++ b/tools/util.js
@@ -31,11 +31,17 @@ async function getFilesFromGit(baseDir, args) {
throw new Error("gitLsFiles failed");
}
- const files = output.split("\0").filter((line) => line.length > 0).map(
- (filePath) => {
- return Deno.realPathSync(join(baseDir, filePath));
- },
- );
+ const files = output
+ .split("\0")
+ .filter((line) => line.length > 0)
+ .map((filePath) => {
+ try {
+ return Deno.realPathSync(join(baseDir, filePath));
+ } catch {
+ return null;
+ }
+ })
+ .filter((filePath) => filePath !== null);
return files;
}