diff --git a/cli/napi/README.md b/cli/napi/README.md index ec637e1bd2..7b359ac6ec 100644 --- a/cli/napi/README.md +++ b/cli/napi/README.md @@ -3,11 +3,8 @@ This directory contains source for Deno's Node-API implementation. It depends on `napi_sym` and `deno_napi`. -- [`async.rs`](./async.rs) - Asynchronous work related functions. -- [`env.rs`](./env.rs) - Environment related functions. -- [`js_native_api.rs`](./js_native_api.rs) - V8/JS related functions. -- [`thread_safe_function.rs`](./threadsafe_functions.rs) - Thread safe function - related functions. +Files are generally organized the same as in Node.js's implementation to ease in +ensuring compatibility. ## Adding a new function diff --git a/cli/napi/async.rs b/cli/napi/async.rs deleted file mode 100644 index 115aa742d7..0000000000 --- a/cli/napi/async.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_runtime::deno_napi::*; - -use crate::check_env; -use crate::napi::threadsafe_functions::SendPtr; - -#[repr(C)] -pub struct AsyncWork { - pub data: *mut c_void, - pub execute: napi_async_execute_callback, - pub complete: napi_async_complete_callback, -} - -unsafe impl Send for AsyncWork {} -unsafe impl Sync for AsyncWork {} - -#[napi_sym::napi_sym] -fn napi_create_async_work( - _env: *mut Env, - _async_resource: napi_value, - _async_resource_name: napi_value, - execute: napi_async_execute_callback, - complete: napi_async_complete_callback, - data: *mut c_void, - result: *mut napi_async_work, -) -> napi_status { - let mut work = AsyncWork { - data, - execute, - complete, - }; - let work_box = Box::new(work); - *result = transmute::<*mut AsyncWork, _>(Box::into_raw(work_box)); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_cancel_async_work( - _env: &mut Env, - _async_work: napi_async_work, -) -> napi_status { - napi_ok -} - -/// Frees a previously allocated work object. -#[napi_sym::napi_sym] -fn napi_delete_async_work( - _env: &mut Env, - work: napi_async_work, -) -> napi_status { - let work = Box::from_raw(work as *mut AsyncWork); - drop(work); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_queue_async_work( - env_ptr: *mut Env, - work: napi_async_work, -) -> napi_status { - let work: &AsyncWork = &*(work as *const AsyncWork); - let Some(env) = env_ptr.as_mut() else { - return napi_invalid_arg; - }; - - let send_env = SendPtr(env_ptr); - - #[inline(always)] - fn do_work(ptr: SendPtr, work: &AsyncWork) { - // SAFETY: This is a valid async work queue call and it runs on the event loop thread - unsafe { - (work.execute)(ptr.0 as napi_env, work.data); - (work.complete)(ptr.0 as napi_env, napi_ok, work.data); - } - } - - env.add_async_work(move || do_work(send_env, work)); - - napi_ok -} - -// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops. -#[napi_sym::napi_sym] -fn napi_async_init( - env: *mut Env, - _async_resource: napi_value, - _async_resource_name: napi_value, - result: *mut *mut (), -) -> napi_status { - check_env!(env); - *result = ptr::null_mut(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_async_destroy(env: *mut Env, async_context: *mut ()) -> napi_status { - check_env!(env); - assert!(async_context.is_null()); - napi_ok -} diff --git a/cli/napi/env.rs b/cli/napi/env.rs deleted file mode 100644 index 34948cde39..0000000000 --- a/cli/napi/env.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_runtime::deno_napi::*; -use std::os::raw::c_char; - -/// # Safety -/// -/// It's an N-API symbol -#[no_mangle] -pub unsafe extern "C" fn napi_fatal_error( - location: *const c_char, - location_len: isize, - message: *const c_char, - message_len: isize, -) -> ! { - let location = if location.is_null() { - None - } else { - Some(if location_len < 0 { - std::ffi::CStr::from_ptr(location).to_str().unwrap() - } else { - let slice = std::slice::from_raw_parts( - location as *const u8, - location_len as usize, - ); - std::str::from_utf8(slice).unwrap() - }) - }; - let message = if message_len < 0 { - std::ffi::CStr::from_ptr(message).to_str().unwrap() - } else { - let slice = - std::slice::from_raw_parts(message as *const u8, message_len as usize); - std::str::from_utf8(slice).unwrap() - }; - panic!( - "Fatal exception triggered by napi_fatal_error!\nLocation: {location:?}\n{message}" - ); -} - -// napi-3 - -#[napi_sym::napi_sym] -fn napi_fatal_exception(env: *mut Env, value: napi_value) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - let value = transmute::>(value); - let error = value.to_rust_string_lossy(&mut env.scope()); - panic!("Fatal exception triggered by napi_fatal_exception!\n{error}"); -} - -#[napi_sym::napi_sym] -fn napi_add_env_cleanup_hook( - env: *mut Env, - hook: extern "C" fn(*const c_void), - data: *const c_void, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - { - let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); - if env_cleanup_hooks - .iter() - .any(|pair| pair.0 == hook && pair.1 == data) - { - panic!("Cleanup hook with this data already registered"); - } - env_cleanup_hooks.push((hook, data)); - } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_remove_env_cleanup_hook( - env: *mut Env, - hook: extern "C" fn(*const c_void), - data: *const c_void, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - { - let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); - // Hooks are supposed to be removed in LIFO order - let maybe_index = env_cleanup_hooks - .iter() - .rposition(|&pair| pair.0 == hook && pair.1 == data); - - if let Some(index) = maybe_index { - env_cleanup_hooks.remove(index); - } else { - panic!("Cleanup hook with this data not found"); - } - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_open_callback_scope( - _env: *mut Env, - _resource_object: napi_value, - _context: napi_value, - _result: *mut napi_callback_scope, -) -> napi_status { - // we open scope automatically when it's needed - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_close_callback_scope( - _env: *mut Env, - _scope: napi_callback_scope, -) -> napi_status { - // we close scope automatically when it's needed - napi_ok -} - -#[napi_sym::napi_sym] -fn node_api_get_module_file_name( - env: *mut Env, - result: *mut *const c_char, -) -> napi_status { - let Some(env) = env.as_mut() else { - return napi_invalid_arg; - }; - - let shared = env.shared(); - *result = shared.filename; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_module_register(module: *const NapiModule) -> napi_status { - MODULE_TO_REGISTER.with(|cell| { - let mut slot = cell.borrow_mut(); - let prev = slot.replace(module); - assert!(prev.is_none()); - }); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_uv_event_loop( - _env: *mut Env, - uv_loop: *mut *mut (), -) -> napi_status { - // There is no uv_loop in Deno - *uv_loop = std::ptr::null_mut(); - napi_ok -} - -const NODE_VERSION: napi_node_version = napi_node_version { - major: 18, - minor: 13, - patch: 0, - release: "Deno\0".as_ptr() as *const c_char, -}; - -#[napi_sym::napi_sym] -fn napi_get_node_version( - env: *mut Env, - result: *mut *const napi_node_version, -) -> napi_status { - crate::check_env!(env); - crate::check_arg!(env, result); - - *result = &NODE_VERSION as *const napi_node_version; - napi_ok -} diff --git a/cli/napi/generated_symbol_exports_list_linux.def b/cli/napi/generated_symbol_exports_list_linux.def index eceac0a438..06e94f05bb 100644 --- a/cli/napi/generated_symbol_exports_list_linux.def +++ b/cli/napi/generated_symbol_exports_list_linux.def @@ -1 +1 @@ -{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; }; \ No newline at end of file +{ "node_api_create_syntax_error"; "napi_make_callback"; "napi_has_named_property"; "napi_async_destroy"; "napi_coerce_to_object"; "napi_get_arraybuffer_info"; "napi_detach_arraybuffer"; "napi_get_undefined"; "napi_reference_unref"; "napi_fatal_error"; "napi_open_callback_scope"; "napi_close_callback_scope"; "napi_get_value_uint32"; "napi_create_function"; "napi_create_arraybuffer"; "napi_get_value_int64"; "napi_get_all_property_names"; "napi_resolve_deferred"; "napi_is_detached_arraybuffer"; "napi_create_string_utf8"; "napi_create_threadsafe_function"; "node_api_throw_syntax_error"; "napi_create_bigint_int64"; "napi_wrap"; "napi_set_property"; "napi_get_value_bigint_int64"; "napi_open_handle_scope"; "napi_create_error"; "napi_create_buffer"; "napi_cancel_async_work"; "napi_is_exception_pending"; "napi_acquire_threadsafe_function"; "napi_create_external"; "napi_get_threadsafe_function_context"; "napi_get_null"; "napi_create_string_utf16"; "node_api_create_external_string_utf16"; "napi_get_value_bigint_uint64"; "napi_module_register"; "napi_is_typedarray"; "napi_create_external_buffer"; "napi_get_new_target"; "napi_get_instance_data"; "napi_close_handle_scope"; "napi_get_value_string_utf16"; "napi_get_property_names"; "napi_is_arraybuffer"; "napi_get_cb_info"; "napi_define_properties"; "napi_add_env_cleanup_hook"; "node_api_get_module_file_name"; "napi_get_node_version"; "napi_create_int64"; "napi_create_double"; "napi_get_and_clear_last_exception"; "napi_create_reference"; "napi_get_typedarray_info"; "napi_call_threadsafe_function"; "napi_get_last_error_info"; "napi_create_array_with_length"; "napi_coerce_to_number"; "napi_get_global"; "napi_is_error"; "napi_set_instance_data"; "napi_create_typedarray"; "napi_throw_type_error"; "napi_has_property"; "napi_get_value_external"; "napi_create_range_error"; "napi_typeof"; "napi_ref_threadsafe_function"; "napi_create_bigint_uint64"; "napi_get_prototype"; "napi_adjust_external_memory"; "napi_release_threadsafe_function"; "napi_delete_async_work"; "napi_create_string_latin1"; "node_api_create_external_string_latin1"; "napi_is_array"; "napi_unref_threadsafe_function"; "napi_throw_error"; "napi_has_own_property"; "napi_get_reference_value"; "napi_remove_env_cleanup_hook"; "napi_get_value_string_utf8"; "napi_is_promise"; "napi_get_boolean"; "napi_run_script"; "napi_get_element"; "napi_get_named_property"; "napi_get_buffer_info"; "napi_get_value_bool"; "napi_reference_ref"; "napi_create_object"; "napi_create_promise"; "napi_create_int32"; "napi_escape_handle"; "napi_open_escapable_handle_scope"; "napi_throw"; "napi_get_value_double"; "napi_set_named_property"; "napi_call_function"; "napi_create_date"; "napi_object_freeze"; "napi_get_uv_event_loop"; "napi_get_value_string_latin1"; "napi_reject_deferred"; "napi_add_finalizer"; "napi_create_array"; "napi_delete_reference"; "napi_get_date_value"; "napi_create_dataview"; "napi_get_version"; "napi_define_class"; "napi_is_date"; "napi_remove_wrap"; "napi_delete_property"; "napi_instanceof"; "napi_create_buffer_copy"; "napi_delete_element"; "napi_object_seal"; "napi_queue_async_work"; "napi_get_value_bigint_words"; "napi_is_buffer"; "napi_get_array_length"; "napi_get_property"; "napi_new_instance"; "napi_set_element"; "napi_create_bigint_words"; "napi_strict_equals"; "napi_is_dataview"; "napi_close_escapable_handle_scope"; "napi_get_dataview_info"; "napi_get_value_int32"; "napi_unwrap"; "napi_throw_range_error"; "napi_coerce_to_bool"; "napi_create_uint32"; "napi_has_element"; "napi_create_external_arraybuffer"; "napi_create_symbol"; "node_api_symbol_for"; "napi_coerce_to_string"; "napi_create_type_error"; "napi_fatal_exception"; "napi_create_async_work"; "napi_async_init"; "node_api_create_property_key_utf16"; "napi_type_tag_object"; "napi_check_object_type_tag"; "node_api_post_finalizer"; "napi_add_async_cleanup_hook"; "napi_remove_async_cleanup_hook"; }; \ No newline at end of file diff --git a/cli/napi/generated_symbol_exports_list_macos.def b/cli/napi/generated_symbol_exports_list_macos.def index be69d297fe..cac7100c6f 100644 --- a/cli/napi/generated_symbol_exports_list_macos.def +++ b/cli/napi/generated_symbol_exports_list_macos.def @@ -34,6 +34,7 @@ _napi_create_external _napi_get_threadsafe_function_context _napi_get_null _napi_create_string_utf16 +_node_api_create_external_string_utf16 _napi_get_value_bigint_uint64 _napi_module_register _napi_is_typedarray @@ -74,6 +75,7 @@ _napi_adjust_external_memory _napi_release_threadsafe_function _napi_delete_async_work _napi_create_string_latin1 +_node_api_create_external_string_latin1 _napi_is_array _napi_unref_threadsafe_function _napi_throw_error @@ -137,8 +139,15 @@ _napi_create_uint32 _napi_has_element _napi_create_external_arraybuffer _napi_create_symbol +_node_api_symbol_for _napi_coerce_to_string _napi_create_type_error _napi_fatal_exception _napi_create_async_work -_napi_async_init \ No newline at end of file +_napi_async_init +_node_api_create_property_key_utf16 +_napi_type_tag_object +_napi_check_object_type_tag +_node_api_post_finalizer +_napi_add_async_cleanup_hook +_napi_remove_async_cleanup_hook \ No newline at end of file diff --git a/cli/napi/generated_symbol_exports_list_windows.def b/cli/napi/generated_symbol_exports_list_windows.def index 45f5d3aabd..5386b46e54 100644 --- a/cli/napi/generated_symbol_exports_list_windows.def +++ b/cli/napi/generated_symbol_exports_list_windows.def @@ -36,6 +36,7 @@ EXPORTS napi_get_threadsafe_function_context napi_get_null napi_create_string_utf16 + node_api_create_external_string_utf16 napi_get_value_bigint_uint64 napi_module_register napi_is_typedarray @@ -76,6 +77,7 @@ EXPORTS napi_release_threadsafe_function napi_delete_async_work napi_create_string_latin1 + node_api_create_external_string_latin1 napi_is_array napi_unref_threadsafe_function napi_throw_error @@ -139,8 +141,15 @@ EXPORTS napi_has_element napi_create_external_arraybuffer napi_create_symbol + node_api_symbol_for napi_coerce_to_string napi_create_type_error napi_fatal_exception napi_create_async_work - napi_async_init \ No newline at end of file + napi_async_init + node_api_create_property_key_utf16 + napi_type_tag_object + napi_check_object_type_tag + node_api_post_finalizer + napi_add_async_cleanup_hook + napi_remove_async_cleanup_hook \ No newline at end of file diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs index 428c4a04a0..cbce113dc0 100644 --- a/cli/napi/js_native_api.rs +++ b/cli/napi/js_native_api.rs @@ -1,670 +1,197 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. #![allow(non_upper_case_globals)] +#![deny(unsafe_op_in_unsafe_fn)] + +const NAPI_VERSION: u32 = 9; use deno_runtime::deno_napi::*; use libc::INT_MAX; -use v8::BackingStore; -use v8::UniqueRef; +use super::util::check_new_from_utf8; +use super::util::check_new_from_utf8_len; use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::v8_name_from_property_descriptor; +use crate::check_arg; +use crate::check_env; use deno_runtime::deno_napi::function::create_function; use deno_runtime::deno_napi::function::create_function_template; use deno_runtime::deno_napi::function::CallbackInfo; +use napi_sym::napi_sym; use std::ptr::NonNull; -#[macro_export] -macro_rules! check_env { - ($env: expr) => { - if $env.is_null() { - return napi_invalid_arg; - } - }; +#[derive(Debug, Clone, Copy, PartialEq)] +enum ReferenceOwnership { + Runtime, + Userland, } -#[inline] -unsafe fn napi_value_unchecked(val: napi_value) -> v8::Local { - transmute::>(val) +enum ReferenceState { + Strong(v8::Global), + Weak(v8::Weak), } -#[macro_export] -macro_rules! return_error_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return Err( - $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ) - .into(), - ); - } - }; -} - -#[macro_export] -macro_rules! return_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ); - } - }; -} - -fn check_new_from_utf8_len<'s>( +struct Reference { env: *mut Env, - str_: *const c_char, - len: usize, -) -> Result, napi_status> { - return_error_status_if_false!( - env, - (len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _, - napi_invalid_arg - ); - return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg); - let string = if len == NAPI_AUTO_LENGTH { - let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str(); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - } else { - let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) }; - let result = std::str::from_utf8(string); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - }; - let result = { - let env = unsafe { &mut *env }; - v8::String::new(&mut env.scope(), string) - }; - return_error_status_if_false!(env, result.is_some(), napi_generic_failure); - Ok(result.unwrap()) -} - -#[inline] -fn check_new_from_utf8<'s>( - env: *mut Env, - str_: *const c_char, -) -> Result, napi_status> { - check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) -} - -#[macro_export] -macro_rules! status_call { - ($call: expr) => { - let status = $call; - if status != napi_ok { - return status; - } - }; -} - -// Macro to check napi arguments. -// If nullptr, return napi_invalid_arg. -#[macro_export] -macro_rules! check_arg { - ($env: expr, $ptr: expr) => { - $crate::return_status_if_false!($env, !$ptr.is_null(), napi_invalid_arg); - }; -} - -macro_rules! check_arg_option { - ($env: expr, $opt: expr) => { - $crate::return_status_if_false!($env, $opt.is_some(), napi_invalid_arg); - }; -} - -fn napi_clear_last_error(env: *mut Env) { - let env = unsafe { &mut *env }; - env.last_error.error_code = napi_ok; - env.last_error.engine_error_code = 0; - env.last_error.engine_reserved = std::ptr::null_mut(); - env.last_error.error_message = std::ptr::null_mut(); -} - -pub(crate) fn napi_set_last_error( - env: *mut Env, - error_code: napi_status, - engine_error_code: i32, - engine_reserved: *mut c_void, -) -> napi_status { - let env = unsafe { &mut *env }; - env.last_error.error_code = error_code; - env.last_error.engine_error_code = engine_error_code; - env.last_error.engine_reserved = engine_reserved; - error_code -} - -/// Returns napi_value that represents a new JavaScript Array. -#[napi_sym::napi_sym] -fn napi_create_array(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), 0).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_array_with_length( - env: *mut Env, - len: i32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), len).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_arraybuffer( - env: *mut Env, - len: usize, - data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); - } - - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_bigint_int64( - env: *mut Env, - value: i64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_bigint_uint64( - env: *mut Env, - value: u64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_bigint_words( - env: *mut Env, - sign_bit: bool, - word_count: usize, - words: *const u64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, words); - let env = unsafe { &mut *env }; - 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, - std::slice::from_raw_parts(words, word_count), - ) { - Some(value) => { - *result = value.into(); - } - None => { - return napi_invalid_arg; - } - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_buffer( - env: *mut Env, - len: usize, - data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); - } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_buffer_copy( - env: *mut Env, - len: usize, - data: *mut u8, - result_data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - let ptr = get_array_buffer_ptr(value); - std::ptr::copy(data, ptr, len); - if !result_data.is_null() { - *result_data = ptr; - } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_coerce_to_bool( - env: *mut Env, - value: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_boolean(&mut env.scope()); - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_coerce_to_number( - env: *mut Env, - value: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Some(coerced) = value.to_number(&mut env.scope()) else { - return napi_number_expected; - }; - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_coerce_to_object( - env: *mut Env, - value: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_object(&mut env.scope()).unwrap(); - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_coerce_to_string( - env: *mut Env, - value: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_string(&mut env.scope()).unwrap(); - let value: v8::Local = coerced.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_dataview( - env: *mut Env, - len: usize, - data: *mut *mut u8, - byte_offset: usize, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, data); - let env = unsafe { &mut *env }; - check_arg!(env, result); - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); - } - let context = &mut env.scope().get_current_context(); - let global = context.global(&mut env.scope()); - let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); - let data_view = global.get(&mut env.scope(), data_view_name.into()).unwrap(); - let Ok(data_view) = v8::Local::::try_from(data_view) else { - return napi_function_expected; - }; - let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); - let byte_length = v8::Number::new(&mut env.scope(), len as f64); - let value = data_view - .new_instance( - &mut env.scope(), - &[value.into(), byte_offset.into(), byte_length.into()], - ) - .unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_date( - env: *mut Env, - time: f64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value: v8::Local = - v8::Date::new(&mut env.scope(), time).unwrap().into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_double( - env: *mut Env, - value: f64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value).into(); - napi_ok -} - -fn set_error_code( - env: *mut Env, - error: v8::Local, - code: napi_value, - code_cstring: *const c_char, -) -> napi_status { - if code.is_some() || !code_cstring.is_null() { - let err_object: v8::Local = error.try_into().unwrap(); - - let code_value: v8::Local = if code.is_some() { - let mut code_value = unsafe { napi_value_unchecked(code) }; - return_status_if_false!( - env, - code_value.is_string(), - napi_string_expected - ); - code_value - } else { - let name = match check_new_from_utf8(env, code_cstring) { - Ok(s) => s, - Err(status) => return status, - }; - name.into() - }; - - let mut scope = unsafe { &mut *env }.scope(); - let code_key = v8::String::new(&mut scope, "code").unwrap(); - - if err_object - .set(&mut scope, code_key.into(), code_value) - .is_none() - { - return napi_generic_failure; - } - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); - check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_type_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); - check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::type_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_range_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); - check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::range_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_external( - env_ptr: *mut Env, - value: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let external: v8::Local = - v8::External::new(&mut env.scope(), value).into(); - - let value = weak_local(env_ptr, external, value, finalize_cb, finalize_hint); - - *result = transmute(value); - napi_ok -} - -pub type BackingStoreDeleterCallback = unsafe extern "C" fn( - data: *mut c_void, - byte_length: usize, - deleter_data: *mut c_void, -); - -extern "C" { - fn v8__ArrayBuffer__NewBackingStore__with_data( - data: *mut c_void, - byte_length: usize, - deleter: BackingStoreDeleterCallback, - deleter_data: *mut c_void, - ) -> *mut BackingStore; -} - -struct BufferFinalizer { - env: *mut Env, - finalize_cb: napi_finalize, + state: ReferenceState, + ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option, finalize_data: *mut c_void, finalize_hint: *mut c_void, } -impl BufferFinalizer { - fn into_raw(self) -> *mut BufferFinalizer { - Box::into_raw(Box::new(self)) - } -} +impl Reference { + fn new( + env: *mut Env, + value: v8::Local, + initial_ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, + ) -> Box { + let isolate = unsafe { &mut *(*env).isolate_ptr }; -impl Drop for BufferFinalizer { - fn drop(&mut self) { - unsafe { - (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint); + 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 { &mut *(*self.env).isolate_ptr }; + 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 { &mut *(*self.env).isolate_ptr }; + 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(); + + if let Some(finalize_cb) = finalize_cb { + unsafe { + finalize_cb(reference.env as _, finalize_data, finalize_hint); + } + } + + if reference.ownership == ReferenceOwnership::Runtime { + unsafe { drop(Reference::from_raw(reference)) } + } + } + + fn into_raw(r: Box) -> *mut Reference { + Box::into_raw(r) + } + + unsafe fn from_raw(r: *mut Reference) -> Box { + 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)) } } } } -pub extern "C" fn backing_store_deleter_callback( - data: *mut c_void, - _byte_length: usize, - deleter_data: *mut c_void, -) { - let mut finalizer = - unsafe { Box::from_raw(deleter_data as *mut BufferFinalizer) }; - - finalizer.finalize_data = data; -} - -#[napi_sym::napi_sym] -fn napi_create_external_arraybuffer( - env_ptr: *mut Env, - data: *mut c_void, - byte_length: usize, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, - }; - - let store: UniqueRef = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); - - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value: v8::Local = ab.into(); - - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_external_buffer( - env_ptr: *mut Env, - byte_length: usize, - data: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, - }; - - let store: UniqueRef = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); - - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value = - v8::Uint8Array::new(&mut env.scope(), ab, 0, byte_length).unwrap(); - let value: v8::Local = value.into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_function( +#[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( + env: &mut Env, name: *const c_char, length: usize, cb: napi_callback, cb_info: napi_callback_info, result: *mut napi_value, ) -> napi_status { - check_env!(env); check_arg!(env, result); - check_arg_option!(env, cb); + check_arg!(env, cb); - let name = if let Some(name) = name.as_ref() { - match check_new_from_utf8_len(env, name, length) { + let name = if !name.is_null() { + match unsafe { check_new_from_utf8_len(env, name, length) } { Ok(s) => Some(s), Err(status) => return status, } @@ -672,748 +199,46 @@ fn napi_create_function( None }; - *result = create_function(env, name, cb, cb_info).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_int32( - env: *mut Env, - value: i32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new(&mut env.scope(), value).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_uint32( - env: *mut Env, - value: u32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_int64( - env: *mut Env, - value: i64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value as f64).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_object(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = v8::Object::new(&mut env.scope()); - *result = object.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_promise( - env: *mut Env, - deferred: *mut napi_deferred, - promise_out: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); - let mut global = v8::Global::new(&mut env.scope(), resolver); - let mut global_ptr = global.into_raw(); - let promise = resolver.get_promise(&mut env.scope()); - *deferred = global_ptr.as_mut() as *mut _ as napi_deferred; - *promise_out = promise.into(); + unsafe { + *result = create_function(env, name, cb, cb_info).into(); + } napi_ok } -#[napi_sym::napi_sym] -fn napi_create_reference( - env: *mut Env, - value: napi_value, - _initial_refcount: u32, - result: *mut napi_ref, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - let global = v8::Global::new(&mut env.scope(), value); - let mut global_ptr = global.into_raw(); - *result = transmute::, napi_ref>(global_ptr); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_string_latin1( - env: *mut Env, - string: *const u8, +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_define_class<'s>( + env: &'s mut Env, + utf8name: *const c_char, length: usize, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - if length > 0 { - check_arg!(env, string); - } - check_arg!(env, result); - return_status_if_false!( - env, - (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, - napi_invalid_arg - ); - - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() - .as_bytes() - } else { - std::slice::from_raw_parts(string, length) - }; - let Some(v8str) = v8::String::new_from_one_byte( - &mut env.scope(), - string, - v8::NewStringType::Normal, - ) else { - return napi_generic_failure; - }; - *result = v8str.into(); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_string_utf16( - env: *mut Env, - string: *const u16, - length: usize, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - if length > 0 { - check_arg!(env, string); - } - check_arg!(env, result); - return_status_if_false!( - env, - (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, - napi_invalid_arg - ); - - let string = if length == NAPI_AUTO_LENGTH { - let s = std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap(); - std::slice::from_raw_parts(s.as_ptr() as *const u16, s.len()) - } else { - std::slice::from_raw_parts(string, length) - }; - - match v8::String::new_from_two_byte( - &mut env.scope(), - string, - v8::NewStringType::Normal, - ) { - Some(v8str) => { - *result = v8str.into(); - } - None => return napi_generic_failure, - } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_string_utf8( - env: *mut Env, - string: *const u8, - length: usize, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - if length > 0 { - check_arg!(env, string); - } - check_arg!(env, result); - return_status_if_false!( - env, - (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, - napi_invalid_arg - ); - - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() - } else { - let string = std::slice::from_raw_parts(string, length); - std::str::from_utf8(string).unwrap() - }; - let v8str = v8::String::new(&mut env.scope(), string).unwrap(); - *result = v8str.into(); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_symbol( - env: *mut Env, - description: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - - let scope = &mut env.scope(); - let description = if let Some(d) = *description { - let Some(d) = d.to_string(scope) else { - return napi_string_expected; - }; - Some(d) - } else { - None - }; - *result = v8::Symbol::new(scope, description).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_create_typedarray( - env: *mut Env, - ty: napi_typedarray_type, - length: usize, - arraybuffer: napi_value, - byte_offset: usize, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let ab = napi_value_unchecked(arraybuffer); - let Ok(ab) = v8::Local::::try_from(ab) else { - return napi_arraybuffer_expected; - }; - let typedarray: v8::Local = match ty { - napi_uint8_array => { - v8::Uint8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint8_clamped_array => { - v8::Uint8ClampedArray::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int8_array => { - v8::Int8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint16_array => { - v8::Uint16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int16_array => { - v8::Int16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint32_array => { - v8::Uint32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int32_array => { - v8::Int32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float32_array => { - v8::Float32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float64_array => { - v8::Float64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_bigint64_array => { - v8::BigInt64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_biguint64_array => { - v8::BigUint64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - _ => { - return napi_invalid_arg; - } - }; - *result = typedarray.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_make_callback( - env: *mut Env, - async_context: *mut c_void, - recv: napi_value, - func: napi_value, - argc: isize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, recv); - if argc > 0 { - check_arg!(env, argv); - } - - if !async_context.is_null() { - log::info!("napi_make_callback: async_context is not supported"); - } - - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); - - let Ok(func) = v8::Local::::try_from(func) else { - return napi_function_expected; - }; - let argv: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc as usize)); - let ret = func.call(&mut env.scope(), recv, argv); - *result = transmute::>, napi_value>(ret); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_bigint_int64( - env: *mut Env, - value: napi_value, - result: *mut i64, - lossless: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); - let (result_, lossless_) = bigint.i64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_bigint_uint64( - env: *mut Env, - value: napi_value, - result: *mut u64, - lossless: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); - let (result_, lossless_) = bigint.u64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_bigint_words( - env: *mut Env, - value: napi_value, - sign_bit: *mut i32, - word_count: *mut usize, - words: *mut u64, -) -> napi_status { - check_env!(env); - // TODO(bartlomieju): - // check_arg!(env, value); - check_arg!(env, word_count); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - let big = match value.to_big_int(&mut env.scope()) { - Some(b) => b, - None => return napi_bigint_expected, - }; - let word_count_int; - - if sign_bit.is_null() && words.is_null() { - word_count_int = big.word_count(); - } else { - check_arg!(env, sign_bit); - check_arg!(env, words); - let out_words = std::slice::from_raw_parts_mut(words, *word_count); - let (sign, slice_) = big.to_words_array(out_words); - word_count_int = slice_.len(); - *sign_bit = sign as i32; - } - - *word_count = word_count_int; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_bool( - env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.boolean_value(&mut env.scope()); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_double( - env: *mut Env, - value: napi_value, - result: *mut f64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_number(), napi_number_expected); - *result = value.number_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_external( - _env: *mut Env, - value: napi_value, - result: *mut *mut c_void, -) -> napi_status { - let value = napi_value_unchecked(value); - let ext = v8::Local::::try_from(value).unwrap(); - *result = ext.value(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_int32( - env: *mut Env, - value: napi_value, - result: *mut i32, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.int32_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_int64( - env: *mut Env, - value: napi_value, - result: *mut i64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.integer_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_string_latin1( - env: *mut Env, - value: napi_value, - buf: *mut u8, - bufsize: usize, - result: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } - - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); - - if buf.is_null() { - *result = string_len; - } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_one_byte( - &mut env.scope(), - buffer, - 0, - v8::WriteOptions::NO_NULL_TERMINATION, - ); - buf.add(copied).write(0); - if !result.is_null() { - *result = copied; - } - } else if !result.is_null() { - *result = string_len; - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_string_utf8( - env: *mut Env, - value: napi_value, - buf: *mut u8, - bufsize: usize, - result: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } - - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); - - if buf.is_null() { - *result = string_len; - } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_utf8( - &mut env.scope(), - buffer, - None, - v8::WriteOptions::NO_NULL_TERMINATION - | v8::WriteOptions::REPLACE_INVALID_UTF8, - ); - buf.add(copied).write(0); - if !result.is_null() { - *result = copied; - } - } else if !result.is_null() { - *result = string_len; - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_string_utf16( - env: *mut Env, - value: napi_value, - buf: *mut u16, - bufsize: usize, - result: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } - - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.length(); - - if buf.is_null() { - *result = string_len; - } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write( - &mut env.scope(), - buffer, - 0, - v8::WriteOptions::NO_NULL_TERMINATION, - ); - buf.add(copied).write(0); - if !result.is_null() { - *result = copied; - } - } else if !result.is_null() { - *result = string_len; - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_value_uint32( - env: *mut Env, - value: napi_value, - result: *mut u32, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.uint32_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_add_finalizer( - env_ptr: *mut Env, - js_object: napi_value, - native_object: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_ref, -) -> napi_status { - check_env!(env_ptr); - - let value = napi_value_unchecked(js_object); - let value = - weak_local(env_ptr, value, native_object, finalize_cb, finalize_hint); - - if !result.is_null() { - *result = transmute(value); - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_adjust_external_memory( - env: *mut Env, - change_in_bytes: i64, - adjusted_value: *mut i64, -) -> napi_status { - check_env!(env); - check_arg!(env, adjusted_value); - - let env = unsafe { &mut *env }; - let isolate = &mut *env.isolate_ptr; - *adjusted_value = - isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); - - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_call_function( - env: *mut Env, - recv: napi_value, - func: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); - let Ok(func) = v8::Local::::try_from(func) else { - return napi_function_expected; - }; - - let argv: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc)); - let ret = func.call(&mut env.scope(), recv, argv); - if !result.is_null() { - *result = transmute::>, napi_value>(ret); - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_close_escapable_handle_scope( - _env: *mut Env, - _scope: napi_escapable_handle_scope, -) -> napi_status { - // TODO: do this properly - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_close_handle_scope( - env: *mut Env, - _scope: napi_handle_scope, -) -> napi_status { - let env = &mut *env; - if env.open_handle_scopes == 0 { - return napi_handle_scope_mismatch; - } - // TODO: We are not opening a handle scope, therefore we cannot close it - // TODO: this is also not implemented in napi_open_handle_scope - // let _scope = &mut *(scope as *mut v8::HandleScope); - env.open_handle_scopes -= 1; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_define_class( - env_ptr: *mut Env, - name: *const c_char, - length: isize, constructor: napi_callback, callback_data: *mut c_void, property_count: usize, properties: *const napi_property_descriptor, - result: *mut napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; check_arg!(env, result); - check_arg_option!(env, constructor); + check_arg!(env, constructor); if property_count > 0 { check_arg!(env, properties); } - let name = if length == -1 { - let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() else { - return napi_invalid_arg; - }; - s - } else { - let slice = std::slice::from_raw_parts(name as *const u8, length as usize); - std::str::from_utf8(slice).unwrap() + let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } { + Ok(string) => string, + Err(status) => return status, }; - let tpl = - create_function_template(env_ptr, Some(name), constructor, callback_data); + let tpl = unsafe { + create_function_template(env, Some(name), constructor, callback_data) + }; - let scope = &mut env.scope(); - let napi_properties: &[napi_property_descriptor] = - std::slice::from_raw_parts(properties, property_count); + 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 { @@ -1423,11 +248,9 @@ fn napi_define_class( continue; } - let name = if !p.utf8name.is_null() { - let name_str = CStr::from_ptr(p.utf8name).to_str().unwrap(); - v8::String::new(scope, name_str).unwrap() - } else { - transmute::>(p.name) + let name = match unsafe { v8_name_from_property_descriptor(env, p) } { + Ok(name) => name, + Err(status) => return status, }; let method = p.method; @@ -1437,13 +260,13 @@ fn napi_define_class( if getter.is_some() || setter.is_some() { let getter: Option> = if getter.is_some() { - Some(create_function_template(env_ptr, None, p.getter, p.data)) + Some(unsafe { create_function_template(env, None, p.getter, p.data) }) } else { None }; let setter: Option> = if setter.is_some() { - Some(create_function_template(env_ptr, None, p.setter, p.data)) + Some(unsafe { create_function_template(env, None, p.setter, p.data) }) } else { None }; @@ -1465,28 +288,26 @@ fn napi_define_class( accessor_property | v8::PropertyAttribute::DONT_DELETE; } - let proto = tpl.prototype_template(scope); - proto.set_accessor_property( - name.into(), - getter, - setter, - accessor_property, - ); + let proto = tpl.prototype_template(&mut env.scope()); + proto.set_accessor_property(name, getter, setter, accessor_property); } else if method.is_some() { - let function = create_function_template(env_ptr, None, p.method, p.data); - let proto = tpl.prototype_template(scope); - proto.set(name.into(), function.into()); + let function = + unsafe { create_function_template(env, None, p.method, p.data) }; + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, function.into()); } else { - let proto = tpl.prototype_template(scope); - proto.set( - name.into(), - transmute::>(p.value), - ); + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, p.value.unwrap().into()); } } - let value: v8::Local = tpl.get_function(scope).unwrap().into(); - *result = value.into(); + let env_ptr = env as *mut Env; + let value: v8::Local = + 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); @@ -1497,646 +318,224 @@ fn napi_define_class( } } - status_call!(napi_define_properties( - env_ptr, - *result, - static_descriptors.len(), - static_descriptors.as_ptr(), - )); - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_define_properties( - env_ptr: *mut Env, - obj: napi_value, - property_count: usize, - properties: *const napi_property_descriptor, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - if property_count > 0 { - check_arg!(env, properties); - } - - let scope = &mut env.scope(); - - let Ok(object) = v8::Local::::try_from(napi_value_unchecked(obj)) - else { - return napi_object_expected; - }; - - let properties = std::slice::from_raw_parts(properties, property_count); - for property in properties { - let name = if !property.utf8name.is_null() { - let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap(); - let Some(name_v8_str) = v8::String::new(scope, name_str) else { - return napi_generic_failure; - }; - name_v8_str.into() - } else { - let property_value = napi_value_unchecked(property.name); - let Ok(prop) = v8::Local::::try_from(property_value) else { - return napi_name_expected; - }; - prop - }; - - if property.getter.is_some() || property.setter.is_some() { - let local_getter: v8::Local = if property.getter.is_some() { - create_function(env_ptr, None, property.getter, property.data).into() - } else { - v8::undefined(scope).into() - }; - let local_setter: v8::Local = if property.setter.is_some() { - create_function(env_ptr, None, property.setter, property.data).into() - } else { - v8::undefined(scope).into() - }; - - let mut desc = - v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); - desc.set_enumerable(property.attributes & napi_enumerable != 0); - desc.set_configurable(property.attributes & napi_configurable != 0); - - let define_maybe = object.define_property(scope, name, &desc); - return_status_if_false!( + crate::status_call!(unsafe { + napi_define_properties( env_ptr, - define_maybe.is_some(), - napi_invalid_arg - ); - } else if property.method.is_some() { - let value: v8::Local = { - let function = - create_function(env_ptr, None, property.method, property.data); - function.into() - }; - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); - } else { - let value = napi_value_unchecked(property.value); - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); - } + *result, + static_descriptors.len(), + static_descriptors.as_ptr(), + ) + }); } napi_ok } -#[napi_sym::napi_sym] -fn napi_delete_element( - env: *mut Env, - value: napi_value, - index: u32, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.delete_index(&mut env.scope(), index).unwrap_or(false); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_delete_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg!(env, result); - - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; - - let Some(deleted) = object.delete(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; - - *result = deleted; - napi_ok -} - -// TODO: properly implement ref counting stuff -#[napi_sym::napi_sym] -fn napi_delete_reference(_env: *mut Env, _nref: napi_ref) -> napi_status { - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); - - let value = napi_value_unchecked(value); - let Ok(ab) = v8::Local::::try_from(value) else { - return napi_arraybuffer_expected; - }; - - if !ab.is_detachable() { - return napi_detachable_arraybuffer_expected; - } - - // Expected to crash for None. - ab.detach(None).unwrap(); - - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_escape_handle<'s>( - _env: *mut Env, - _handle_scope: napi_escapable_handle_scope, - escapee: napi_value<'s>, - result: *mut napi_value<'s>, -) -> napi_status { - // TODO - *result = escapee; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_all_property_names(_env: *mut Env) -> napi_status { - // TODO - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_and_clear_last_exception( - env: *mut Env, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - // TODO: just return undefined for now we don't cache - // exceptions in env. - let value: v8::Local = v8::undefined(&mut env.scope()).into(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_array_length( - _env: *mut Env, - value: napi_value, - result: *mut u32, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = v8::Local::::try_from(value).unwrap().length(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_arraybuffer_info( - _env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, -) -> napi_status { - let value = napi_value_unchecked(value); - let buf = v8::Local::::try_from(value).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf); - } - if !length.is_null() { - *length = buf.byte_length(); - } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_boolean( - env: *mut Env, - value: bool, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Boolean::new(env.isolate(), value).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_buffer_info( - env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::::try_from(value) else { - return napi_arraybuffer_expected; - }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_arraybuffer_expected; - }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); - } - if !length.is_null() { - *length = abuf.byte_length(); - } - napi_ok -} - -#[napi_sym::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 { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg!(env, cbinfo); - - let cbinfo: &CallbackInfo = &*(cbinfo as *const CallbackInfo); - let args = &*(cbinfo.args as *const v8::FunctionCallbackArguments); - - if !argv.is_null() { - check_arg!(env, argc); - let mut v_argv = std::slice::from_raw_parts_mut(argv, argc as usize); - for i in 0..*argc { - let mut arg = args.get(i); - v_argv[i as usize] = arg.into(); - } - } - - if !argc.is_null() { - *argc = args.length(); - } - - if !this_arg.is_null() { - let mut this = args.this(); - *this_arg = this.into(); - } - - if !data.is_null() { - *data = cbinfo.cb_info; - } - - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_dataview_info( - env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::::try_from(value) else { - return napi_invalid_arg; - }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_invalid_arg; - }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); - } - *length = abuf.byte_length(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_date_value( - env: *mut Env, - value: napi_value, - result: *mut f64, -) -> napi_status { - check_env!(env); - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_date(), napi_date_expected); - let env = unsafe { &mut *env }; - let Ok(date) = v8::Local::::try_from(value) else { - return napi_date_expected; - }; - // TODO: should be value of - *result = date.number_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_element( - env: *mut Env, - object: napi_value, - index: u32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::::try_from(object) else { - return napi_invalid_arg; - }; - let value: v8::Local = - object.get_index(&mut env.scope(), index).unwrap(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - - let value: v8::Local = - transmute::, v8::Local>((*env).global); - *result = value.into(); - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_instance_data( - env: *mut Env, - result: *mut *mut c_void, -) -> napi_status { - let env = &mut *env; - let shared = env.shared(); - *result = shared.instance_data; - napi_ok -} - -// TODO(bartlomieju): this function is broken -#[napi_sym::napi_sym] -fn napi_get_last_error_info( - _env: *mut Env, - error_code: *mut *const napi_extended_error_info, -) -> napi_status { - let err_info = Box::new(napi_extended_error_info { - error_message: std::ptr::null(), - engine_reserved: std::ptr::null_mut(), - engine_error_code: 0, - error_code: napi_ok, - }); - - *error_code = Box::into_raw(err_info); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_named_property( - env: *mut Env, - object: napi_value, - utf8_name: *const c_char, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let utf8_name = std::ffi::CStr::from_ptr(utf8_name); - let name = - v8::String::new(&mut env.scope(), &utf8_name.to_string_lossy()).unwrap(); - let value: v8::Local = object - .to_object(&mut env.scope()) - .unwrap() - .get(&mut env.scope(), name.into()) - .unwrap(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_new_target( - _env: &mut Env, - cbinfo: &CallbackInfo, - result: &mut v8::Local, -) -> napi_status { - let info = &*(cbinfo.args as *const v8::FunctionCallbackArguments); - *result = info.new_target(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::null(env.isolate()).into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = transmute::>(object); - let key = napi_value_unchecked(key); - let value: v8::Local = object.get(&mut env.scope(), key).unwrap(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_property_names( env: *mut Env, object: napi_value, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let array: v8::Local = object - .to_object(&mut env.scope()) - .unwrap() - .get_property_names(&mut env.scope(), Default::default()) - .unwrap(); - let value: v8::Local = array.into(); - *result = value.into(); - napi_ok + 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::napi_sym] -fn napi_get_prototype( - env: *mut Env, - value: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_get_all_property_names<'s>( + env: &'s mut Env, + object: napi_value, + key_mode: napi_key_collection_mode, + key_filter: napi_key_filter, + key_conversion: napi_key_conversion, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let proto = obj.get_prototype(&mut env.scope()).unwrap(); - *result = proto.into(); - napi_ok -} + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_get_reference_value( - env: *mut Env, - reference: napi_ref, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let value = transmute::>(reference); - *result = value.into(); - napi_ok -} + let scope = &mut env.scope(); -#[napi_sym::napi_sym] -fn napi_get_typedarray_info( - env: *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 { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(typedarray); - let Ok(array) = v8::Local::::try_from(value) else { - return napi_invalid_arg; + let Some(obj) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - if !type_.is_null() { - if value.is_int8_array() { - *type_ = napi_int8_array; - } else if value.is_uint8_array() { - *type_ = napi_uint8_array; - } else if value.is_uint8_clamped_array() { - *type_ = napi_uint8_clamped_array; - } else if value.is_int16_array() { - *type_ = napi_int16_array; - } else if value.is_uint16_array() { - *type_ = napi_uint16_array; - } else if value.is_int32_array() { - *type_ = napi_int32_array; - } else if value.is_uint32_array() { - *type_ = napi_uint32_array; - } else if value.is_float32_array() { - *type_ = napi_float32_array; - } else if value.is_float64_array() { - *type_ = napi_float64_array; - } + let 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; } - if !length.is_null() { - *length = array.length(); - } + let key_mode = match key_mode { + napi_key_include_prototypes => v8::KeyCollectionMode::IncludePrototypes, + napi_key_own_only => v8::KeyCollectionMode::OwnOnly, + _ => return napi_invalid_arg, + }; - if !data.is_null() || !arraybuffer.is_null() { - let buf = array.buffer(&mut env.scope()).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf) as *mut c_void; - } - if !arraybuffer.is_null() { - *arraybuffer = buf.into(); - } - } + 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, + }; - if !byte_offset.is_null() { - *byte_offset = array.byte_offset(); + let filter = v8::GetPropertyNamesArgsBuilder::new() + .mode(key_mode) + .property_filter(filter) + .index_filter(v8::IndexFilter::IncludeIndices) + .key_conversion(key_conversion) + .build(); + + let property_names = match obj.get_property_names(scope, filter) { + Some(n) => n, + None => return napi_generic_failure, + }; + + unsafe { + *result = property_names.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::undefined(env.isolate()).into(); - napi_ok -} - -pub const NAPI_VERSION: u32 = 8; - -#[napi_sym::napi_sym] -fn napi_get_version(_: napi_env, version: *mut u32) -> napi_status { - *version = NAPI_VERSION; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_element( - env: *mut Env, +#[napi_sym] +fn napi_set_property( + env: &mut Env, + object: napi_value, + key: napi_value, value: napi_value, - index: u32, - result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.has_index(&mut env.scope(), index).unwrap_or(false); + check_arg!(env, key); + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if object.set(scope, key.unwrap(), value.unwrap()).is_none() { + return napi_generic_failure; + }; + napi_ok } -#[napi_sym::napi_sym] -fn napi_has_named_property( - env: *mut Env, - value: napi_value, - key: *const c_char, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let key = CStr::from_ptr(key).to_str().unwrap(); - let key = v8::String::new(&mut env.scope(), key).unwrap(); - *result = obj.has(&mut env.scope(), key.into()).unwrap_or(false); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_own_property( - env: *mut Env, +#[napi_sym] +fn napi_has_property( + env: &mut Env, object: napi_value, key: napi_value, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + check_arg!(env, key); check_arg!(env, result); let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - if key.is_none() { - return napi_invalid_arg; + 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::::try_from(key.unwrap()) else { return napi_name_expected; }; @@ -2145,573 +544,975 @@ fn napi_has_own_property( return napi_generic_failure; }; - *result = has_own; + unsafe { + *result = has_own; + } napi_ok } -#[napi_sym::napi_sym] -fn napi_has_property( - env: *mut Env, - object: napi_value, - key: napi_value, +#[napi_sym] +fn napi_has_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + let env_ptr = env as *mut Env; check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; - - let Some(has) = object.has(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; - *result = has; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_instanceof( - env: *mut Env, - value: napi_value, - constructor: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, constructor); - check_arg_option!(env, value); - - let value = napi_value_unchecked(value); - let constructor = napi_value_unchecked(constructor); - let Some(ctor) = constructor.to_object(&mut env.scope()) else { + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { return napi_object_expected; }; - if !ctor.is_function() { - return napi_function_expected; - } - let Some(res) = value.instance_of(&mut env.scope(), ctor) else { + + 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; }; - *result = res; + unsafe { + *result = has_property; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_is_array( - _env: *mut Env, - value: napi_value, - result: *mut bool, +#[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 { - let value = napi_value_unchecked(value); - *result = value.is_array(); + 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::napi_sym] -fn napi_is_arraybuffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, +#[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 { - let value = napi_value_unchecked(value); - *result = value.is_array_buffer(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_buffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - // TODO: should we assume Buffer as Uint8Array in Deno? - // or use std/node polyfill? - *result = value.is_typed_array(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_dataview( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_data_view(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_date( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_date(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_detached_arraybuffer( - env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); check_arg!(env, result); + let env_ptr = env as *mut Env; - let value = napi_value_unchecked(value); - - *result = match v8::Local::::try_from(value) { - Ok(array_buffer) => array_buffer.was_detached(), - Err(_) => false, + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; }; - napi_clear_last_error(env); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_error( - env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - { - check_env!(env); - if value.is_none() { - return napi_invalid_arg; - } - check_arg!(env, result); - - let value = napi_value_unchecked(value); - *result = value.is_native_error(); - } - napi_clear_last_error(env); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_exception_pending(_env: *mut Env, result: *mut bool) -> napi_status { - // TODO - *result = false; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_promise( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_promise(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_is_typedarray( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_typed_array(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_new_instance( - env: *mut Env, - constructor: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let constructor = napi_value_unchecked(constructor); - let Ok(constructor) = v8::Local::::try_from(constructor) else { - return napi_function_expected; + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, }; - let args: &[v8::Local] = - transmute(std::slice::from_raw_parts(argv, argc)); - let inst = constructor.new_instance(&mut env.scope(), args).unwrap(); - let value: v8::Local = inst.into(); - *result = value.into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_object_freeze( - env: &mut Env, - object: v8::Local, -) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Frozen) - .is_none() - { + let Some(value) = object.get(&mut env.scope(), key.into()) else { return napi_generic_failure; }; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_object_seal( - env: &mut Env, - object: v8::Local, -) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Sealed) - .is_none() - { - return napi_generic_failure; + unsafe { + *result = value.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_open_escapable_handle_scope( - _env: *mut Env, - _result: *mut napi_escapable_handle_scope, -) -> napi_status { - // TODO: do this properly - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_open_handle_scope( - env: *mut Env, - _result: *mut napi_handle_scope, -) -> napi_status { - let env = &mut *env; - - // TODO: this is also not implemented in napi_close_handle_scope - // *result = &mut env.scope() as *mut _ as napi_handle_scope; - env.open_handle_scopes += 1; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_reference_ref() -> napi_status { - // TODO - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_reference_unref() -> napi_status { - // TODO - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_reject_deferred( - env: *mut Env, - deferred: napi_deferred, - error: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull, - v8::Local, - >(deferred_ptr); - resolver - .reject(&mut env.scope(), napi_value_unchecked(error)) - .unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_remove_wrap(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - obj.delete_private(&mut env.scope(), napi_wrap).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_resolve_deferred( - env: *mut Env, - deferred: napi_deferred, - result: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull, - v8::Local, - >(deferred_ptr); - resolver - .resolve(&mut env.scope(), napi_value_unchecked(result)) - .unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_run_script( - env: *mut Env, - script: napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let script = napi_value_unchecked(script); - if !script.is_string() { - // TODO: - // napi_set_last_error - return napi_string_expected; - } - let script = script.to_string(&mut env.scope()).unwrap(); - - let script = v8::Script::compile(&mut env.scope(), script, None); - if script.is_none() { - return napi_generic_failure; - } - let script = script.unwrap(); - let rv = script.run(&mut env.scope()); - - if let Some(rv) = rv { - *result = rv.into(); - } else { - return napi_generic_failure; - } - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_element( - env: *mut Env, - object: napi_value, +#[napi_sym] +fn napi_set_element<'s>( + env: &'s mut Env, + object: napi_value<'s>, index: u32, - value: napi_value, + value: napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::::try_from(object) else { - return napi_invalid_arg; - }; - let value = napi_value_unchecked(value); - object.set_index(&mut env.scope(), index, value).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_instance_data( - env: *mut Env, - data: *mut c_void, - finalize_cb: Option, - finalize_hint: *mut c_void, -) -> napi_status { - let env = &mut *env; - let shared = env.shared_mut(); - shared.instance_data = data; - shared.data_finalize = if finalize_cb.is_some() { - finalize_cb - } else { - None - }; - shared.data_finalize_hint = finalize_hint; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_named_property( - env: *mut Env, - object: napi_value, - name: *const c_char, - value: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let name = CStr::from_ptr(name).to_str().unwrap(); - let object = transmute::>(object); - let value = napi_value_unchecked(value); - let name = v8::String::new(&mut env.scope(), name).unwrap(); - object.set(&mut env.scope(), name.into(), value).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_property( - env: *mut Env, - object: napi_value, - key: napi_value, - value: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg_option!(env, value); + check_arg!(env, value); let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - if object - .set(scope, key.unwrap_unchecked(), value.unwrap_unchecked()) - .is_none() + if !object + .set_index(scope, index, value.unwrap()) + .unwrap_or(false) { return napi_generic_failure; - }; + } napi_ok } -#[napi_sym::napi_sym] -fn napi_strict_equals( +#[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 = if property.getter.is_some() { + unsafe { + create_function(env_ptr, None, property.getter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + let local_setter: v8::Local = if property.setter.is_some() { + unsafe { + create_function(env_ptr, None, property.setter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else if property.method.is_some() { + let method: v8::Local = { + let function = unsafe { + create_function(env_ptr, None, property.method, property.data) + }; + function.into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(method, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_generic_failure; + } + } else { + let value = property.value.unwrap(); + + if enumerable & writable & configurable { + if !object + .create_data_property(scope, property_name, value) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else { + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(value, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } + } + } + + 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::::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_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, lhs); - check_arg_option!(env, rhs); + check_arg!(env, lhs); + check_arg!(env, rhs); + check_arg!(env, result); - *result = lhs.unwrap_unchecked().strict_equals(rhs.unwrap_unchecked()); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let error = napi_value_unchecked(error); - env.scope().throw_exception(error); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_throw_error( - env: *mut Env, - code: *const c_char, - msg: *const c_char, -) -> napi_status { - // TODO: add preamble here - - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - - unsafe { &mut *env }.scope().throw_exception(error); + unsafe { + *result = lhs.unwrap().strict_equals(rhs.unwrap()); } - napi_clear_last_error(env); + napi_ok } -#[napi_sym::napi_sym] -fn napi_throw_range_error( - env: *mut Env, - code: *const c_char, - msg: *const c_char, +#[napi_sym] +fn napi_get_prototype<'s>( + env: &'s mut Env, + object: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - // TODO: add preamble here + check_arg!(env, result); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::range_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); + let 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_clear_last_error(env); + napi_ok } -#[napi_sym::napi_sym] -fn napi_throw_type_error( - env: *mut Env, - code: *const c_char, - msg: *const c_char, +#[napi_sym] +fn napi_create_object( + env_ptr: *mut Env, + result: *mut napi_value, ) -> napi_status { - // TODO: add preamble here + let env = check_env!(env_ptr); + check_arg!(env, result); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::type_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); + unsafe { + *result = v8::Object::new(&mut env.scope()).into(); } - napi_clear_last_error(env); + + 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] +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: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, +) -> napi_status { + return napi_set_last_error(env_ptr, napi_generic_failure); +} + +#[napi_sym] +fn node_api_create_external_string_utf16( + env_ptr: *mut Env, + _string: *const u16, + _length: usize, + _nogc_finalize_callback: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, +) -> napi_status { + return napi_set_last_error(env_ptr, napi_generic_failure); +} + +#[napi_sym] +fn node_api_create_property_key_utf16( + env_ptr: *mut Env, + string: *const u16, + length: usize, + result: *mut napi_value, +) -> napi_status { + 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::::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 = 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) -> Option { if value.is_undefined() { Some(napi_undefined) @@ -2738,98 +1539,2048 @@ pub fn get_value_type(value: v8::Local) -> Option { } } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_typeof( env: *mut Env, value: napi_value, result: *mut napi_valuetype, ) -> napi_status { - check_env!(env); - check_arg_option!(env, value); + let env = check_env!(env); + check_arg!(env, value); check_arg!(env, result); let Some(ty) = get_value_type(value.unwrap()) else { - return napi_invalid_arg; + return napi_set_last_error(env, napi_invalid_arg); }; - *result = ty; + + unsafe { + *result = ty; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::undefined(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::null(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_cb_info( + env: *mut Env, + cbinfo: napi_callback_info, + argc: *mut i32, + argv: *mut napi_value, + this_arg: *mut napi_value, + data: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + if !argv.is_null() { + check_arg!(env, argc); + let argc = unsafe { *argc as usize }; + for i in 0..argc { + let mut arg = args.get(i as _); + unsafe { + *argv.add(i) = arg.into(); + } + } + } + + if !argc.is_null() { + unsafe { + *argc = args.length(); + } + } + + if !this_arg.is_null() { + unsafe { + *this_arg = args.this().into(); + } + } + + if !data.is_null() { + unsafe { + *data = cbinfo.cb_info; + } + } + + napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] +#[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( + env_ptr: *mut Env, + recv: napi_value, + func: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, recv); + let args = if argc > 0 { + check_arg!(env, argv); + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local, argc) + } + } else { + &[] + }; + + let Some(func) = + func.and_then(|f| v8::Local::::try_from(f).ok()) + else { + return napi_set_last_error(env, napi_function_expected); + }; + + let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + if !result.is_null() { + unsafe { + *result = v.into(); + } + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = std::mem::transmute::, v8::Local>( + env.global, + ) + .into(); + } + + return napi_clear_last_error(env); +} + +#[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 = 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::::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::::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::::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::::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::::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::::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::::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::::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_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::::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, + 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::::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::::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::::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_sym] +fn napi_create_external<'s>( + env: &'s mut Env, + data: *mut c_void, + finalize_cb: Option, + 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::::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::::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::::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 { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = obj.get_private(&mut env.scope(), napi_wrap).unwrap(); - let Some(ext) = v8::Local::::try_from(ext).ok() else { - return napi_invalid_arg; + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(external) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); }; - *result = ext.value(); - napi_ok + + let wrapper_ptr = external.value() as *const ExternalWrapper; + let wrapper = unsafe { &*wrapper_ptr }; + + unsafe { + *result = wrapper.data; + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_wrap( +#[napi_sym] +fn napi_create_reference( env: *mut Env, value: napi_value, - native_object: *mut c_void, + initial_refcount: u32, + result: *mut napi_ref, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = v8::External::new(&mut env.scope(), native_object); - obj.set_private(&mut env.scope(), napi_wrap, ext.into()); - napi_ok + 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::napi_sym] -fn node_api_throw_syntax_error( - env: *mut Env, - _code: *const c_char, - msg: *const c_char, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; +#[napi_sym] +fn napi_delete_reference(env: *mut Env, ref_: napi_ref) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); - // let code = CStr::from_ptr(code).to_str().unwrap(); - let msg = CStr::from_ptr(msg).to_str().unwrap(); + let reference = unsafe { Reference::from_raw(ref_ as _) }; - // let code = v8::String::new(&mut env.scope(), code).unwrap(); - let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + drop(reference); - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - env.scope().throw_exception(error); - - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn node_api_create_syntax_error( +#[napi_sym] +fn napi_reference_ref( env: *mut Env, - _code: napi_value, - msg: napi_value, + 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 { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); + check_arg!(env, ref_); + check_arg!(env, result); - // let code = napi_value_unchecked(code); - let msg = napi_value_unchecked(msg); + let reference = unsafe { &mut *(ref_ as *mut Reference) }; - let msg = msg.to_string(&mut env.scope()).unwrap(); + 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()), + }; - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - *result = error.into(); + 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::::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, 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, + "ERR_NAPI_CONS_FUNCTION\0".as_ptr() as _, + "Constructor must be a function\0".as_ptr() as _, + ); + } + return napi_function_expected; + } + + let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else { + return napi_generic_failure; + }; + + 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 = + 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) as _; + } + } + + 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 = ab.into(); + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_arraybuffer_info( + env: *mut Env, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + + let Some(buf) = + value.and_then(|v| v8::Local::::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::::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 = 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::::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, + 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::::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + if byte_length + byte_offset > buffer.byte_length() { + unsafe { + return napi_throw_range_error( + env, + "ERR_NAPI_INVALID_DATAVIEW_ARGS\0".as_ptr() as _, + "byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in\0".as_ptr() as _, + ); + } + } + + // let dataview = v8::DataView::new(&mut env, buffer, byte_offset, byte_length); + let dataview = { + let context = &mut env.scope().get_current_context(); + let global = context.global(&mut env.scope()); + let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); + let data_view = + global.get(&mut env.scope(), data_view_name.into()).unwrap(); + let Ok(data_view) = v8::Local::::try_from(data_view) else { + return napi_function_expected; + }; + let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); + let byte_length = v8::Number::new(&mut env.scope(), byte_length as f64); + let Some(dv) = data_view.new_instance( + &mut env.scope(), + &[buffer.into(), byte_offset.into(), byte_length.into()], + ) else { + return napi_generic_failure; + }; + dv + }; + + 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::::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); + + let isolate = unsafe { &mut *env.isolate_ptr }; + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .resolve(&mut env.scope(), result.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_reject_deferred( + env: &mut Env, + deferred: napi_deferred, + result: napi_value, +) -> napi_status { + check_arg!(env, result); + check_arg!(env, deferred); + + let isolate = unsafe { &mut *env.isolate_ptr }; + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .reject(&mut env.scope(), result.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + 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::::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::::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, + 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::::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); + + let isolate = unsafe { &mut *env.isolate_ptr }; + + unsafe { + *adjusted_value = + isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_set_instance_data( + env: *mut Env, + data: *mut c_void, + finalize_cb: Option, + 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::::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::::try_from(v).ok()) + { + Some(ab) => ab.was_detached(), + None => false, + }; + + unsafe { + *result = is_detached; + } + + napi_clear_last_error(env) +} diff --git a/cli/napi/mod.rs b/cli/napi/mod.rs index 697ec06e35..122d2ff060 100644 --- a/cli/napi/mod.rs +++ b/cli/napi/mod.rs @@ -15,8 +15,6 @@ //! 2. Add the function's identifier to this JSON list. //! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `cli/napi/generated_symbol_exports_list_*.def`. -pub mod r#async; -pub mod env; pub mod js_native_api; -pub mod threadsafe_functions; +pub mod node_api; pub mod util; diff --git a/cli/napi/node_api.rs b/cli/napi/node_api.rs new file mode 100644 index 0000000000..28a11d614d --- /dev/null +++ b/cli/napi/node_api.rs @@ -0,0 +1,1020 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +#![deny(unsafe_op_in_unsafe_fn)] + +use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::SendPtr; +use crate::check_arg; +use crate::check_env; +use deno_core::V8CrossThreadTaskSpawner; +use deno_runtime::deno_napi::*; +use napi_sym::napi_sym; +use std::ptr::NonNull; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicU8; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +#[napi_sym] +fn napi_module_register(module: *const NapiModule) -> napi_status { + MODULE_TO_REGISTER.with(|cell| { + let mut slot = cell.borrow_mut(); + let prev = slot.replace(module); + assert!(prev.is_none()); + }); + napi_ok +} + +#[napi_sym] +fn napi_add_env_cleanup_hook( + env: *mut Env, + fun: Option, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); + if env_cleanup_hooks + .iter() + .any(|pair| pair.0 == fun && pair.1 == arg) + { + panic!("Cleanup hook with this data already registered"); + } + env_cleanup_hooks.push((fun, arg)); + + napi_ok +} + +#[napi_sym] +fn napi_remove_env_cleanup_hook( + env: *mut Env, + fun: Option, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); + // Hooks are supposed to be removed in LIFO order + let maybe_index = env_cleanup_hooks + .iter() + .rposition(|&pair| pair.0 == fun && pair.1 == arg); + + if let Some(index) = maybe_index { + env_cleanup_hooks.remove(index); + } else { + panic!("Cleanup hook with this data not found"); + } + + napi_ok +} + +type AsyncCleanupHandle = (*mut Env, napi_async_cleanup_hook, *mut c_void); + +unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) { + unsafe { + let (env, hook, arg) = *Box::::from_raw(arg as _); + hook(env as _, arg); + } +} + +#[napi_sym] +fn napi_add_async_cleanup_hook( + env: *mut Env, + hook: Option, + 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((env, hook, arg))) as _; + + unsafe { + napi_add_env_cleanup_hook(env, Some(async_cleanup_handler), handle); + } + + if !remove_handle.is_null() { + unsafe { + *remove_handle = handle; + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_remove_async_cleanup_hook( + remove_handle: napi_async_cleanup_hook_handle, +) -> napi_status { + if remove_handle.is_null() { + return napi_invalid_arg; + } + + let handle = unsafe { &*(remove_handle as *mut AsyncCleanupHandle) }; + + let env = unsafe { &mut *handle.0 }; + + unsafe { + napi_remove_env_cleanup_hook( + env, + Some(async_cleanup_handler), + remove_handle, + ); + } + + napi_ok +} + +#[napi_sym] +fn napi_fatal_exception(env: &mut Env, err: napi_value) -> napi_status { + check_arg!(env, err); + + let report_error = unsafe { + std::mem::transmute::, v8::Local>( + env.report_error, + ) + }; + + let this = v8::undefined(&mut env.scope()); + if report_error + .call(&mut env.scope(), this.into(), &[err.unwrap()]) + .is_none() + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +#[allow(clippy::print_stderr)] +fn napi_fatal_error( + location: *const c_char, + location_len: usize, + message: *const c_char, + message_len: usize, +) -> napi_status { + let location = if location.is_null() { + None + } else { + unsafe { + Some(if location_len == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(location).to_str().unwrap() + } else { + let slice = std::slice::from_raw_parts( + location as *const u8, + location_len as usize, + ); + std::str::from_utf8(slice).unwrap() + }) + } + }; + + let message = if message_len == NAPI_AUTO_LENGTH { + unsafe { std::ffi::CStr::from_ptr(message).to_str().unwrap() } + } else { + let slice = unsafe { + std::slice::from_raw_parts(message as *const u8, message_len as usize) + }; + std::str::from_utf8(slice).unwrap() + }; + + if let Some(location) = location { + eprintln!("NODE API FATAL ERROR: {} {}", location, message); + } else { + eprintln!("NODE API FATAL ERROR: {}", message); + } + + std::process::abort(); +} + +#[napi_sym] +fn napi_open_callback_scope( + env: *mut Env, + _resource_object: napi_value, + _context: napi_value, + result: *mut napi_callback_scope, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + // we open scope automatically when it's needed + unsafe { + *result = std::ptr::null_mut(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_close_callback_scope( + env: *mut Env, + scope: napi_callback_scope, +) -> napi_status { + let env = check_env!(env); + // we close scope automatically when it's needed + assert!(scope.is_null()); + napi_clear_last_error(env) +} + +// NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops. +#[napi_sym] +fn napi_async_init( + env: *mut Env, + _async_resource: napi_value, + _async_resource_name: napi_value, + result: *mut napi_async_context, +) -> napi_status { + let env = check_env!(env); + unsafe { + *result = ptr::null_mut(); + } + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_async_destroy( + env: *mut Env, + async_context: napi_async_context, +) -> napi_status { + let env = check_env!(env); + assert!(async_context.is_null()); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_make_callback<'s>( + env: &'s mut Env, + _async_context: napi_async_context, + recv: napi_value, + func: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, recv); + if argc > 0 { + check_arg!(env, argv); + } + + let Some(recv) = recv.and_then(|v| v.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let Some(func) = + func.and_then(|v| v8::Local::::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, argc) + } + } else { + &[] + }; + + // TODO: async_context + + let Some(v) = func.call(&mut env.scope(), recv.into(), args) else { + return napi_generic_failure; + }; + + unsafe { + *result = v.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_buffer<'s>( + env: &'s mut Env, + length: usize, + data: *mut *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let ab = v8::ArrayBuffer::new(&mut env.scope(), length); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(ab) as _; + } + } + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_external_buffer<'s>( + env: &'s mut Env, + length: usize, + data: *mut c_void, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let store = make_external_backing_store( + env, + data, + length, + ptr::null_mut(), + finalize_cb, + finalize_hint, + ); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_buffer_copy<'s>( + env: &'s mut Env, + length: usize, + data: *mut c_void, + result_data: *mut *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let ab = v8::ArrayBuffer::new(&mut env.scope(), length); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + let Some(buffer) = + buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) + else { + return napi_generic_failure; + }; + + let ptr = get_array_buffer_ptr(ab) as *mut c_void; + unsafe { + std::ptr::copy(data, ptr, length); + } + + if !result_data.is_null() { + unsafe { + *result_data = ptr; + } + } + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_is_buffer( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + + let Some(is_buffer) = value + .unwrap() + .instance_of(&mut env.scope(), buffer_constructor.into()) + else { + return napi_set_last_error(env, napi_generic_failure); + }; + + unsafe { + *result = is_buffer; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_buffer_info( + env: *mut Env, + value: napi_value, + data: *mut *mut c_void, + length: *mut usize, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + + let Some(ta) = + value.and_then(|v| v8::Local::::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + let buffer_constructor = unsafe { + std::mem::transmute::, v8::Local>( + env.buffer_constructor, + ) + }; + + if !ta + .instance_of(&mut env.scope(), buffer_constructor.into()) + .unwrap_or(false) + { + return napi_set_last_error(env, napi_invalid_arg); + } + + if !data.is_null() { + unsafe { + *data = ta.data(); + } + } + + if !length.is_null() { + unsafe { + *length = ta.byte_length(); + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_node_version( + env: *mut Env, + result: *mut *const napi_node_version, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + const NODE_VERSION: napi_node_version = napi_node_version { + major: 20, + minor: 11, + patch: 1, + release: c"Deno".as_ptr(), + }; + + unsafe { + *result = &NODE_VERSION as *const napi_node_version; + } + + napi_clear_last_error(env) +} + +struct AsyncWork { + state: AtomicU8, + env: *mut Env, + _async_resource: v8::Global, + _async_resource_name: String, + execute: napi_async_execute_callback, + complete: Option, + data: *mut c_void, +} + +impl AsyncWork { + const IDLE: u8 = 0; + const QUEUED: u8 = 1; + const RUNNING: u8 = 2; +} + +#[napi_sym] +fn napi_create_async_work( + env: *mut Env, + async_resource: napi_value, + async_resource_name: napi_value, + execute: Option, + complete: Option, + data: *mut c_void, + result: *mut napi_async_work, +) -> napi_status { + let env_ptr = env; + let env = check_env!(env); + check_arg!(env, execute); + check_arg!(env, result); + + let resource = if let Some(v) = *async_resource { + let Some(resource) = v.to_object(&mut env.scope()) else { + return napi_set_last_error(env, napi_object_expected); + }; + resource + } else { + v8::Object::new(&mut env.scope()) + }; + + let Some(resource_name) = + async_resource_name.and_then(|v| v.to_string(&mut env.scope())) + else { + return napi_set_last_error(env, napi_string_expected); + }; + + let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); + + let work = Box::new(AsyncWork { + state: AtomicU8::new(AsyncWork::IDLE), + env: env_ptr, + _async_resource: v8::Global::new(&mut env.scope(), resource), + _async_resource_name: resource_name, + execute: execute.unwrap(), + complete, + data, + }); + + unsafe { + *result = Box::into_raw(work) as _; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_delete_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + drop(unsafe { Box::::from_raw(work as _) }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_uv_event_loop(env: *mut Env, uv_loop: *mut *mut ()) -> napi_status { + let env = check_env!(env); + check_arg!(env, uv_loop); + // There is no uv_loop in Deno + napi_set_last_error(env, napi_generic_failure) +} + +#[napi_sym] +fn napi_queue_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + let work = unsafe { &*(work as *mut AsyncWork) }; + + let result = + work + .state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { + // allow queue if idle or if running, but not if already queued. + if state == AsyncWork::IDLE || state == AsyncWork::RUNNING { + Some(AsyncWork::QUEUED) + } else { + None + } + }); + + if result.is_err() { + return napi_clear_last_error(env); + } + + let work = SendPtr(work); + + env.add_async_work(move || { + let work = work.take(); + let work = unsafe { &*work }; + + let state = work.state.compare_exchange( + AsyncWork::QUEUED, + AsyncWork::RUNNING, + Ordering::SeqCst, + Ordering::SeqCst, + ); + + if state.is_ok() { + unsafe { + (work.execute)(work.env as _, work.data); + } + + // reset back to idle if its still marked as running + let _ = work.state.compare_exchange( + AsyncWork::RUNNING, + AsyncWork::IDLE, + Ordering::SeqCst, + Ordering::Relaxed, + ); + } + + if let Some(complete) = work.complete { + let status = if state.is_ok() { + napi_ok + } else if state == Err(AsyncWork::IDLE) { + napi_cancelled + } else { + napi_generic_failure + }; + + unsafe { + complete(work.env as _, status, work.data); + } + } + + // `complete` probably deletes this `work`, so don't use it here. + }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_cancel_async_work(env: *mut Env, work: napi_async_work) -> napi_status { + let env = check_env!(env); + check_arg!(env, work); + + let work = unsafe { &*(work as *mut AsyncWork) }; + + let _ = work.state.compare_exchange( + AsyncWork::QUEUED, + AsyncWork::IDLE, + Ordering::SeqCst, + Ordering::Relaxed, + ); + + napi_clear_last_error(env) +} + +extern "C" fn default_call_js_cb( + env: napi_env, + js_callback: napi_value, + _context: *mut c_void, + _data: *mut c_void, +) { + if let Some(js_callback) = *js_callback { + if let Ok(js_callback) = v8::Local::::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>, + _max_queue_size: usize, + thread_count: AtomicUsize, + thread_finalize_data: *mut c_void, + thread_finalize_cb: Option, + context: *mut c_void, + call_js_cb: napi_threadsafe_function_call_js, + _resource: v8::Global, + _resource_name: String, + is_closing: AtomicBool, + is_closed: Arc, + sender: V8CrossThreadTaskSpawner, + is_ref: AtomicBool, +} + +impl Drop for TsFn { + fn drop(&mut self) { + assert!(self + .is_closed + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok()); + + self.unref(); + + if let Some(finalizer) = self.thread_finalize_cb { + unsafe { + (finalizer)(self.env as _, self.thread_finalize_data, ptr::null_mut()); + } + } + } +} + +impl TsFn { + pub fn acquire(&self) -> napi_status { + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + self.thread_count.fetch_add(1, Ordering::Relaxed); + napi_ok + } + + pub fn release( + tsfn: *mut TsFn, + mode: napi_threadsafe_function_release_mode, + ) -> napi_status { + let tsfn = unsafe { &mut *tsfn }; + + let result = tsfn.thread_count.fetch_update( + Ordering::Relaxed, + Ordering::Relaxed, + |x| { + if x == 0 { + None + } else { + Some(x - 1) + } + }, + ); + + if result.is_err() { + return napi_invalid_arg; + } + + if (result == Ok(1) || mode == napi_tsfn_abort) + && tsfn + .is_closing + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let tsfnptr = SendPtr(tsfn); + // drop must be queued in order to preserve ordering consistent + // with Node.js and so that the finalizer runs on the main thread. + tsfn.sender.spawn(move |_| { + let tsfn = unsafe { Box::from_raw(tsfnptr.take() as *mut TsFn) }; + drop(tsfn); + }); + } + + napi_ok + } + + pub fn ref_(&self) -> napi_status { + if self + .is_ref + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let env = unsafe { &mut *self.env }; + env.threadsafe_function_ref(); + } + napi_ok + } + + pub fn unref(&self) -> napi_status { + if self + .is_ref + .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + let env = unsafe { &mut *self.env }; + env.threadsafe_function_unref(); + } + + napi_ok + } + + pub fn call( + &self, + data: *mut c_void, + _mode: napi_threadsafe_function_call_mode, + ) -> napi_status { + // TODO: + // if self.max_queue_size > 0 && queued >= self.max_queue_size { + // if mode == napi_tsfn_blocking { + // wait somehow + // } else { + // return napi_queue_full; + // } + // } + + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + + let is_closed = self.is_closed.clone(); + let tsfn = SendPtr(self); + let data = SendPtr(data); + let context = SendPtr(self.context); + let call_js_cb = self.call_js_cb; + self.sender.spawn(move |scope: &mut v8::HandleScope| { + let data = data.take(); + + // if is_closed then tsfn is freed, don't read from it. + if is_closed.load(Ordering::Relaxed) { + unsafe { + call_js_cb( + std::ptr::null_mut(), + None::>.into(), + context.take() as _, + data as _, + ); + } + } else { + let tsfn = tsfn.take(); + + let tsfn = unsafe { &*tsfn }; + + let func = tsfn.func.as_ref().map(|f| v8::Local::new(scope, f)); + + unsafe { + (tsfn.call_js_cb)( + tsfn.env as _, + func.into(), + tsfn.context, + data as _, + ); + } + } + }); + + napi_ok + } +} + +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_create_threadsafe_function( + env: *mut Env, + func: napi_value, + async_resource: napi_value, + async_resource_name: napi_value, + max_queue_size: usize, + initial_thread_count: usize, + thread_finalize_data: *mut c_void, + thread_finalize_cb: Option, + context: *mut c_void, + call_js_cb: Option, + 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::::try_from(value) else { + return napi_set_last_error(env, napi_function_expected); + }; + Some(v8::Global::new(&mut env.scope(), func)) + } else { + check_arg!(env, call_js_cb); + None + }; + + let resource = if let Some(v) = *async_resource { + let Some(resource) = v.to_object(&mut env.scope()) else { + return napi_set_last_error(env, napi_object_expected); + }; + resource + } else { + v8::Object::new(&mut env.scope()) + }; + let resource = v8::Global::new(&mut env.scope(), resource); + + let Some(resource_name) = + async_resource_name.and_then(|v| v.to_string(&mut env.scope())) + else { + return napi_set_last_error(env, napi_string_expected); + }; + let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); + + let mut tsfn = Box::new(TsFn { + env, + func, + _max_queue_size: max_queue_size, + thread_count: AtomicUsize::new(initial_thread_count), + thread_finalize_data, + thread_finalize_cb, + context, + call_js_cb: call_js_cb.unwrap_or(default_call_js_cb), + _resource: resource, + _resource_name: resource_name, + is_closing: AtomicBool::new(false), + is_closed: Arc::new(AtomicBool::new(false)), + is_ref: AtomicBool::new(false), + sender: env.async_work_sender.clone(), + }); + + tsfn.ref_(); + + unsafe { + *result = Box::into_raw(tsfn) as _; + } + + napi_clear_last_error(env) +} + +/// Maybe called from any thread. +#[napi_sym] +fn napi_get_threadsafe_function_context( + func: napi_threadsafe_function, + result: *mut *const c_void, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *const TsFn) }; + unsafe { + *result = tsfn.context; + } + napi_ok +} + +#[napi_sym] +fn napi_call_threadsafe_function( + func: napi_threadsafe_function, + data: *mut c_void, + is_blocking: napi_threadsafe_function_call_mode, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.call(data, is_blocking) +} + +#[napi_sym] +fn napi_acquire_threadsafe_function( + tsfn: napi_threadsafe_function, +) -> napi_status { + assert!(!tsfn.is_null()); + let tsfn = unsafe { &*(tsfn as *mut TsFn) }; + tsfn.acquire() +} + +#[napi_sym] +fn napi_release_threadsafe_function( + tsfn: napi_threadsafe_function, + mode: napi_threadsafe_function_release_mode, +) -> napi_status { + assert!(!tsfn.is_null()); + TsFn::release(tsfn as _, mode) +} + +#[napi_sym] +fn napi_unref_threadsafe_function( + _env: &mut Env, + func: napi_threadsafe_function, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.unref() +} + +#[napi_sym] +fn napi_ref_threadsafe_function( + _env: &mut Env, + func: napi_threadsafe_function, +) -> napi_status { + assert!(!func.is_null()); + let tsfn = unsafe { &*(func as *mut TsFn) }; + tsfn.ref_() +} + +#[napi_sym] +fn node_api_get_module_file_name( + env: *mut Env, + result: *mut *const c_char, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = env.shared().filename.as_ptr() as _; + } + + napi_clear_last_error(env) +} diff --git a/cli/napi/sym/lib.rs b/cli/napi/sym/lib.rs index 33f039b5fd..e2826306b9 100644 --- a/cli/napi/sym/lib.rs +++ b/cli/napi/sym/lib.rs @@ -20,17 +20,12 @@ pub fn napi_sym(_attr: TokenStream, item: TokenStream) -> TokenStream { let name = &func.sig.ident; assert!( exports.symbols.contains(&name.to_string()), - "tools/napi/sym/symbol_exports.json is out of sync!" + "cli/napi/sym/symbol_exports.json is out of sync!" ); - let block = &func.block; - let inputs = &func.sig.inputs; - let generics = &func.sig.generics; TokenStream::from(quote! { - // SAFETY: it's an NAPI function. - #[no_mangle] - pub unsafe extern "C" fn #name #generics (#inputs) -> napi_status { - #block - } + crate::napi_wrap! { + #func + } }) } diff --git a/cli/napi/sym/symbol_exports.json b/cli/napi/sym/symbol_exports.json index ba1bba67a6..64b548d496 100644 --- a/cli/napi/sym/symbol_exports.json +++ b/cli/napi/sym/symbol_exports.json @@ -36,6 +36,7 @@ "napi_get_threadsafe_function_context", "napi_get_null", "napi_create_string_utf16", + "node_api_create_external_string_utf16", "napi_get_value_bigint_uint64", "napi_module_register", "napi_is_typedarray", @@ -76,6 +77,7 @@ "napi_release_threadsafe_function", "napi_delete_async_work", "napi_create_string_latin1", + "node_api_create_external_string_latin1", "napi_is_array", "napi_unref_threadsafe_function", "napi_throw_error", @@ -139,10 +141,17 @@ "napi_has_element", "napi_create_external_arraybuffer", "napi_create_symbol", + "node_api_symbol_for", "napi_coerce_to_string", "napi_create_type_error", "napi_fatal_exception", "napi_create_async_work", - "napi_async_init" + "napi_async_init", + "node_api_create_property_key_utf16", + "napi_type_tag_object", + "napi_check_object_type_tag", + "node_api_post_finalizer", + "napi_add_async_cleanup_hook", + "napi_remove_async_cleanup_hook" ] } diff --git a/cli/napi/threadsafe_functions.rs b/cli/napi/threadsafe_functions.rs deleted file mode 100644 index 2ab4886276..0000000000 --- a/cli/napi/threadsafe_functions.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::futures::channel::mpsc; -use deno_core::V8CrossThreadTaskSpawner; -use deno_runtime::deno_napi::*; -use once_cell::sync::Lazy; -use std::mem::forget; -use std::ptr::NonNull; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; - -#[repr(transparent)] -pub struct SendPtr(pub *const T); - -unsafe impl Send for SendPtr {} -unsafe impl Sync for SendPtr {} - -static TS_FN_ID_COUNTER: Lazy = Lazy::new(|| AtomicUsize::new(0)); - -pub struct TsFn { - pub id: usize, - pub env: *mut Env, - pub maybe_func: Option>, - pub maybe_call_js_cb: Option, - pub context: *mut c_void, - pub thread_counter: usize, - pub ref_counter: Arc, - finalizer: Option, - finalizer_data: *mut c_void, - sender: V8CrossThreadTaskSpawner, - tsfn_sender: mpsc::UnboundedSender, -} - -impl Drop for TsFn { - fn drop(&mut self) { - let env = unsafe { self.env.as_mut().unwrap() }; - env.remove_threadsafe_function_ref_counter(self.id); - if let Some(finalizer) = self.finalizer { - unsafe { - (finalizer)(self.env as _, self.finalizer_data, ptr::null_mut()); - } - } - } -} - -impl TsFn { - pub fn acquire(&mut self) -> napi_status { - self.thread_counter += 1; - napi_ok - } - - pub fn release(mut self) -> napi_status { - self.thread_counter -= 1; - if self.thread_counter == 0 { - if self - .tsfn_sender - .unbounded_send(ThreadSafeFunctionStatus::Dead) - .is_err() - { - return napi_generic_failure; - } - drop(self); - } else { - forget(self); - } - napi_ok - } - - pub fn ref_(&mut self) -> napi_status { - self - .ref_counter - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - napi_ok - } - - pub fn unref(&mut self) -> napi_status { - let _ = self.ref_counter.fetch_update( - std::sync::atomic::Ordering::SeqCst, - std::sync::atomic::Ordering::SeqCst, - |x| { - if x == 0 { - None - } else { - Some(x - 1) - } - }, - ); - - napi_ok - } - - pub fn call(&self, data: *mut c_void, is_blocking: bool) { - let js_func = self.maybe_func.clone(); - - let env = SendPtr(self.env); - let context = SendPtr(self.context); - let data = SendPtr(data); - - #[inline(always)] - fn spawn( - sender: &V8CrossThreadTaskSpawner, - is_blocking: bool, - f: impl FnOnce(&mut v8::HandleScope) + Send + 'static, - ) { - if is_blocking { - sender.spawn_blocking(f); - } else { - sender.spawn(f); - } - } - - if let Some(call_js_cb) = self.maybe_call_js_cb { - if let Some(func) = js_func { - let func = SendPtr(func.into_raw().as_ptr()); - #[inline(always)] - fn call( - scope: &mut v8::HandleScope, - call_js_cb: napi_threadsafe_function_call_js, - func: SendPtr, - env: SendPtr, - context: SendPtr, - data: SendPtr, - ) { - // SAFETY: This is a valid global from above - let func: v8::Global = unsafe { - v8::Global::::from_raw( - scope, - NonNull::new_unchecked(func.0 as _), - ) - }; - let func: v8::Local = - func.open(scope).to_object(scope).unwrap().into(); - // SAFETY: env is valid for the duration of the callback. - // data lifetime is users responsibility. - unsafe { - call_js_cb(env.0 as _, func.into(), context.0 as _, data.0 as _) - } - } - spawn(&self.sender, is_blocking, move |scope| { - call(scope, call_js_cb, func, env, context, data); - }); - } else { - #[inline(always)] - fn call( - call_js_cb: napi_threadsafe_function_call_js, - env: SendPtr, - context: SendPtr, - data: SendPtr, - ) { - // SAFETY: env is valid for the duration of the callback. - // data lifetime is users responsibility. - unsafe { - call_js_cb( - env.0 as _, - std::mem::zeroed(), - context.0 as _, - data.0 as _, - ) - } - } - spawn(&self.sender, is_blocking, move |_| { - call(call_js_cb, env, context, data); - }); - } - } else { - spawn(&self.sender, is_blocking, |_| { - // TODO: func.call - }); - }; - } -} - -#[napi_sym::napi_sym] -fn napi_create_threadsafe_function( - env: *mut Env, - func: napi_value, - _async_resource: napi_value, - _async_resource_name: napi_value, - _max_queue_size: usize, - initial_thread_count: usize, - thread_finalize_data: *mut c_void, - thread_finalize_cb: Option, - context: *mut c_void, - maybe_call_js_cb: Option, - result: *mut napi_threadsafe_function, -) -> napi_status { - let Some(env_ref) = env.as_mut() else { - return napi_generic_failure; - }; - if initial_thread_count == 0 { - return napi_invalid_arg; - } - - let mut maybe_func = None; - - if let Some(value) = *func { - let Ok(func) = v8::Local::::try_from(value) else { - return napi_function_expected; - }; - maybe_func = Some(v8::Global::new(&mut env_ref.scope(), func)); - } - - let id = TS_FN_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - - let tsfn = TsFn { - id, - maybe_func, - maybe_call_js_cb, - context, - thread_counter: initial_thread_count, - sender: env_ref.async_work_sender.clone(), - finalizer: thread_finalize_cb, - finalizer_data: thread_finalize_data, - tsfn_sender: env_ref.threadsafe_function_sender.clone(), - ref_counter: Arc::new(AtomicUsize::new(1)), - env, - }; - - env_ref - .add_threadsafe_function_ref_counter(tsfn.id, tsfn.ref_counter.clone()); - - if env_ref - .threadsafe_function_sender - .unbounded_send(ThreadSafeFunctionStatus::Alive) - .is_err() - { - return napi_generic_failure; - } - *result = transmute::, _>(Box::new(tsfn)); - - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_acquire_threadsafe_function( - tsfn: napi_threadsafe_function, - _mode: napi_threadsafe_function_release_mode, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn); - tsfn.acquire() -} - -#[napi_sym::napi_sym] -fn napi_unref_threadsafe_function( - _env: &mut Env, - tsfn: napi_threadsafe_function, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(tsfn as *mut TsFn); - tsfn.unref() -} - -/// Maybe called from any thread. -#[napi_sym::napi_sym] -pub fn napi_get_threadsafe_function_context( - func: napi_threadsafe_function, - result: *mut *const c_void, -) -> napi_status { - let tsfn: &TsFn = &*(func as *const TsFn); - *result = tsfn.context; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_call_threadsafe_function( - func: napi_threadsafe_function, - data: *mut c_void, - is_blocking: napi_threadsafe_function_call_mode, -) -> napi_status { - let tsfn: &TsFn = &*(func as *const TsFn); - tsfn.call(data, is_blocking != 0); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_ref_threadsafe_function( - _env: &mut Env, - func: napi_threadsafe_function, -) -> napi_status { - let tsfn: &mut TsFn = &mut *(func as *mut TsFn); - tsfn.ref_() -} - -#[napi_sym::napi_sym] -fn napi_release_threadsafe_function( - tsfn: napi_threadsafe_function, - _mode: napi_threadsafe_function_release_mode, -) -> napi_status { - let tsfn: Box = Box::from_raw(tsfn as *mut TsFn); - tsfn.release() -} diff --git a/cli/napi/util.rs b/cli/napi/util.rs index edf109460f..cd3ef14a2d 100644 --- a/cli/napi/util.rs +++ b/cli/napi/util.rs @@ -1,8 +1,292 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_runtime::deno_napi::*; +use libc::INT_MAX; + +#[repr(transparent)] +pub struct SendPtr(pub *const T); + +impl SendPtr { + // silly function to get around `clippy::redundant_locals` + pub fn take(self) -> *const T { + self.0 + } +} + +unsafe impl Send for SendPtr {} +unsafe impl Sync for SendPtr {} pub fn get_array_buffer_ptr(ab: v8::Local) -> *mut u8 { // SAFETY: Thanks to the null pointer optimization, NonNull and Option> are guaranteed // to have the same size and alignment. unsafe { std::mem::transmute(ab.data()) } } + +struct BufferFinalizer { + env: *mut Env, + finalize_cb: napi_finalize, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +} + +impl Drop for BufferFinalizer { + fn drop(&mut self) { + unsafe { + (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint); + } + } +} + +pub extern "C" fn backing_store_deleter_callback( + data: *mut c_void, + _byte_length: usize, + deleter_data: *mut c_void, +) { + let mut finalizer = + unsafe { Box::::from_raw(deleter_data as _) }; + + finalizer.finalize_data = data; + + drop(finalizer); +} + +pub fn make_external_backing_store( + env: *mut Env, + data: *mut c_void, + byte_length: usize, + finalize_data: *mut c_void, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, +) -> v8::UniqueRef { + let finalizer = Box::new(BufferFinalizer { + env, + finalize_data, + finalize_cb, + finalize_hint, + }); + + unsafe { + v8::ArrayBuffer::new_backing_store_from_ptr( + data, + byte_length, + backing_store_deleter_callback, + Box::into_raw(finalizer) as _, + ) + } +} + +#[macro_export] +macro_rules! check_env { + ($env: expr) => {{ + let env = $env; + if env.is_null() { + return napi_invalid_arg; + } + unsafe { &mut *env } + }}; +} + +#[macro_export] +macro_rules! return_error_status_if_false { + ($env: expr, $condition: expr, $status: ident) => { + if !$condition { + return Err( + $crate::napi::util::napi_set_last_error($env, $status).into(), + ); + } + }; +} + +#[macro_export] +macro_rules! return_status_if_false { + ($env: expr, $condition: expr, $status: ident) => { + if !$condition { + return $crate::napi::util::napi_set_last_error($env, $status); + } + }; +} + +pub(crate) unsafe fn check_new_from_utf8_len<'s>( + env: *mut Env, + str_: *const c_char, + len: usize, +) -> Result, 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, 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, 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 Nullable for *mut T { + fn is_null(&self) -> bool { + (*self).is_null() + } +} + +impl Nullable for *const T { + fn is_null(&self) -> bool { + (*self).is_null() + } +} + +impl Nullable for Option { + fn is_null(&self) -> bool { + self.is_none() + } +} + +impl<'s> Nullable for napi_value<'s> { + fn is_null(&self) -> bool { + self.is_none() + } +} + +// TODO: replace Nullable with some sort of "CheckedUnwrap" trait +// *mut T -> &mut MaybeUninit +// Option -> T +// napi_value -> Local +#[macro_export] +macro_rules! check_arg { + ($env: expr, $ptr: expr) => { + $crate::return_status_if_false!( + $env, + !$crate::napi::util::Nullable::is_null(&$ptr), + napi_invalid_arg + ); + }; +} + +#[macro_export] +macro_rules! napi_wrap { + ( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $env:ident : & $( $lt:lifetime )? mut Env $( , $ident:ident : $ty:ty )* $(,)? ) -> napi_status $body:block ) => { + $( # $attr )* + #[no_mangle] + pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( env_ptr : *mut Env , $( $ident : $ty ),* ) -> napi_status { + let env: & $( $lt )? mut Env = $crate::check_env!(env_ptr); + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + $crate::napi::util::napi_clear_last_error(env); + + let scope_env = unsafe { &mut *env_ptr }; + let scope = &mut scope_env.scope(); + let try_catch = &mut v8::TryCatch::new(scope); + + #[inline(always)] + fn inner $( < $( $x ),* > )? ( $env: & $( $lt )? mut Env , $( $ident : $ty ),* ) -> napi_status $body + + log::trace!("NAPI ENTER: {}", stringify!($name)); + + let result = inner( env, $( $ident ),* ); + + log::trace!("NAPI EXIT: {} {}", stringify!($name), result); + + if let Some(exception) = try_catch.exception() { + let env = unsafe { &mut *env_ptr }; + let global = v8::Global::new(env.isolate(), exception); + env.last_exception = Some(global); + return $crate::napi::util::napi_set_last_error(env_ptr, napi_pending_exception); + } + + if result != napi_ok { + return $crate::napi::util::napi_set_last_error(env_ptr, result); + } + + return result; + } + }; + + ( $( # $attr:tt )* fn $name:ident $( < $( $x:lifetime ),* > )? ( $( $ident:ident : $ty:ty ),* $(,)? ) -> napi_status $body:block ) => { + $( # $attr )* + #[no_mangle] + pub unsafe extern "C" fn $name $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status { + #[inline(always)] + fn inner $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status $body + + log::trace!("NAPI ENTER: {}", stringify!($name)); + + let result = inner( $( $ident ),* ); + + log::trace!("NAPI EXIT: {} {}", stringify!($name), result); + + result + } + }; +} diff --git a/ext/napi/function.rs b/ext/napi/function.rs index 5cc23dcd0f..2d2933b954 100644 --- a/ext/napi/function.rs +++ b/ext/napi/function.rs @@ -27,9 +27,10 @@ impl CallbackInfo { } extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { - let info = unsafe { &*info }; - let args = v8::FunctionCallbackArguments::from_function_callback_info(info); - let mut rv = v8::ReturnValue::from_function_callback_info(info); + let callback_info = unsafe { &*info }; + let args = + v8::FunctionCallbackArguments::from_function_callback_info(callback_info); + let mut rv = v8::ReturnValue::from_function_callback_info(callback_info); // SAFETY: create_function guarantees that the data is a CallbackInfo external. let info_ptr: *mut CallbackInfo = unsafe { let external_value = v8::Local::::cast(args.data()); @@ -43,19 +44,29 @@ extern "C" fn call_fn(info: *const v8::FunctionCallbackInfo) { if let Some(f) = info.cb { // SAFETY: calling user provided function pointer. let value = unsafe { f(info.env, info_ptr as *mut _) }; - // SAFETY: napi_value is represented as v8::Local internally. - rv.set(unsafe { transmute::>(value) }); + if let Some(exc) = unsafe { &mut *(info.env as *mut Env) } + .last_exception + .take() + { + let scope = unsafe { &mut v8::CallbackScope::new(callback_info) }; + let exc = v8::Local::new(scope, exc); + scope.throw_exception(exc); + } + if let Some(value) = *value { + rv.set(value); + } } } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn create_function<'a>( +/// # Safety +/// env_ptr must be valid +pub unsafe fn create_function<'a>( env_ptr: *mut Env, name: Option>, cb: napi_callback, cb_info: napi_callback_info, ) -> v8::Local<'a, v8::Function> { - let env: &mut Env = unsafe { &mut *env_ptr }; + let env = unsafe { &mut *env_ptr }; let scope = &mut env.scope(); let external = v8::External::new( @@ -74,14 +85,15 @@ pub fn create_function<'a>( function } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub fn create_function_template<'a>( +/// # Safety +/// env_ptr must be valid +pub unsafe fn create_function_template<'a>( env_ptr: *mut Env, - name: Option<&str>, + name: Option>, cb: napi_callback, cb_info: napi_callback_info, ) -> v8::Local<'a, v8::FunctionTemplate> { - let env: &mut Env = unsafe { &mut *env_ptr }; + let env = unsafe { &mut *env_ptr }; let scope = &mut env.scope(); let external = v8::External::new( @@ -92,8 +104,7 @@ pub fn create_function_template<'a>( .data(external.into()) .build(scope); - if let Some(name) = name { - let v8str = v8::String::new(scope, name).unwrap(); + if let Some(v8str) = name { function.set_class_name(v8str); } diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs index f4fa33438f..39b303f860 100644 --- a/ext/napi/lib.rs +++ b/ext/napi/lib.rs @@ -8,18 +8,14 @@ use core::ptr::NonNull; use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::futures::channel::mpsc; use deno_core::op2; -use deno_core::parking_lot::Mutex; +use deno_core::ExternalOpsTracker; use deno_core::OpState; use deno_core::V8CrossThreadTaskSpawner; use std::cell::RefCell; -use std::ffi::CString; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; use std::thread_local; #[cfg(unix)] @@ -32,7 +28,6 @@ use libloading::os::windows::*; // `use deno_napi::*` pub use deno_core::v8; pub use std::ffi::CStr; -pub use std::mem::transmute; pub use std::os::raw::c_char; pub use std::os::raw::c_void; pub use std::ptr; @@ -52,6 +47,7 @@ pub type napi_callback_scope = *mut c_void; pub type napi_escapable_handle_scope = *mut c_void; pub type napi_async_cleanup_hook_handle = *mut c_void; pub type napi_async_work = *mut c_void; +pub type napi_async_context = *mut c_void; pub const napi_ok: napi_status = 0; pub const napi_invalid_arg: napi_status = 1; @@ -75,6 +71,35 @@ pub const napi_date_expected: napi_status = 18; pub const napi_arraybuffer_expected: napi_status = 19; pub const napi_detachable_arraybuffer_expected: napi_status = 20; pub const napi_would_deadlock: napi_status = 21; +pub const napi_no_external_buffers_allowed: napi_status = 22; +pub const napi_cannot_run_js: napi_status = 23; + +pub static ERROR_MESSAGES: &[&CStr] = &[ + c"", + c"Invalid argument", + c"An object was expected", + c"A string was expected", + c"A string or symbol was expected", + c"A function was expected", + c"A number was expected", + c"A boolean was expected", + c"An array was expected", + c"Unknown failure", + c"An exception is pending", + c"The async work item was cancelled", + c"napi_escape_handle already called on scope", + c"Invalid handle scope usage", + c"Invalid callback scope usage", + c"Thread-safe function queue is full", + c"Thread-safe function handle is closing", + c"A bigint was expected", + c"A date was expected", + c"An arraybuffer was expected", + c"A detachable arraybuffer was expected", + c"Main thread would deadlock", + c"External buffers are not allowed", + c"Cannot run JavaScript", +]; pub const NAPI_AUTO_LENGTH: usize = usize::MAX; @@ -83,7 +108,9 @@ thread_local! { } type napi_addon_register_func = - extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; + unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; +type napi_register_module_v1 = + unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value; #[repr(C)] #[derive(Clone)] @@ -113,7 +140,7 @@ pub const napi_bigint: napi_valuetype = 9; pub type napi_threadsafe_function_release_mode = i32; pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0; -pub const napi_tsfn_abortext: napi_threadsafe_function_release_mode = 1; +pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1; pub type napi_threadsafe_function_call_mode = i32; @@ -153,6 +180,8 @@ pub const napi_float64_array: napi_typedarray_type = 8; pub const napi_bigint64_array: napi_typedarray_type = 9; pub const napi_biguint64_array: napi_typedarray_type = 10; +#[repr(C)] +#[derive(Clone, Copy, PartialEq)] pub struct napi_type_tag { pub lower: u64, pub upper: u64, @@ -187,6 +216,8 @@ pub type napi_threadsafe_function_call_js = unsafe extern "C" fn( pub type napi_async_cleanup_hook = unsafe extern "C" fn(env: napi_env, data: *mut c_void); +pub type napi_cleanup_hook = unsafe extern "C" fn(data: *mut c_void); + pub type napi_property_attributes = i32; pub const napi_default: napi_property_attributes = 0; @@ -233,17 +264,9 @@ pub struct napi_node_version { pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {} impl PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {} -pub type ThreadsafeFunctionRefCounters = Vec<(usize, Arc)>; pub struct NapiState { // Thread safe functions. - pub active_threadsafe_functions: usize, - pub threadsafe_function_receiver: - mpsc::UnboundedReceiver, - pub threadsafe_function_sender: - mpsc::UnboundedSender, - pub env_cleanup_hooks: - Rc>>, - pub tsfn_ref_counters: Arc>, + pub env_cleanup_hooks: Rc>>, } impl Drop for NapiState { @@ -267,7 +290,10 @@ impl Drop for NapiState { continue; } - (hook.0)(hook.1); + unsafe { + (hook.0)(hook.1); + } + { self .env_cleanup_hooks @@ -277,38 +303,44 @@ impl Drop for NapiState { } } } + +#[repr(C)] +#[derive(Debug)] +pub struct InstanceData { + pub data: *mut c_void, + pub finalize_cb: Option, + pub finalize_hint: *mut c_void, +} + #[repr(C)] #[derive(Debug)] /// Env that is shared between all contexts in same native module. pub struct EnvShared { - pub instance_data: *mut c_void, - pub data_finalize: Option, - pub data_finalize_hint: *mut c_void, + pub instance_data: Option, pub napi_wrap: v8::Global, + pub type_tag: v8::Global, pub finalize: Option, pub finalize_hint: *mut c_void, - pub filename: *const c_char, + pub filename: String, } impl EnvShared { - pub fn new(napi_wrap: v8::Global) -> Self { + pub fn new( + napi_wrap: v8::Global, + type_tag: v8::Global, + filename: String, + ) -> Self { Self { - instance_data: std::ptr::null_mut(), - data_finalize: None, - data_finalize_hint: std::ptr::null_mut(), + instance_data: None, napi_wrap, + type_tag, finalize: None, finalize_hint: std::ptr::null_mut(), - filename: std::ptr::null(), + filename, } } } -pub enum ThreadSafeFunctionStatus { - Alive, - Dead, -} - #[repr(C)] pub struct Env { context: NonNull, @@ -316,46 +348,48 @@ pub struct Env { pub open_handle_scopes: usize, pub shared: *mut EnvShared, pub async_work_sender: V8CrossThreadTaskSpawner, - pub threadsafe_function_sender: - mpsc::UnboundedSender, - pub cleanup_hooks: - Rc>>, - pub tsfn_ref_counters: Arc>, + pub cleanup_hooks: Rc>>, + pub external_ops_tracker: ExternalOpsTracker, pub last_error: napi_extended_error_info, + pub last_exception: Option>, pub global: NonNull, + pub buffer_constructor: NonNull, + pub report_error: NonNull, } unsafe impl Send for Env {} unsafe impl Sync for Env {} impl Env { + #[allow(clippy::too_many_arguments)] pub fn new( isolate_ptr: *mut v8::OwnedIsolate, context: v8::Global, global: v8::Global, + buffer_constructor: v8::Global, + report_error: v8::Global, sender: V8CrossThreadTaskSpawner, - threadsafe_function_sender: mpsc::UnboundedSender, - cleanup_hooks: Rc< - RefCell>, - >, - tsfn_ref_counters: Arc>, + cleanup_hooks: Rc>>, + external_ops_tracker: ExternalOpsTracker, ) -> Self { Self { isolate_ptr, context: context.into_raw(), global: global.into_raw(), + buffer_constructor: buffer_constructor.into_raw(), + report_error: report_error.into_raw(), shared: std::ptr::null_mut(), open_handle_scopes: 0, async_work_sender: sender, - threadsafe_function_sender, cleanup_hooks, - tsfn_ref_counters, + external_ops_tracker, last_error: napi_extended_error_info { error_message: std::ptr::null(), engine_reserved: std::ptr::null_mut(), engine_error_code: 0, error_code: napi_ok, }, + last_exception: None, } } @@ -384,7 +418,9 @@ impl Env { // SAFETY: `v8::Local` is always non-null pointer; the `HandleScope` is // already on the stack, but we don't have access to it. let context = unsafe { - transmute::, v8::Local>(self.context) + std::mem::transmute::, v8::Local>( + self.context, + ) }; // SAFETY: there must be a `HandleScope` on the stack, this is ensured because // we are in a V8 callback or the module has already opened a `HandleScope` @@ -392,20 +428,12 @@ impl Env { unsafe { v8::CallbackScope::new(context) } } - pub fn add_threadsafe_function_ref_counter( - &mut self, - id: usize, - counter: Arc, - ) { - let mut counters = self.tsfn_ref_counters.lock(); - assert!(!counters.iter().any(|(i, _)| *i == id)); - counters.push((id, counter)); + pub fn threadsafe_function_ref(&mut self) { + self.external_ops_tracker.ref_op(); } - pub fn remove_threadsafe_function_ref_counter(&mut self, id: usize) { - let mut counters = self.tsfn_ref_counters.lock(); - let index = counters.iter().position(|(i, _)| *i == id).unwrap(); - counters.remove(index); + pub fn threadsafe_function_unref(&mut self) { + self.external_ops_tracker.unref_op(); } } @@ -415,14 +443,8 @@ deno_core::extension!(deno_napi, op_napi_open

], state = |state| { - let (threadsafe_function_sender, threadsafe_function_receiver) = - mpsc::unbounded::(); state.put(NapiState { - threadsafe_function_sender, - threadsafe_function_receiver, - active_threadsafe_functions: 0, env_cleanup_hooks: Rc::new(RefCell::new(vec![])), - tsfn_ref_counters: Arc::new(Mutex::new(vec![])), }); }, ); @@ -441,69 +463,21 @@ impl NapiPermissions for deno_permissions::PermissionsContainer { } } -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointer Env. -/// - The caller must ensure that the pointer is valid. -/// - The caller must ensure that the pointer is not freed. -pub unsafe fn weak_local( - env_ptr: *mut Env, - value: v8::Local, - data: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, -) -> Option> { - use std::cell::Cell; - - let env = &mut *env_ptr; - - let weak_ptr = Rc::new(Cell::new(None)); - let scope = &mut env.scope(); - - let weak = v8::Weak::with_finalizer( - scope, - value, - Box::new({ - let weak_ptr = weak_ptr.clone(); - move |isolate| { - finalize_cb(env_ptr as _, data as _, finalize_hint as _); - - // Self-deleting weak. - if let Some(weak_ptr) = weak_ptr.get() { - let weak: v8::Weak = - unsafe { v8::Weak::from_raw(isolate, Some(weak_ptr)) }; - drop(weak); - } - } - }), - ); - - let value = weak.to_local(scope); - let raw = weak.into_raw(); - weak_ptr.set(raw); - - value -} - -#[op2] +#[op2(reentrant)] fn op_napi_open( scope: &mut v8::HandleScope<'scope>, op_state: Rc>, #[string] path: String, global: v8::Local<'scope, v8::Value>, + buffer_constructor: v8::Local<'scope, v8::Function>, + report_error: v8::Local<'scope, v8::Function>, ) -> std::result::Result, AnyError> where NP: NapiPermissions + 'static, { // We must limit the OpState borrow because this function can trigger a // re-borrow through the NAPI module. - let ( - async_work_sender, - tsfn_sender, - isolate_ptr, - cleanup_hooks, - tsfn_ref_counters, - ) = { + let (async_work_sender, isolate_ptr, cleanup_hooks, external_ops_tracker) = { let mut op_state = op_state.borrow_mut(); let permissions = op_state.borrow_mut::(); permissions.check(Some(&PathBuf::from(&path)))?; @@ -511,10 +485,9 @@ where let isolate_ptr = op_state.borrow::<*mut v8::OwnedIsolate>(); ( op_state.borrow::().clone(), - napi_state.threadsafe_function_sender.clone(), *isolate_ptr, napi_state.env_cleanup_hooks.clone(), - napi_state.tsfn_ref_counters.clone(), + op_state.external_ops_tracker.clone(), ) }; @@ -522,23 +495,25 @@ where let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name)); let napi_wrap = v8::Global::new(scope, napi_wrap); + let type_tag_name = v8::String::new(scope, "type_tag").unwrap(); + let type_tag = v8::Private::new(scope, Some(type_tag_name)); + let type_tag = v8::Global::new(scope, type_tag); + // The `module.exports` object. let exports = v8::Object::new(scope); - let mut env_shared = EnvShared::new(napi_wrap); - let cstr = CString::new(&*path).unwrap(); - env_shared.filename = cstr.as_ptr(); - std::mem::forget(cstr); + let env_shared = EnvShared::new(napi_wrap, type_tag, path.clone()); let ctx = scope.get_current_context(); let mut env = Env::new( isolate_ptr, v8::Global::new(scope, ctx), v8::Global::new(scope, global), + v8::Global::new(scope, buffer_constructor), + v8::Global::new(scope, report_error), async_work_sender, - tsfn_sender, cleanup_hooks, - tsfn_ref_counters, + external_ops_tracker, ); env.shared = Box::into_raw(Box::new(env_shared)); let env_ptr = Box::into_raw(Box::new(env)) as _; @@ -567,63 +542,30 @@ where slot.take() }); - if let Some(module_to_register) = maybe_module { + let maybe_exports = if let Some(module_to_register) = maybe_module { // SAFETY: napi_register_module guarantees that `module_to_register` is valid. let nm = unsafe { &*module_to_register }; assert_eq!(nm.nm_version, 1); // SAFETY: we are going blind, calling the register function on the other side. - let maybe_exports = unsafe { - (nm.nm_register_func)( - env_ptr, - std::mem::transmute::, napi_value>(exports.into()), - ) - }; - - let exports = if maybe_exports.is_some() { - // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer - // to a value, they have the same layout - unsafe { - std::mem::transmute::>(maybe_exports) - } - } else { - exports.into() - }; - - // NAPI addons can't be unloaded, so we're going to "forget" the library - // object so it lives till the program exit. - std::mem::forget(library); - return Ok(exports); - } - - // Initializer callback. - // SAFETY: we are going blind, calling the register function on the other side. - - let maybe_exports = unsafe { - let Ok(init) = library - .get:: napi_value>(b"napi_register_module_v1") else { - return Err(type_error(format!("Unable to find napi_register_module_v1 symbol in {}", path))); - }; - init( - env_ptr, - std::mem::transmute::, napi_value>(exports.into()), - ) - }; - - let exports = if maybe_exports.is_some() { - // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer - // to a value, they have the same layout - unsafe { - std::mem::transmute::>(maybe_exports) - } + unsafe { (nm.nm_register_func)(env_ptr, exports.into()) } + } else if let Ok(init) = unsafe { + library.get::(b"napi_register_module_v1") + } { + // Initializer callback. + // SAFETY: we are going blind, calling the register function on the other side. + unsafe { init(env_ptr, exports.into()) } } else { - exports.into() + return Err(type_error(format!( + "Unable to find register Node-API module at {}", + path + ))); }; + let exports = maybe_exports.unwrap_or(exports.into()); + // NAPI addons can't be unloaded, so we're going to "forget" the library // object so it lives till the program exit. std::mem::forget(library); + Ok(exports) } diff --git a/ext/napi/value.rs b/ext/napi/value.rs index c1607f2c24..6fb96758c1 100644 --- a/ext/napi/value.rs +++ b/ext/napi/value.rs @@ -37,6 +37,19 @@ where } } +impl<'s, T> From>> for napi_value<'s> +where + v8::Local<'s, T>: Into>, +{ + fn from(v: Option>) -> Self { + if let Some(v) = v { + NapiValue::from(v) + } else { + Self(None, std::marker::PhantomData) + } + } +} + const _: () = { assert!( std::mem::size_of::() == std::mem::size_of::<*mut c_void>() diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index b0e4be89b1..10e89b3399 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -1103,7 +1103,12 @@ Module._extensions[".node"] = function (module, filename) { if (filename.endsWith("fsevents.node")) { throw new Error("Using fsevents module is currently not supported"); } - module.exports = op_napi_open(filename, globalThis); + module.exports = op_napi_open( + filename, + globalThis, + nodeGlobals.Buffer, + reportError, + ); }; function createRequireFromPath(filename) { diff --git a/tests/napi/init_test.js b/tests/napi/init_test.js index 5f25078762..9db99d8a05 100644 --- a/tests/napi/init_test.js +++ b/tests/napi/init_test.js @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "node:buffer"; import { assert, libSuffix } from "./common.js"; const ops = Deno[Deno.internal].core.ops; @@ -8,7 +9,7 @@ Deno.test("ctr initialization (napi_module_register)", { ignore: Deno.build.os == "windows", }, function () { const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname; - const obj = ops.op_napi_open(path, {}); + const obj = ops.op_napi_open(path, {}, Buffer, reportError); assert(obj != null); assert(typeof obj === "object"); }); diff --git a/tests/napi/object_wrap_test.js b/tests/napi/object_wrap_test.js index f79fd08f89..de6391fb1c 100644 --- a/tests/napi/object_wrap_test.js +++ b/tests/napi/object_wrap_test.js @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "node:buffer"; import { assert, assertEquals, loadTestLibrary } from "./common.js"; const objectWrap = loadTestLibrary(); @@ -30,7 +31,7 @@ Deno.test("napi external finalizer", function () { Deno.test("napi external buffer", function () { let buf = objectWrap.test_external_buffer(); - assertEquals(buf, new Uint8Array([1, 2, 3])); + assertEquals(buf, new Buffer([1, 2, 3])); buf = null; }); diff --git a/tests/napi/src/object_wrap.rs b/tests/napi/src/object_wrap.rs index d04107cf0c..8c29caec53 100644 --- a/tests/napi/src/object_wrap.rs +++ b/tests/napi/src/object_wrap.rs @@ -11,7 +11,6 @@ use std::ptr; pub struct NapiObject { counter: i32, - _wrapper: napi_ref, } impl NapiObject { @@ -33,18 +32,14 @@ impl NapiObject { assert_napi_ok!(napi_get_value_int32(env, args[0], &mut value)); - let mut wrapper: napi_ref = ptr::null_mut(); - let obj = Box::new(Self { - counter: value, - _wrapper: wrapper, - }); + let obj = Box::new(Self { counter: value }); assert_napi_ok!(napi_wrap( env, this, Box::into_raw(obj) as *mut c_void, None, ptr::null_mut(), - &mut wrapper, + ptr::null_mut(), )); return this; diff --git a/tools/util.js b/tools/util.js index 1497a28873..251aaa1fa7 100644 --- a/tools/util.js +++ b/tools/util.js @@ -31,11 +31,17 @@ async function getFilesFromGit(baseDir, args) { throw new Error("gitLsFiles failed"); } - const files = output.split("\0").filter((line) => line.length > 0).map( - (filePath) => { - return Deno.realPathSync(join(baseDir, filePath)); - }, - ); + const files = output + .split("\0") + .filter((line) => line.length > 0) + .map((filePath) => { + try { + return Deno.realPathSync(join(baseDir, filePath)); + } catch { + return null; + } + }) + .filter((filePath) => filePath !== null); return files; }