summaryrefslogtreecommitdiff
path: root/ext/napi
diff options
context:
space:
mode:
authorsnek <snek@deno.com>2024-10-24 09:13:54 +0200
committerGitHub <noreply@github.com>2024-10-24 09:13:54 +0200
commit79a3ad2b950009f560641cea359d7deb6f7a61ac (patch)
treea775ff101d1513b24a8cc0afc80fdcbd4c6c9074 /ext/napi
parent27df42f659ae7b77968d31363ade89addb516ea1 (diff)
feat: support node-api in denort (#26389)
exposes node-api symbols in denort so that `deno compile` can run native addons.
Diffstat (limited to 'ext/napi')
-rw-r--r--ext/napi/Cargo.toml9
-rw-r--r--ext/napi/README.md114
-rw-r--r--ext/napi/build.rs22
-rw-r--r--ext/napi/generated_symbol_exports_list_linux.def1
-rw-r--r--ext/napi/generated_symbol_exports_list_macos.def160
-rw-r--r--ext/napi/generated_symbol_exports_list_windows.def162
-rw-r--r--ext/napi/js_native_api.rs3616
-rw-r--r--ext/napi/lib.rs43
-rw-r--r--ext/napi/node_api.rs1010
-rw-r--r--ext/napi/sym/Cargo.toml21
-rw-r--r--ext/napi/sym/README.md38
-rw-r--r--ext/napi/sym/lib.rs31
-rw-r--r--ext/napi/sym/symbol_exports.json164
-rw-r--r--ext/napi/util.rs287
-rw-r--r--ext/napi/uv.rs231
15 files changed, 5909 insertions, 0 deletions
diff --git a/ext/napi/Cargo.toml b/ext/napi/Cargo.toml
index ef2e41d57..ed39d96df 100644
--- a/ext/napi/Cargo.toml
+++ b/ext/napi/Cargo.toml
@@ -16,5 +16,14 @@ path = "lib.rs"
[dependencies]
deno_core.workspace = true
deno_permissions.workspace = true
+libc.workspace = true
libloading = { version = "0.7" }
+log.workspace = true
+napi_sym.workspace = true
thiserror.workspace = true
+
+[target.'cfg(windows)'.dependencies]
+windows-sys.workspace = true
+
+[dev-dependencies]
+libuv-sys-lite = "=1.48.2"
diff --git a/ext/napi/README.md b/ext/napi/README.md
index e69de29bb..b47929524 100644
--- a/ext/napi/README.md
+++ b/ext/napi/README.md
@@ -0,0 +1,114 @@
+# napi
+
+This directory contains source for Deno's Node-API implementation. It depends on
+`napi_sym` and `deno_napi`.
+
+Files are generally organized the same as in Node.js's implementation to ease in
+ensuring compatibility.
+
+## Adding a new function
+
+Add the symbol name to
+[`cli/napi_sym/symbol_exports.json`](../napi_sym/symbol_exports.json).
+
+```diff
+{
+ "symbols": [
+ ...
+ "napi_get_undefined",
+- "napi_get_null"
++ "napi_get_null",
++ "napi_get_boolean"
+ ]
+}
+```
+
+Determine where to place the implementation. `napi_get_boolean` is related to JS
+values so we will place it in `js_native_api.rs`. If something is not clear,
+just create a new file module.
+
+See [`napi_sym`](../napi_sym/) for writing the implementation:
+
+```rust
+#[napi_sym::napi_sym]
+fn napi_get_boolean(
+ env: *mut Env,
+ value: bool,
+ result: *mut napi_value,
+) -> Result {
+ // ...
+ Ok(())
+}
+```
+
+Update the generated symbol lists using the script:
+
+```
+deno run --allow-write tools/napi/generate_symbols_lists.js
+```
+
+Add a test in [`/tests/napi`](../../tests/napi/). You can also refer to Node.js
+test suite for Node-API.
+
+```js
+// tests/napi/boolean_test.js
+import { assertEquals, loadTestLibrary } from "./common.js";
+const lib = loadTestLibrary();
+Deno.test("napi get boolean", function () {
+ assertEquals(lib.test_get_boolean(true), true);
+ assertEquals(lib.test_get_boolean(false), false);
+});
+```
+
+```rust
+// tests/napi/src/boolean.rs
+
+use napi_sys::Status::napi_ok;
+use napi_sys::ValueType::napi_boolean;
+use napi_sys::*;
+
+extern "C" fn test_boolean(
+ env: napi_env,
+ info: napi_callback_info,
+) -> napi_value {
+ let (args, argc, _) = crate::get_callback_info!(env, info, 1);
+ assert_eq!(argc, 1);
+
+ let mut ty = -1;
+ assert!(unsafe { napi_typeof(env, args[0], &mut ty) } == napi_ok);
+ assert_eq!(ty, napi_boolean);
+
+ // Use napi_get_boolean here...
+
+ value
+}
+
+pub fn init(env: napi_env, exports: napi_value) {
+ let properties = &[crate::new_property!(env, "test_boolean\0", test_boolean)];
+
+ unsafe {
+ napi_define_properties(env, exports, properties.len(), properties.as_ptr())
+ };
+}
+```
+
+```diff
+// tests/napi/src/lib.rs
+
++ mod boolean;
+
+...
+
+#[no_mangle]
+unsafe extern "C" fn napi_register_module_v1(
+ env: napi_env,
+ exports: napi_value,
+) -> napi_value {
+ ...
++ boolean::init(env, exports);
+
+ exports
+}
+```
+
+Run the test using `cargo test -p tests/napi`.
diff --git a/ext/napi/build.rs b/ext/napi/build.rs
new file mode 100644
index 000000000..8705830a9
--- /dev/null
+++ b/ext/napi/build.rs
@@ -0,0 +1,22 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+fn main() {
+ let symbols_file_name = match std::env::consts::OS {
+ "android" | "freebsd" | "openbsd" => {
+ "generated_symbol_exports_list_linux.def".to_string()
+ }
+ os => format!("generated_symbol_exports_list_{}.def", os),
+ };
+ let symbols_path = std::path::Path::new(".")
+ .join(symbols_file_name)
+ .canonicalize()
+ .expect(
+ "Missing symbols list! Generate using tools/napi/generate_symbols_lists.js",
+ );
+
+ println!("cargo:rustc-rerun-if-changed={}", symbols_path.display());
+
+ let path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
+ .join("napi_symbol_path.txt");
+ std::fs::write(path, symbols_path.as_os_str().as_encoded_bytes()).unwrap();
+}
diff --git a/ext/napi/generated_symbol_exports_list_linux.def b/ext/napi/generated_symbol_exports_list_linux.def
new file mode 100644
index 000000000..614880ebf
--- /dev/null
+++ b/ext/napi/generated_symbol_exports_list_linux.def
@@ -0,0 +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"; "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"; "uv_mutex_init"; "uv_mutex_lock"; "uv_mutex_unlock"; "uv_mutex_destroy"; "uv_async_init"; "uv_async_send"; "uv_close"; }; \ No newline at end of file
diff --git a/ext/napi/generated_symbol_exports_list_macos.def b/ext/napi/generated_symbol_exports_list_macos.def
new file mode 100644
index 000000000..36b2f37fa
--- /dev/null
+++ b/ext/napi/generated_symbol_exports_list_macos.def
@@ -0,0 +1,160 @@
+_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
+_uv_mutex_init
+_uv_mutex_lock
+_uv_mutex_unlock
+_uv_mutex_destroy
+_uv_async_init
+_uv_async_send
+_uv_close \ No newline at end of file
diff --git a/ext/napi/generated_symbol_exports_list_windows.def b/ext/napi/generated_symbol_exports_list_windows.def
new file mode 100644
index 000000000..b7355112e
--- /dev/null
+++ b/ext/napi/generated_symbol_exports_list_windows.def
@@ -0,0 +1,162 @@
+LIBRARY
+EXPORTS
+ 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
+ uv_mutex_init
+ uv_mutex_lock
+ uv_mutex_unlock
+ uv_mutex_destroy
+ uv_async_init
+ uv_async_send
+ uv_close \ No newline at end of file
diff --git a/ext/napi/js_native_api.rs b/ext/napi/js_native_api.rs
new file mode 100644
index 000000000..53a12d6eb
--- /dev/null
+++ b/ext/napi/js_native_api.rs
@@ -0,0 +1,3616 @@
+// 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 crate::*;
+use libc::INT_MAX;
+
+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 crate::function::create_function;
+use crate::function::create_function_template;
+use crate::function::CallbackInfo;
+use napi_sym::napi_sym;
+use std::ptr::NonNull;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum ReferenceOwnership {
+ Runtime,
+ Userland,
+}
+
+enum ReferenceState {
+ Strong(v8::Global<v8::Value>),
+ Weak(v8::Weak<v8::Value>),
+}
+
+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,
+}
+
+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 { (*env).isolate() };
+
+ 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();
+ }
+
+ reference
+ }
+
+ fn ref_(&mut self) -> u32 {
+ self.ref_count += 1;
+ if self.ref_count == 1 {
+ self.set_strong();
+ }
+ self.ref_count
+ }
+
+ 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
+ }
+
+ fn reset(&mut self) {
+ self.finalize_cb = None;
+ self.finalize_data = std::ptr::null_mut();
+ self.finalize_hint = std::ptr::null_mut();
+ }
+
+ fn set_strong(&mut self) {
+ if let ReferenceState::Weak(w) = &self.state {
+ let isolate = unsafe { (*self.env).isolate() };
+ if let Some(g) = w.to_global(isolate) {
+ self.state = ReferenceState::Strong(g);
+ }
+ }
+ }
+
+ 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 { (*self.env).isolate() };
+ self.state =
+ ReferenceState::Weak(v8::Weak::with_finalizer(isolate, g, cb));
+ }
+ }
+
+ fn weak_callback(reference: *mut Reference) {
+ let reference = unsafe { &mut *reference };
+
+ let finalize_cb = reference.finalize_cb;
+ let finalize_data = reference.finalize_data;
+ let finalize_hint = reference.finalize_hint;
+ reference.reset();
+
+ // copy this value before the finalize callback, since
+ // it might free the reference (which would be a UAF)
+ let ownership = reference.ownership;
+ if let Some(finalize_cb) = finalize_cb {
+ unsafe {
+ finalize_cb(reference.env as _, finalize_data, finalize_hint);
+ }
+ }
+
+ if ownership == ReferenceOwnership::Runtime {
+ unsafe { drop(Reference::from_raw(reference)) }
+ }
+ }
+
+ fn into_raw(r: Box<Reference>) -> *mut Reference {
+ Box::into_raw(r)
+ }
+
+ 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]
+fn napi_get_last_error_info(
+ env: *mut Env,
+ result: *mut *const napi_extended_error_info,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ 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]
+fn napi_create_function<'s>(
+ env: &'s mut Env,
+ name: *const c_char,
+ length: usize,
+ cb: Option<napi_callback>,
+ cb_info: napi_callback_info,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ let env_ptr = env as *mut Env;
+ check_arg!(env, result);
+ 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(&mut env.scope(), env_ptr, name, cb.unwrap(), cb_info)
+ .into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+#[allow(clippy::too_many_arguments)]
+fn napi_define_class<'s>(
+ env: &'s mut Env,
+ utf8name: *const c_char,
+ length: usize,
+ constructor: Option<napi_callback>,
+ callback_data: *mut c_void,
+ property_count: usize,
+ properties: *const napi_property_descriptor,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ let env_ptr = env as *mut Env;
+ check_arg!(env, result);
+ check_arg!(env, constructor);
+
+ if property_count > 0 {
+ check_arg!(env, properties);
+ }
+
+ let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } {
+ Ok(string) => string,
+ Err(status) => return status,
+ };
+
+ let tpl = create_function_template(
+ &mut env.scope(),
+ env_ptr,
+ Some(name),
+ constructor.unwrap(),
+ 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;
+ }
+
+ let name = match unsafe { v8_name_from_property_descriptor(env_ptr, p) } {
+ Ok(name) => name,
+ Err(status) => return status,
+ };
+
+ let mut accessor_property = v8::PropertyAttribute::NONE;
+
+ 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;
+ }
+
+ if p.getter.is_some() || p.setter.is_some() {
+ let getter = p.getter.map(|g| {
+ create_function_template(&mut env.scope(), env_ptr, None, g, p.data)
+ });
+ let setter = p.setter.map(|s| {
+ create_function_template(&mut env.scope(), env_ptr, None, s, p.data)
+ });
+ if getter.is_some()
+ && setter.is_some()
+ && (p.attributes & napi_writable) == 0
+ {
+ accessor_property =
+ accessor_property | v8::PropertyAttribute::READ_ONLY;
+ }
+ let proto = tpl.prototype_template(&mut env.scope());
+ proto.set_accessor_property(name, getter, setter, accessor_property);
+ } else if let Some(method) = p.method {
+ let function = create_function_template(
+ &mut env.scope(),
+ env_ptr,
+ None,
+ method,
+ p.data,
+ );
+ let proto = tpl.prototype_template(&mut env.scope());
+ proto.set_with_attr(name, function.into(), accessor_property);
+ } else {
+ let proto = tpl.prototype_template(&mut env.scope());
+ if (p.attributes & napi_writable) == 0 {
+ accessor_property =
+ accessor_property | v8::PropertyAttribute::READ_ONLY;
+ }
+ proto.set_with_attr(name, p.value.unwrap().into(), accessor_property);
+ }
+ }
+
+ 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]
+fn napi_get_property_names(
+ env: *mut Env,
+ object: napi_value,
+ result: *mut napi_value,
+) -> napi_status {
+ 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,
+ )
+ }
+}
+
+#[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_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]
+fn napi_set_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ value: napi_value,
+) -> napi_status {
+ 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]
+fn napi_has_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ 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(has) = object.has(scope, key.unwrap()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = has;
+ }
+
+ napi_ok
+}
+
+#[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_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]
+fn napi_delete_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ 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]
+fn napi_has_own_property(
+ env: &mut Env,
+ object: napi_value,
+ key: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ 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 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]
+fn napi_has_named_property<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ utf8name: *const c_char,
+ result: *mut bool,
+) -> napi_status {
+ let env_ptr = env as *mut Env;
+ check_arg!(env, result);
+
+ 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
+}
+
+#[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 {
+ check_arg!(env, value);
+ 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 value = value.unwrap();
+
+ if !object
+ .set(&mut env.scope(), key.into(), value)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+
+ napi_ok
+}
+
+#[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_arg!(env, result);
+ 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]
+fn napi_set_element<'s>(
+ env: &'s mut Env,
+ object: napi_value<'s>,
+ index: u32,
+ value: napi_value<'s>,
+) -> napi_status {
+ 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]
+fn napi_has_element(
+ env: &mut Env,
+ object: napi_value,
+ index: u32,
+ result: *mut bool,
+) -> napi_status {
+ 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(has) = object.has_index(scope, index) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = has;
+ }
+
+ napi_ok
+}
+
+#[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);
+
+ 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_index(scope, index) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = value.into();
+ }
+
+ napi_ok
+}
+
+#[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 {
+ *result = deleted;
+ }
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_define_properties(
+ env: &mut Env,
+ object: napi_value,
+ property_count: usize,
+ properties: *const napi_property_descriptor,
+) -> napi_status {
+ let env_ptr = env as *mut Env;
+
+ 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 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 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 let Some(getter) =
+ property.getter
+ {
+ create_function(&mut env.scope(), env_ptr, None, getter, property.data)
+ .into()
+ } else {
+ v8::undefined(scope).into()
+ };
+ let local_setter: v8::Local<v8::Value> = if let Some(setter) =
+ property.setter
+ {
+ create_function(&mut env.scope(), env_ptr, None, 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 let Some(method) = property.method {
+ let method: v8::Local<v8::Value> = {
+ let function = create_function(
+ &mut env.scope(),
+ env_ptr,
+ None,
+ 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;
+ }
+ }
+ }
+ }
+
+ napi_ok
+}
+
+#[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;
+ };
+
+ if !object
+ .set_integrity_level(scope, v8::IntegrityLevel::Frozen)
+ .unwrap_or(false)
+ {
+ return napi_generic_failure;
+ }
+
+ napi_ok
+}
+
+#[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,
+ value: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ 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
+ }
+ 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());
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_get_prototype<'s>(
+ env: &'s mut Env,
+ object: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ 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(proto) = object.get_prototype(scope) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = proto.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_create_object(
+ env_ptr: *mut Env,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Object::new(&mut env.scope()).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_create_array(
+ env_ptr: *mut Env,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Array::new(&mut env.scope(), 0).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_create_array_with_length(
+ env_ptr: *mut Env,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = v8::Array::new(&mut env.scope(), length as _).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_create_string_latin1(
+ env_ptr: *mut Env,
+ string: *const c_char,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ 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 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]
+pub(crate) fn napi_create_string_utf8(
+ env_ptr: *mut Env,
+ string: *const c_char,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ 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 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_utf8(
+ &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]
+fn napi_create_string_utf16(
+ env_ptr: *mut Env,
+ string: *const u16,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ 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 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 Some(string) = v8::String::new_from_two_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]
+fn node_api_create_external_string_latin1(
+ env_ptr: *mut Env,
+ string: *const c_char,
+ length: usize,
+ nogc_finalize_callback: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+ result: *mut napi_value,
+ copied: *mut bool,
+) -> napi_status {
+ let status =
+ unsafe { napi_create_string_latin1(env_ptr, string, length, result) };
+
+ if status == napi_ok {
+ unsafe {
+ *copied = true;
+ }
+
+ if let Some(finalize) = nogc_finalize_callback {
+ unsafe {
+ finalize(env_ptr as napi_env, string as *mut c_void, finalize_hint);
+ }
+ }
+ }
+
+ status
+}
+
+#[napi_sym]
+fn node_api_create_external_string_utf16(
+ env_ptr: *mut Env,
+ string: *const u16,
+ length: usize,
+ nogc_finalize_callback: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+ result: *mut napi_value,
+ copied: *mut bool,
+) -> napi_status {
+ let status =
+ unsafe { napi_create_string_utf16(env_ptr, string, length, result) };
+
+ if status == napi_ok {
+ unsafe {
+ *copied = true;
+ }
+
+ if let Some(finalize) = nogc_finalize_callback {
+ unsafe {
+ finalize(env_ptr as napi_env, string as *mut c_void, finalize_hint);
+ }
+ }
+ }
+
+ status
+}
+
+#[napi_sym]
+fn node_api_create_property_key_utf16(
+ env_ptr: *mut Env,
+ string: *const u16,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ 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 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 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]
+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 {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ let description = if let Some(d) = *description {
+ let Some(d) = d.to_string(&mut env.scope()) else {
+ return napi_set_last_error(env, napi_string_expected);
+ };
+ Some(d)
+ } else {
+ None
+ };
+
+ unsafe {
+ *result = v8::Symbol::new(&mut env.scope(), description).into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn node_api_symbol_for(
+ env: *mut Env,
+ utf8description: *const c_char,
+ length: usize,
+ result: *mut napi_value,
+) -> napi_status {
+ {
+ 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();
+ }
+ }
+
+ 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);
+ };
+
+ 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 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]
+fn napi_get_new_target(
+ env: *mut Env,
+ 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<'s>(
+ env: &'s mut Env,
+ recv: napi_value<'s>,
+ func: napi_value<'s>,
+ argc: usize,
+ argv: *const napi_value<'s>,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ 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_function_expected;
+ };
+
+ let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else {
+ return napi_generic_failure;
+ };
+
+ if !result.is_null() {
+ unsafe {
+ *result = v.into();
+ }
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_get_global(env_ptr: *mut Env, result: *mut napi_value) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ let global = v8::Local::new(&mut env.scope(), &env.global);
+ unsafe {
+ *result = global.into();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[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);
+ };
+
+ 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]
+fn napi_get_value_bigint_int64(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut i64,
+ lossless: *mut bool,
+) -> napi_status {
+ 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();
+
+ unsafe {
+ *result = result_;
+ *lossless = lossless_;
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_bigint_uint64(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut u64,
+ lossless: *mut bool,
+) -> napi_status {
+ 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();
+
+ unsafe {
+ *result = result_;
+ *lossless = lossless_;
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_bigint_words(
+ env_ptr: *mut Env,
+ value: napi_value,
+ sign_bit: *mut i32,
+ word_count: *mut usize,
+ words: *mut u64,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, word_count);
+
+ 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 = bigint.word_count();
+ } else {
+ check_arg!(env, sign_bit);
+ check_arg!(env, 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();
+ unsafe {
+ *sign_bit = sign as i32;
+ }
+ }
+
+ unsafe {
+ *word_count = word_count_int;
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_bool(
+ env_ptr: *mut Env,
+ value: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ 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);
+ };
+
+ unsafe {
+ *result = boolean.is_true();
+ }
+
+ return napi_clear_last_error(env_ptr);
+}
+
+#[napi_sym]
+fn napi_get_value_string_latin1(
+ env_ptr: *mut Env,
+ value: napi_value,
+ buf: *mut c_char,
+ bufsize: usize,
+ result: *mut usize,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+
+ 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() {
+ check_arg!(env, result);
+ unsafe {
+ *result = value.length();
+ }
+ } else if bufsize != 0 {
+ 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,
+ );
+ unsafe {
+ buf.add(copied).write(0);
+ }
+ if !result.is_null() {
+ unsafe {
+ *result = copied;
+ }
+ }
+ } else if !result.is_null() {
+ unsafe {
+ *result = 0;
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_get_value_string_utf8(
+ env_ptr: *mut Env,
+ value: napi_value,
+ buf: *mut u8,
+ bufsize: usize,
+ result: *mut usize,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+
+ 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() {
+ check_arg!(env, result);
+ unsafe {
+ *result = value.utf8_length(env.isolate());
+ }
+ } else if bufsize != 0 {
+ 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::REPLACE_INVALID_UTF8
+ | v8::WriteOptions::NO_NULL_TERMINATION,
+ );
+ unsafe {
+ buf.add(copied).write(0);
+ }
+ if !result.is_null() {
+ unsafe {
+ *result = copied;
+ }
+ }
+ } else if !result.is_null() {
+ unsafe {
+ *result = 0;
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_get_value_string_utf16(
+ env_ptr: *mut Env,
+ value: napi_value,
+ buf: *mut u16,
+ bufsize: usize,
+ result: *mut usize,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+
+ 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() {
+ check_arg!(env, result);
+ unsafe {
+ *result = value.length();
+ }
+ } else if bufsize != 0 {
+ 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,
+ );
+ unsafe {
+ buf.add(copied).write(0);
+ }
+ if !result.is_null() {
+ unsafe {
+ *result = copied;
+ }
+ }
+ } else if !result.is_null() {
+ unsafe {
+ *result = 0;
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_coerce_to_bool<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ 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]
+fn napi_coerce_to_number<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(coerced) = value.unwrap().to_number(&mut env.scope()) else {
+ return napi_number_expected;
+ };
+
+ unsafe {
+ *result = coerced.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_coerce_to_object<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(coerced) = value.unwrap().to_object(&mut env.scope()) else {
+ return napi_object_expected;
+ };
+
+ unsafe {
+ *result = coerced.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_coerce_to_string<'s>(
+ env: &'s mut Env,
+ value: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let Some(coerced) = value.unwrap().to_string(&mut env.scope()) else {
+ return napi_string_expected;
+ };
+
+ unsafe {
+ *result = coerced.into();
+ }
+
+ napi_ok
+}
+
+#[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 {
+ check_arg!(env, js_object);
+ let env_ptr = env as *mut Env;
+
+ 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);
+ }
+
+ 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
+}
+
+fn unwrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+ keep: bool,
+) -> napi_status {
+ check_arg!(env, obj);
+ if keep {
+ check_arg!(env, result);
+ }
+
+ 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 Ok(external) = v8::Local::<v8::External>::try_from(val) else {
+ return napi_invalid_arg;
+ };
+
+ let reference = external.value() as *mut Reference;
+ let reference = unsafe { &mut *reference };
+
+ if !result.is_null() {
+ unsafe {
+ *result = reference.finalize_data;
+ }
+ }
+
+ if !keep {
+ assert!(obj
+ .delete_private(&mut env.scope(), napi_wrap)
+ .unwrap_or(false));
+ unsafe { Reference::remove(reference) };
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_unwrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+) -> napi_status {
+ unwrap(env, obj, result, true)
+}
+
+#[napi_sym]
+fn napi_remove_wrap(
+ env: &mut Env,
+ obj: napi_value,
+ result: *mut *mut c_void,
+) -> napi_status {
+ unwrap(env, obj, result, false)
+}
+
+struct ExternalWrapper {
+ data: *mut c_void,
+ type_tag: Option<napi_type_tag>,
+}
+
+#[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);
+
+ let wrapper = Box::new(ExternalWrapper {
+ data,
+ type_tag: None,
+ });
+
+ let wrapper = Box::into_raw(wrapper);
+ let external = v8::External::new(&mut env.scope(), wrapper as _);
+
+ if let Some(finalize_cb) = finalize_cb {
+ Reference::into_raw(Reference::new(
+ env_ptr,
+ external.into(),
+ 0,
+ ReferenceOwnership::Runtime,
+ Some(finalize_cb),
+ data,
+ finalize_hint,
+ ));
+ }
+
+ unsafe {
+ *result = external.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_type_tag_object(
+ env: &mut Env,
+ object_or_external: napi_value,
+ type_tag: *const napi_type_tag,
+) -> napi_status {
+ 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 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);
+
+ 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_generic_failure;
+ };
+
+ 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,
+ };
+ };
+ return napi_ok;
+ }
+
+ 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 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
+ };
+ unsafe {
+ *result = pass;
+ }
+ }
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_get_value_external(
+ env: *mut Env,
+ value: napi_value,
+ result: *mut *mut c_void,
+) -> napi_status {
+ 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]
+fn napi_create_reference(
+ env: *mut Env,
+ value: napi_value,
+ initial_refcount: u32,
+ result: *mut napi_ref,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, result);
+
+ let value = value.unwrap();
+
+ let reference = Reference::new(
+ env,
+ value,
+ initial_refcount,
+ ReferenceOwnership::Userland,
+ None,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ );
+
+ let ptr = Reference::into_raw(reference);
+
+ unsafe {
+ *result = ptr as _;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[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]
+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 reference = unsafe { &mut *(ref_ as *mut Reference) };
+
+ let count = reference.ref_();
+
+ if !result.is_null() {
+ unsafe {
+ *result = count;
+ }
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_reference_unref(
+ env: *mut Env,
+ ref_: napi_ref,
+ result: *mut u32,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, ref_);
+
+ 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]
+fn napi_get_reference_value(
+ env_ptr: *mut Env,
+ ref_: napi_ref,
+ result: *mut napi_value,
+) -> napi_status {
+ 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]
+fn napi_open_handle_scope(
+ env: *mut Env,
+ _result: *mut napi_handle_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_close_handle_scope(
+ env: *mut Env,
+ _scope: napi_handle_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_open_escapable_handle_scope(
+ env: *mut Env,
+ _result: *mut napi_escapable_handle_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_close_escapable_handle_scope(
+ env: *mut Env,
+ _scope: napi_escapable_handle_scope,
+) -> napi_status {
+ let env = check_env!(env);
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_escape_handle<'s>(
+ env: *mut Env,
+ _scope: napi_escapable_handle_scope,
+ escapee: napi_value<'s>,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ let env = check_env!(env);
+
+ unsafe {
+ *result = escapee;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[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);
+
+ 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_ok
+}
+
+#[napi_sym]
+fn napi_instanceof(
+ env: &mut Env,
+ object: napi_value,
+ constructor: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ 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;
+ };
+
+ if !ctor.is_function() {
+ unsafe {
+ napi_throw_type_error(
+ env,
+ c"ERR_NAPI_CONS_FUNCTION".as_ptr(),
+ c"Constructor must be a function".as_ptr(),
+ );
+ }
+ return napi_function_expected;
+ }
+
+ let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = res;
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_exception_pending(
+ env_ptr: *mut Env,
+ result: *mut bool,
+) -> napi_status {
+ 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]
+fn napi_get_and_clear_last_exception(
+ env_ptr: *mut Env,
+ result: *mut napi_value,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, result);
+
+ 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]
+fn napi_is_arraybuffer(
+ 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_array_buffer();
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[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_arg!(env, result);
+
+ let buffer = v8::ArrayBuffer::new(&mut env.scope(), len);
+
+ if !data.is_null() {
+ unsafe {
+ *data = get_array_buffer_ptr(buffer);
+ }
+ }
+
+ unsafe {
+ *result = buffer.into();
+ }
+
+ napi_ok
+}
+
+#[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_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]
+fn napi_get_arraybuffer_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(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]
+fn napi_is_typedarray(
+ 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_typed_array();
+ }
+
+ napi_ok
+}
+
+#[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_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]
+fn napi_get_typedarray_info(
+ env_ptr: *mut Env,
+ typedarray: napi_value,
+ type_: *mut napi_typedarray_type,
+ length: *mut usize,
+ data: *mut *mut c_void,
+ arraybuffer: *mut napi_value,
+ byte_offset: *mut usize,
+) -> napi_status {
+ 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() {
+ 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() {
+ unsafe {
+ *length = array.length();
+ }
+ }
+
+ if !data.is_null() {
+ unsafe {
+ *data = array.data();
+ }
+ }
+
+ if !arraybuffer.is_null() {
+ let buf = array.buffer(&mut env.scope()).unwrap();
+ unsafe {
+ *arraybuffer = buf.into();
+ }
+ }
+
+ if !byte_offset.is_null() {
+ unsafe {
+ *byte_offset = array.byte_offset();
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_create_dataview<'s>(
+ env: &'s mut Env,
+ byte_length: usize,
+ arraybuffer: napi_value<'s>,
+ byte_offset: usize,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, arraybuffer);
+ check_arg!(env, result);
+
+ let Some(buffer) =
+ arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
+ };
+
+ if byte_length + byte_offset > buffer.byte_length() {
+ unsafe {
+ return napi_throw_range_error(
+ env,
+ c"ERR_NAPI_INVALID_DATAVIEW_ARGS".as_ptr(),
+ c"byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in".as_ptr(),
+ );
+ }
+ }
+
+ let dataview =
+ v8::DataView::new(&mut env.scope(), buffer, byte_offset, byte_length);
+
+ unsafe {
+ *result = dataview.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_dataview(
+ 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_data_view();
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[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 {
+ let env = check_env!(env_ptr);
+ check_arg!(env, dataview);
+
+ let Some(array) =
+ dataview.and_then(|v| v8::Local::<v8::DataView>::try_from(v).ok())
+ else {
+ return napi_invalid_arg;
+ };
+
+ if !byte_length.is_null() {
+ unsafe {
+ *byte_length = array.byte_length();
+ }
+ }
+
+ if !arraybuffer.is_null() {
+ let Some(buffer) = array.buffer(&mut env.scope()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *arraybuffer = buffer.into();
+ }
+ }
+
+ if !data.is_null() {
+ unsafe {
+ *data = array.data();
+ }
+ }
+
+ if !byte_offset.is_null() {
+ unsafe {
+ *byte_offset = array.byte_offset();
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn napi_get_version(env: *mut Env, result: *mut u32) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, result);
+
+ unsafe {
+ *result = NAPI_VERSION;
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_create_promise<'s>(
+ env: &'s mut Env,
+ deferred: *mut napi_deferred,
+ promise: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, deferred);
+ check_arg!(env, promise);
+
+ let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap();
+
+ let global = v8::Global::new(&mut env.scope(), resolver);
+ let global_ptr = global.into_raw().as_ptr() as napi_deferred;
+
+ let p = resolver.get_promise(&mut env.scope());
+
+ unsafe {
+ *deferred = global_ptr;
+ }
+
+ unsafe {
+ *promise = p.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_resolve_deferred(
+ env: &mut Env,
+ deferred: napi_deferred,
+ result: napi_value,
+) -> napi_status {
+ check_arg!(env, result);
+ check_arg!(env, deferred);
+
+ // Make sure microtasks don't run and call back into JS
+ env
+ .scope()
+ .set_microtasks_policy(v8::MicrotasksPolicy::Explicit);
+
+ let deferred_ptr =
+ unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) };
+ let global = unsafe { v8::Global::from_raw(env.isolate(), deferred_ptr) };
+ let resolver = v8::Local::new(&mut env.scope(), global);
+
+ let success = resolver
+ .resolve(&mut env.scope(), result.unwrap())
+ .unwrap_or(false);
+
+ // Restore policy
+ env
+ .scope()
+ .set_microtasks_policy(v8::MicrotasksPolicy::Auto);
+
+ if success {
+ napi_ok
+ } else {
+ napi_generic_failure
+ }
+}
+
+#[napi_sym]
+fn napi_reject_deferred(
+ env: &mut Env,
+ deferred: napi_deferred,
+ result: napi_value,
+) -> napi_status {
+ check_arg!(env, result);
+ check_arg!(env, deferred);
+
+ let deferred_ptr =
+ unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) };
+ let global = unsafe { v8::Global::from_raw(env.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;
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_promise(
+ env: *mut Env,
+ value: napi_value,
+ is_promise: *mut bool,
+) -> napi_status {
+ 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]
+fn napi_create_date<'s>(
+ env: &'s mut Env,
+ time: f64,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, result);
+
+ let Some(date) = v8::Date::new(&mut env.scope(), time) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = date.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_date(
+ env: *mut Env,
+ value: napi_value,
+ is_date: *mut bool,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, value);
+ check_arg!(env, is_date);
+
+ unsafe {
+ *is_date = value.unwrap().is_date();
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_date_value(
+ env: &mut Env,
+ value: napi_value,
+ result: *mut f64,
+) -> napi_status {
+ 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]
+fn napi_run_script<'s>(
+ env: &'s mut Env,
+ script: napi_value,
+ result: *mut napi_value<'s>,
+) -> napi_status {
+ check_arg!(env, script);
+ check_arg!(env, result);
+
+ let Some(script) =
+ script.and_then(|v| v8::Local::<v8::String>::try_from(v).ok())
+ else {
+ return napi_string_expected;
+ };
+
+ let Some(script) = v8::Script::compile(&mut env.scope(), script, None) else {
+ return napi_generic_failure;
+ };
+
+ let Some(rv) = script.run(&mut env.scope()) else {
+ return napi_generic_failure;
+ };
+
+ unsafe {
+ *result = rv.into();
+ }
+
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_add_finalizer(
+ env_ptr: *mut Env,
+ value: napi_value,
+ finalize_data: *mut c_void,
+ finalize_cb: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+ result: *mut napi_ref,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, value);
+ check_arg!(env, finalize_cb);
+
+ 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);
+ };
+
+ let ownership = if result.is_null() {
+ ReferenceOwnership::Runtime
+ } else {
+ ReferenceOwnership::Userland
+ };
+ let reference = Reference::new(
+ env,
+ value.into(),
+ 0,
+ ownership,
+ finalize_cb,
+ finalize_data,
+ finalize_hint,
+ );
+
+ if !result.is_null() {
+ unsafe {
+ *result = Reference::into_raw(reference) as _;
+ }
+ }
+
+ napi_clear_last_error(env_ptr)
+}
+
+#[napi_sym]
+fn node_api_post_finalizer(
+ env: *mut Env,
+ _finalize_cb: napi_finalize,
+ _finalize_data: *mut c_void,
+ _finalize_hint: *mut c_void,
+) -> napi_status {
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_adjust_external_memory(
+ env: *mut Env,
+ change_in_bytes: i64,
+ adjusted_value: *mut i64,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, adjusted_value);
+
+ unsafe {
+ *adjusted_value = env
+ .isolate()
+ .adjust_amount_of_external_allocated_memory(change_in_bytes);
+ }
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_set_instance_data(
+ env: *mut Env,
+ data: *mut c_void,
+ finalize_cb: Option<napi_finalize>,
+ finalize_hint: *mut c_void,
+) -> napi_status {
+ let env = check_env!(env);
+
+ env.shared_mut().instance_data = Some(InstanceData {
+ data,
+ finalize_cb,
+ finalize_hint,
+ });
+
+ napi_clear_last_error(env)
+}
+
+#[napi_sym]
+fn napi_get_instance_data(
+ env: *mut Env,
+ data: *mut *mut c_void,
+) -> napi_status {
+ let env = check_env!(env);
+ check_arg!(env, data);
+
+ let instance_data = match &env.shared().instance_data {
+ Some(v) => v.data,
+ None => std::ptr::null_mut(),
+ };
+
+ unsafe { *data = instance_data };
+
+ napi_clear_last_error(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 Some(ab) =
+ value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ else {
+ return napi_set_last_error(env, napi_arraybuffer_expected);
+ };
+
+ if !ab.is_detachable() {
+ return napi_set_last_error(env, napi_detachable_arraybuffer_expected);
+ }
+
+ // Expected to crash for None.
+ ab.detach(None).unwrap();
+
+ napi_clear_last_error(env);
+ napi_ok
+}
+
+#[napi_sym]
+fn napi_is_detached_arraybuffer(
+ env_ptr: *mut Env,
+ arraybuffer: napi_value,
+ result: *mut bool,
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, arraybuffer);
+ check_arg!(env, result);
+
+ let is_detached = match arraybuffer
+ .and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok())
+ {
+ Some(ab) => ab.was_detached(),
+ None => false,
+ };
+
+ unsafe {
+ *result = is_detached;
+ }
+
+ napi_clear_last_error(env)
+}
diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs
index 0b2b3eb5e..0d41bb40f 100644
--- a/ext/napi/lib.rs
+++ b/ext/napi/lib.rs
@@ -5,6 +5,22 @@
#![allow(clippy::undocumented_unsafe_blocks)]
#![deny(clippy::missing_safety_doc)]
+//! Symbols to be exported are now defined in this JSON file.
+//! The `#[napi_sym]` macro checks for missing entries and panics.
+//!
+//! `./tools/napi/generate_symbols_list.js` is used to generate the LINK `cli/exports.def` on Windows,
+//! which is also checked into git.
+//!
+//! To add a new napi function:
+//! 1. Place `#[napi_sym]` on top of your implementation.
+//! 2. Add the function's identifier to this JSON list.
+//! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `ext/napi/generated_symbol_exports_list_*.def`.
+
+pub mod js_native_api;
+pub mod node_api;
+pub mod util;
+pub mod uv;
+
use core::ptr::NonNull;
use deno_core::op2;
use deno_core::parking_lot::RwLock;
@@ -631,3 +647,30 @@ where
Ok(exports)
}
+
+#[allow(clippy::print_stdout)]
+pub fn print_linker_flags(name: &str) {
+ let symbols_path =
+ include_str!(concat!(env!("OUT_DIR"), "/napi_symbol_path.txt"));
+
+ #[cfg(target_os = "windows")]
+ println!("cargo:rustc-link-arg-bin={name}=/DEF:{}", symbols_path);
+
+ #[cfg(target_os = "macos")]
+ println!(
+ "cargo:rustc-link-arg-bin={name}=-Wl,-exported_symbols_list,{}",
+ symbols_path,
+ );
+
+ #[cfg(target_os = "linux")]
+ println!(
+ "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
+ symbols_path,
+ );
+
+ #[cfg(target_os = "android")]
+ println!(
+ "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
+ symbols_path,
+ );
+}
diff --git a/ext/napi/node_api.rs b/ext/napi/node_api.rs
new file mode 100644
index 000000000..186ae42c4
--- /dev/null
+++ b/ext/napi/node_api.rs
@@ -0,0 +1,1010 @@
+// 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 crate::*;
+use deno_core::parking_lot::Condvar;
+use deno_core::parking_lot::Mutex;
+use deno_core::V8CrossThreadTaskSpawner;
+use napi_sym::napi_sym;
+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();
+
+ env.add_cleanup_hook(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();
+
+ env.remove_cleanup_hook(fun, arg);
+
+ napi_ok
+}
+
+struct AsyncCleanupHandle {
+ env: *mut Env,
+ hook: napi_async_cleanup_hook,
+ data: *mut c_void,
+}
+
+unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) {
+ unsafe {
+ let handle = Box::<AsyncCleanupHandle>::from_raw(arg as _);
+ (handle.hook)(arg, handle.data);
+ }
+}
+
+#[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::new(AsyncCleanupHandle {
+ env,
+ hook,
+ data: arg,
+ })) as *mut c_void;
+
+ env.add_cleanup_hook(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 { Box::<AsyncCleanupHandle>::from_raw(remove_handle as _) };
+
+ let env = unsafe { &mut *handle.env };
+
+ env.remove_cleanup_hook(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 = v8::Local::new(&mut env.scope(), &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<'s>,
+ 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 =
+ v8::Local::new(&mut env.scope(), &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);
+ }
+ }
+
+ 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 =
+ v8::Local::new(&mut env.scope(), &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 =
+ v8::Local::new(&mut env.scope(), &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);
+ 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 =
+ v8::Local::new(&mut env.scope(), &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);
+
+ // NB: Any TypedArray instance seems to be accepted by this function
+ // in Node.js.
+ 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);
+ };
+
+ 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]
+pub(crate) 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]
+pub(crate) 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_ptr: *mut Env,
+ uv_loop: *mut *mut (),
+) -> napi_status {
+ let env = check_env!(env_ptr);
+ check_arg!(env, uv_loop);
+ unsafe {
+ *uv_loop = env_ptr.cast();
+ }
+ 0
+}
+
+#[napi_sym]
+pub(crate) 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,
+ queue_size: Mutex<usize>,
+ queue_cond: Condvar,
+ 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, self.context);
+ }
+ }
+ }
+}
+
+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()
+ {
+ tsfn.queue_cond.notify_all();
+ 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 {
+ if self.is_closing.load(Ordering::SeqCst) {
+ return napi_closing;
+ }
+
+ if self.max_queue_size > 0 {
+ let mut queue_size = self.queue_size.lock();
+ while *queue_size >= self.max_queue_size {
+ if mode == napi_tsfn_blocking {
+ self.queue_cond.wait(&mut queue_size);
+
+ if self.is_closing.load(Ordering::SeqCst) {
+ return napi_closing;
+ }
+ } else {
+ return napi_queue_full;
+ }
+ }
+ *queue_size += 1;
+ }
+
+ 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 };
+
+ if tsfn.max_queue_size > 0 {
+ let mut queue_size = tsfn.queue_size.lock();
+ let size = *queue_size;
+ *queue_size -= 1;
+ if size == tsfn.max_queue_size {
+ tsfn.queue_cond.notify_one();
+ }
+ }
+
+ 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 tsfn = Box::new(TsFn {
+ env,
+ func,
+ max_queue_size,
+ queue_size: Mutex::new(0),
+ queue_cond: Condvar::new(),
+ 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/ext/napi/sym/Cargo.toml b/ext/napi/sym/Cargo.toml
new file mode 100644
index 000000000..7c9bf208c
--- /dev/null
+++ b/ext/napi/sym/Cargo.toml
@@ -0,0 +1,21 @@
+# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+[package]
+name = "napi_sym"
+version = "0.103.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+readme = "README.md"
+repository.workspace = true
+description = "proc macro for writing N-API symbols"
+
+[lib]
+path = "./lib.rs"
+proc-macro = true
+
+[dependencies]
+quote.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+syn.workspace = true
diff --git a/ext/napi/sym/README.md b/ext/napi/sym/README.md
new file mode 100644
index 000000000..66eb4bff2
--- /dev/null
+++ b/ext/napi/sym/README.md
@@ -0,0 +1,38 @@
+# napi_sym
+
+A proc_macro for Deno's Node-API implementation. It does the following things:
+
+- Marks the symbol as `#[no_mangle]` and rewrites it as
+ `unsafe extern "C" $name`.
+- Asserts that the function symbol is present in
+ [`symbol_exports.json`](./symbol_exports.json).
+- Maps `deno_napi::Result` to raw `napi_result`.
+
+```rust
+use deno_napi::napi_value;
+use deno_napi::Env;
+use deno_napi::Error;
+use deno_napi::Result;
+
+#[napi_sym::napi_sym]
+fn napi_get_boolean(
+ env: *mut Env,
+ value: bool,
+ result: *mut napi_value,
+) -> Result {
+ let _env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?;
+ // *result = ...
+ Ok(())
+}
+```
+
+### `symbol_exports.json`
+
+A file containing the symbols that need to be put into the executable's dynamic
+symbol table at link-time.
+
+This is done using `/DEF:` on Windows, `-exported_symbol,_` on macOS and
+`--export-dynamic-symbol=` on Linux. See [`cli/build.rs`](../build.rs).
+
+On Windows, you need to generate the `.def` file by running
+[`tools/napi/generate_symbols_lists.js`](../../tools/napi/generate_symbols_lists.js).
diff --git a/ext/napi/sym/lib.rs b/ext/napi/sym/lib.rs
new file mode 100644
index 000000000..e2826306b
--- /dev/null
+++ b/ext/napi/sym/lib.rs
@@ -0,0 +1,31 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use proc_macro::TokenStream;
+use quote::quote;
+use serde::Deserialize;
+
+static NAPI_EXPORTS: &str = include_str!("./symbol_exports.json");
+
+#[derive(Deserialize)]
+struct SymbolExports {
+ pub symbols: Vec<String>,
+}
+
+#[proc_macro_attribute]
+pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let func = syn::parse::<syn::ItemFn>(item).expect("expected a function");
+
+ let exports: SymbolExports =
+ serde_json::from_str(NAPI_EXPORTS).expect("failed to parse exports");
+ let name = &func.sig.ident;
+ assert!(
+ exports.symbols.contains(&name.to_string()),
+ "cli/napi/sym/symbol_exports.json is out of sync!"
+ );
+
+ TokenStream::from(quote! {
+ crate::napi_wrap! {
+ #func
+ }
+ })
+}
diff --git a/ext/napi/sym/symbol_exports.json b/ext/napi/sym/symbol_exports.json
new file mode 100644
index 000000000..00946b8ed
--- /dev/null
+++ b/ext/napi/sym/symbol_exports.json
@@ -0,0 +1,164 @@
+{
+ "symbols": [
+ "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",
+ "uv_mutex_init",
+ "uv_mutex_lock",
+ "uv_mutex_unlock",
+ "uv_mutex_destroy",
+ "uv_async_init",
+ "uv_async_send",
+ "uv_close"
+ ]
+}
diff --git a/ext/napi/util.rs b/ext/napi/util.rs
new file mode 100644
index 000000000..21e9d433a
--- /dev/null
+++ b/ext/napi/util.rs
@@ -0,0 +1,287 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+use crate::*;
+use libc::INT_MAX;
+
+#[repr(transparent)]
+pub(crate) 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 c_void {
+ match ab.data() {
+ Some(p) => p.as_ptr(),
+ None => std::ptr::null_mut(),
+ }
+}
+
+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(crate) 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(crate) 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::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::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()
+ }
+}
+
+#[macro_export]
+macro_rules! check_arg {
+ ($env: expr, $ptr: expr) => {
+ $crate::return_status_if_false!(
+ $env,
+ !$crate::util::Nullable::is_null(&$ptr),
+ napi_invalid_arg
+ );
+ };
+}
+
+#[macro_export]
+macro_rules! napi_wrap {
+ ( $( # [ $attr:meta ] )* $vis:vis fn $name:ident $( < $( $x:lifetime ),* > )? ( $env:ident : & $( $lt:lifetime )? mut Env $( , $ident:ident : $ty:ty )* $(,)? ) -> napi_status $body:block ) => {
+ $( # [ $attr ] )*
+ #[no_mangle]
+ $vis 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::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::util::napi_set_last_error(env_ptr, napi_pending_exception);
+ }
+
+ if result != napi_ok {
+ return $crate::util::napi_set_last_error(env_ptr, result);
+ }
+
+ return result;
+ }
+ };
+
+ ( $( # [ $attr:meta ] )* $vis:vis fn $name:ident $( < $( $x:lifetime ),* > )? ( $( $ident:ident : $ty:ty ),* $(,)? ) -> napi_status $body:block ) => {
+ $( # [ $attr ] )*
+ #[no_mangle]
+ $vis 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/uv.rs b/ext/napi/uv.rs
new file mode 100644
index 000000000..6f728a92b
--- /dev/null
+++ b/ext/napi/uv.rs
@@ -0,0 +1,231 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::*;
+use deno_core::parking_lot::Mutex;
+use std::mem::MaybeUninit;
+use std::ptr::addr_of_mut;
+
+#[allow(clippy::print_stderr)]
+fn assert_ok(res: c_int) -> c_int {
+ if res != 0 {
+ eprintln!("bad result in uv polyfill: {res}");
+ // don't panic because that might unwind into
+ // c/c++
+ std::process::abort();
+ }
+ res
+}
+
+use js_native_api::napi_create_string_utf8;
+use node_api::napi_create_async_work;
+use node_api::napi_delete_async_work;
+use node_api::napi_queue_async_work;
+use std::ffi::c_int;
+
+const UV_MUTEX_SIZE: usize = {
+ #[cfg(unix)]
+ {
+ std::mem::size_of::<libc::pthread_mutex_t>()
+ }
+ #[cfg(windows)]
+ {
+ std::mem::size_of::<windows_sys::Win32::System::Threading::CRITICAL_SECTION>(
+ )
+ }
+};
+
+#[repr(C)]
+struct uv_mutex_t {
+ mutex: Mutex<()>,
+ _padding: [MaybeUninit<usize>; const {
+ (UV_MUTEX_SIZE - size_of::<Mutex<()>>()) / size_of::<usize>()
+ }],
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_init(lock: *mut uv_mutex_t) -> c_int {
+ unsafe {
+ addr_of_mut!((*lock).mutex).write(Mutex::new(()));
+ 0
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_lock(lock: *mut uv_mutex_t) {
+ unsafe {
+ let guard = (*lock).mutex.lock();
+ // forget the guard so it doesn't unlock when it goes out of scope.
+ // we're going to unlock it manually
+ std::mem::forget(guard);
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_unlock(lock: *mut uv_mutex_t) {
+ unsafe {
+ (*lock).mutex.force_unlock();
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_mutex_destroy(_lock: *mut uv_mutex_t) {
+ // no cleanup required
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+#[allow(dead_code)]
+enum uv_handle_type {
+ UV_UNKNOWN_HANDLE = 0,
+ UV_ASYNC,
+ UV_CHECK,
+ UV_FS_EVENT,
+ UV_FS_POLL,
+ UV_HANDLE,
+ UV_IDLE,
+ UV_NAMED_PIPE,
+ UV_POLL,
+ UV_PREPARE,
+ UV_PROCESS,
+ UV_STREAM,
+ UV_TCP,
+ UV_TIMER,
+ UV_TTY,
+ UV_UDP,
+ UV_SIGNAL,
+ UV_FILE,
+ UV_HANDLE_TYPE_MAX,
+}
+
+const UV_HANDLE_SIZE: usize = 96;
+
+#[repr(C)]
+struct uv_handle_t {
+ // public members
+ pub data: *mut c_void,
+ pub r#loop: *mut uv_loop_t,
+ pub r#type: uv_handle_type,
+
+ _padding: [MaybeUninit<usize>; const {
+ (UV_HANDLE_SIZE
+ - size_of::<*mut c_void>()
+ - size_of::<*mut uv_loop_t>()
+ - size_of::<uv_handle_type>())
+ / size_of::<usize>()
+ }],
+}
+
+#[cfg(unix)]
+const UV_ASYNC_SIZE: usize = 128;
+
+#[cfg(windows)]
+const UV_ASYNC_SIZE: usize = 224;
+
+#[repr(C)]
+struct uv_async_t {
+ // public members
+ pub data: *mut c_void,
+ pub r#loop: *mut uv_loop_t,
+ pub r#type: uv_handle_type,
+ // private
+ async_cb: uv_async_cb,
+ work: napi_async_work,
+ _padding: [MaybeUninit<usize>; const {
+ (UV_ASYNC_SIZE
+ - size_of::<*mut c_void>()
+ - size_of::<*mut uv_loop_t>()
+ - size_of::<uv_handle_type>()
+ - size_of::<uv_async_cb>()
+ - size_of::<napi_async_work>())
+ / size_of::<usize>()
+ }],
+}
+
+type uv_loop_t = Env;
+type uv_async_cb = extern "C" fn(handle: *mut uv_async_t);
+#[no_mangle]
+unsafe extern "C" fn uv_async_init(
+ r#loop: *mut uv_loop_t,
+ // probably uninitialized
+ r#async: *mut uv_async_t,
+ async_cb: uv_async_cb,
+) -> c_int {
+ unsafe {
+ addr_of_mut!((*r#async).r#loop).write(r#loop);
+ addr_of_mut!((*r#async).r#type).write(uv_handle_type::UV_ASYNC);
+ addr_of_mut!((*r#async).async_cb).write(async_cb);
+
+ let mut resource_name: MaybeUninit<napi_value> = MaybeUninit::uninit();
+ assert_ok(napi_create_string_utf8(
+ r#loop,
+ c"uv_async".as_ptr(),
+ usize::MAX,
+ resource_name.as_mut_ptr(),
+ ));
+ let resource_name = resource_name.assume_init();
+
+ let res = napi_create_async_work(
+ r#loop,
+ None::<v8::Local<'static, v8::Value>>.into(),
+ resource_name,
+ Some(async_exec_wrap),
+ None,
+ r#async.cast(),
+ addr_of_mut!((*r#async).work),
+ );
+ -res
+ }
+}
+
+#[no_mangle]
+unsafe extern "C" fn uv_async_send(handle: *mut uv_async_t) -> c_int {
+ unsafe { -napi_queue_async_work((*handle).r#loop, (*handle).work) }
+}
+
+type uv_close_cb = unsafe extern "C" fn(*mut uv_handle_t);
+
+#[no_mangle]
+unsafe extern "C" fn uv_close(handle: *mut uv_handle_t, close: uv_close_cb) {
+ unsafe {
+ if handle.is_null() {
+ close(handle);
+ return;
+ }
+ if let uv_handle_type::UV_ASYNC = (*handle).r#type {
+ let handle: *mut uv_async_t = handle.cast();
+ napi_delete_async_work((*handle).r#loop, (*handle).work);
+ }
+ close(handle);
+ }
+}
+
+unsafe extern "C" fn async_exec_wrap(_env: napi_env, data: *mut c_void) {
+ let data: *mut uv_async_t = data.cast();
+ unsafe {
+ ((*data).async_cb)(data);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn sizes() {
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_mutex_t>(),
+ UV_MUTEX_SIZE
+ );
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_handle_t>(),
+ UV_HANDLE_SIZE
+ );
+ assert_eq!(
+ std::mem::size_of::<libuv_sys_lite::uv_async_t>(),
+ UV_ASYNC_SIZE
+ );
+ assert_eq!(std::mem::size_of::<uv_mutex_t>(), UV_MUTEX_SIZE);
+ assert_eq!(std::mem::size_of::<uv_handle_t>(), UV_HANDLE_SIZE);
+ assert_eq!(std::mem::size_of::<uv_async_t>(), UV_ASYNC_SIZE);
+ }
+}