diff options
author | snek <the@snek.dev> | 2024-06-10 09:20:44 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-10 16:20:44 +0000 |
commit | e3b2ee183bc7497ec0432bc764678f5eda6495a7 (patch) | |
tree | 7a5fa0208ef56cb83fa6bae9bad0bc89334ed533 /cli/napi/js_native_api.rs | |
parent | 7c5dbd5d54770dba5e56442b633e9597403ef5da (diff) |
fix: Rewrite Node-API (#24101)
Phase 1 node-api rewrite
Diffstat (limited to 'cli/napi/js_native_api.rs')
-rw-r--r-- | cli/napi/js_native_api.rs | 4933 |
1 files changed, 2842 insertions, 2091 deletions
diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs index 428c4a04a..cbce113dc 100644 --- a/cli/napi/js_native_api.rs +++ b/cli/napi/js_native_api.rs @@ -1,2016 +1,3046 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. #![allow(non_upper_case_globals)] +#![deny(unsafe_op_in_unsafe_fn)] + +const NAPI_VERSION: u32 = 9; use deno_runtime::deno_napi::*; use libc::INT_MAX; -use v8::BackingStore; -use v8::UniqueRef; +use super::util::check_new_from_utf8; +use super::util::check_new_from_utf8_len; use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::v8_name_from_property_descriptor; +use crate::check_arg; +use crate::check_env; use deno_runtime::deno_napi::function::create_function; use deno_runtime::deno_napi::function::create_function_template; use deno_runtime::deno_napi::function::CallbackInfo; +use napi_sym::napi_sym; use std::ptr::NonNull; -#[macro_export] -macro_rules! check_env { - ($env: expr) => { - if $env.is_null() { - return napi_invalid_arg; - } - }; +#[derive(Debug, Clone, Copy, PartialEq)] +enum ReferenceOwnership { + Runtime, + Userland, } -#[inline] -unsafe fn napi_value_unchecked(val: napi_value) -> v8::Local<v8::Value> { - transmute::<napi_value, v8::Local<v8::Value>>(val) +enum ReferenceState { + Strong(v8::Global<v8::Value>), + Weak(v8::Weak<v8::Value>), } -#[macro_export] -macro_rules! return_error_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return Err( - $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ) - .into(), - ); - } - }; +struct Reference { + env: *mut Env, + state: ReferenceState, + ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option<napi_finalize>, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, } -#[macro_export] -macro_rules! return_status_if_false { - ($env: expr, $condition: expr, $status: ident) => { - if !$condition { - return $crate::napi::js_native_api::napi_set_last_error( - $env, - $status, - 0, - std::ptr::null_mut(), - ); +impl Reference { + fn new( + env: *mut Env, + value: v8::Local<v8::Value>, + initial_ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option<napi_finalize>, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, + ) -> Box<Self> { + let isolate = unsafe { &mut *(*env).isolate_ptr }; + + let mut reference = Box::new(Reference { + env, + state: ReferenceState::Strong(v8::Global::new(isolate, value)), + ref_count: initial_ref_count, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + }); + + if initial_ref_count == 0 { + reference.set_weak(); } - }; -} -fn check_new_from_utf8_len<'s>( - env: *mut Env, - str_: *const c_char, - len: usize, -) -> Result<v8::Local<'s, v8::String>, napi_status> { - return_error_status_if_false!( - env, - (len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _, - napi_invalid_arg - ); - return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg); - let string = if len == NAPI_AUTO_LENGTH { - let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str(); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - } else { - let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) }; - let result = std::str::from_utf8(string); - return_error_status_if_false!(env, result.is_ok(), napi_generic_failure); - result.unwrap() - }; - let result = { - let env = unsafe { &mut *env }; - v8::String::new(&mut env.scope(), string) - }; - return_error_status_if_false!(env, result.is_some(), napi_generic_failure); - Ok(result.unwrap()) -} + reference + } -#[inline] -fn check_new_from_utf8<'s>( - env: *mut Env, - str_: *const c_char, -) -> Result<v8::Local<'s, v8::String>, napi_status> { - check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) -} + fn ref_(&mut self) -> u32 { + self.ref_count += 1; + if self.ref_count == 1 { + self.set_strong(); + } + self.ref_count + } -#[macro_export] -macro_rules! status_call { - ($call: expr) => { - let status = $call; - if status != napi_ok { - return status; + fn unref(&mut self) -> u32 { + let old_ref_count = self.ref_count; + if self.ref_count > 0 { + self.ref_count -= 1; } - }; -} + if old_ref_count == 1 && self.ref_count == 0 { + self.set_weak(); + } + self.ref_count + } -// Macro to check napi arguments. -// If nullptr, return napi_invalid_arg. -#[macro_export] -macro_rules! check_arg { - ($env: expr, $ptr: expr) => { - $crate::return_status_if_false!($env, !$ptr.is_null(), napi_invalid_arg); - }; -} + fn reset(&mut self) { + self.finalize_cb = None; + self.finalize_data = std::ptr::null_mut(); + self.finalize_hint = std::ptr::null_mut(); + } -macro_rules! check_arg_option { - ($env: expr, $opt: expr) => { - $crate::return_status_if_false!($env, $opt.is_some(), napi_invalid_arg); - }; -} + fn set_strong(&mut self) { + if let ReferenceState::Weak(w) = &self.state { + let isolate = unsafe { &mut *(*self.env).isolate_ptr }; + if let Some(g) = w.to_global(isolate) { + self.state = ReferenceState::Strong(g); + } + } + } -fn napi_clear_last_error(env: *mut Env) { - let env = unsafe { &mut *env }; - env.last_error.error_code = napi_ok; - env.last_error.engine_error_code = 0; - env.last_error.engine_reserved = std::ptr::null_mut(); - env.last_error.error_message = std::ptr::null_mut(); -} + fn set_weak(&mut self) { + let reference = self as *mut Reference; + if let ReferenceState::Strong(g) = &self.state { + let cb = Box::new(move |_: &mut v8::Isolate| { + Reference::weak_callback(reference) + }); + let isolate = unsafe { &mut *(*self.env).isolate_ptr }; + self.state = + ReferenceState::Weak(v8::Weak::with_finalizer(isolate, g, cb)); + } + } -pub(crate) fn napi_set_last_error( - env: *mut Env, - error_code: napi_status, - engine_error_code: i32, - engine_reserved: *mut c_void, -) -> napi_status { - let env = unsafe { &mut *env }; - env.last_error.error_code = error_code; - env.last_error.engine_error_code = engine_error_code; - env.last_error.engine_reserved = engine_reserved; - error_code -} + fn weak_callback(reference: *mut Reference) { + let reference = unsafe { &mut *reference }; -/// Returns napi_value that represents a new JavaScript Array. -#[napi_sym::napi_sym] -fn napi_create_array(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), 0).into(); - napi_ok -} + let finalize_cb = reference.finalize_cb; + let finalize_data = reference.finalize_data; + let finalize_hint = reference.finalize_hint; + reference.reset(); -#[napi_sym::napi_sym] -fn napi_create_array_with_length( - env: *mut Env, - len: i32, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Array::new(&mut env.scope(), len).into(); - napi_ok -} + if let Some(finalize_cb) = finalize_cb { + unsafe { + finalize_cb(reference.env as _, finalize_data, finalize_hint); + } + } -#[napi_sym::napi_sym] -fn napi_create_arraybuffer( - env: *mut Env, - len: usize, - data: *mut *mut u8, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; + if reference.ownership == ReferenceOwnership::Runtime { + unsafe { drop(Reference::from_raw(reference)) } + } + } - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); + fn into_raw(r: Box<Reference>) -> *mut Reference { + Box::into_raw(r) } - *result = value.into(); - napi_ok + unsafe fn from_raw(r: *mut Reference) -> Box<Reference> { + unsafe { Box::from_raw(r) } + } + + unsafe fn remove(r: *mut Reference) { + let r = unsafe { &mut *r }; + if r.ownership == ReferenceOwnership::Userland { + r.reset(); + } else { + unsafe { drop(Reference::from_raw(r)) } + } + } } -#[napi_sym::napi_sym] -fn napi_create_bigint_int64( +#[napi_sym] +fn napi_get_last_error_info( env: *mut Env, - value: i64, - result: *mut napi_value, + result: *mut *const napi_extended_error_info, ) -> napi_status { - check_env!(env); + let env = check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); + + if env.last_error.error_code == napi_ok { + napi_clear_last_error(env); + } else { + env.last_error.error_message = + ERROR_MESSAGES[env.last_error.error_code as usize].as_ptr(); + } + + unsafe { + *result = &env.last_error; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_bigint_uint64( - env: *mut Env, - value: u64, +#[napi_sym] +fn napi_create_function( + env: &mut Env, + name: *const c_char, + length: usize, + cb: napi_callback, + cb_info: napi_callback_info, result: *mut napi_value, ) -> napi_status { - check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + check_arg!(env, cb); + + let name = if !name.is_null() { + match unsafe { check_new_from_utf8_len(env, name, length) } { + Ok(s) => Some(s), + Err(status) => return status, + } + } else { + None + }; + + unsafe { + *result = create_function(env, name, cb, cb_info).into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_bigint_words( - env: *mut Env, - sign_bit: bool, - word_count: usize, - words: *const u64, - result: *mut napi_value, +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_define_class<'s>( + env: &'s mut Env, + utf8name: *const c_char, + length: usize, + constructor: napi_callback, + callback_data: *mut c_void, + property_count: usize, + properties: *const napi_property_descriptor, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - check_arg!(env, words); - let env = unsafe { &mut *env }; check_arg!(env, result); + check_arg!(env, constructor); - if word_count > INT_MAX as _ { - return napi_invalid_arg; + if property_count > 0 { + check_arg!(env, properties); } - match v8::BigInt::new_from_words( - &mut env.scope(), - sign_bit, - std::slice::from_raw_parts(words, word_count), - ) { - Some(value) => { - *result = value.into(); + let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } { + Ok(string) => string, + Err(status) => return status, + }; + + let tpl = unsafe { + create_function_template(env, Some(name), constructor, callback_data) + }; + + let napi_properties: &[napi_property_descriptor] = if property_count > 0 { + unsafe { std::slice::from_raw_parts(properties, property_count) } + } else { + &[] + }; + let mut static_property_count = 0; + + for p in napi_properties { + if p.attributes & napi_static != 0 { + // Will be handled below + static_property_count += 1; + continue; } - None => { - return napi_invalid_arg; + + let name = match unsafe { v8_name_from_property_descriptor(env, p) } { + Ok(name) => name, + Err(status) => return status, + }; + + let method = p.method; + let getter = p.getter; + let setter = p.setter; + + if getter.is_some() || setter.is_some() { + let getter: Option<v8::Local<v8::FunctionTemplate>> = if getter.is_some() + { + Some(unsafe { create_function_template(env, None, p.getter, p.data) }) + } else { + None + }; + let setter: Option<v8::Local<v8::FunctionTemplate>> = if setter.is_some() + { + Some(unsafe { create_function_template(env, None, p.setter, p.data) }) + } else { + None + }; + + let mut accessor_property = v8::PropertyAttribute::NONE; + if getter.is_some() + && setter.is_some() + && (p.attributes & napi_writable) == 0 + { + accessor_property = + accessor_property | v8::PropertyAttribute::READ_ONLY; + } + if p.attributes & napi_enumerable == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::DONT_ENUM; + } + if p.attributes & napi_configurable == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::DONT_DELETE; + } + + let proto = tpl.prototype_template(&mut env.scope()); + proto.set_accessor_property(name, getter, setter, accessor_property); + } else if method.is_some() { + let function = + unsafe { create_function_template(env, None, p.method, p.data) }; + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, function.into()); + } else { + let proto = tpl.prototype_template(&mut env.scope()); + proto.set(name, p.value.unwrap().into()); } } + let env_ptr = env as *mut Env; + let value: v8::Local<v8::Value> = + tpl.get_function(&mut env.scope()).unwrap().into(); + + unsafe { + *result = value.into(); + } + + if static_property_count > 0 { + let mut static_descriptors = Vec::with_capacity(static_property_count); + + for p in napi_properties { + if p.attributes & napi_static != 0 { + static_descriptors.push(*p); + } + } + + crate::status_call!(unsafe { + napi_define_properties( + env_ptr, + *result, + static_descriptors.len(), + static_descriptors.as_ptr(), + ) + }); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_buffer( +#[napi_sym] +fn napi_get_property_names( env: *mut Env, - len: usize, - data: *mut *mut u8, + object: napi_value, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); + unsafe { + napi_get_all_property_names( + env, + object, + napi_key_include_prototypes, + napi_key_enumerable | napi_key_skip_symbols, + napi_key_numbers_to_strings, + result, + ) } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local<v8::Value> = value.into(); - *result = value.into(); - napi_ok } -#[napi_sym::napi_sym] -fn napi_create_buffer_copy( - env: *mut Env, - len: usize, - data: *mut u8, - result_data: *mut *mut u8, - result: *mut napi_value, +#[napi_sym] +fn napi_get_all_property_names<'s>( + env: &'s mut Env, + object: napi_value, + key_mode: napi_key_collection_mode, + key_filter: napi_key_filter, + key_conversion: napi_key_conversion, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - let ptr = get_array_buffer_ptr(value); - std::ptr::copy(data, ptr, len); - if !result_data.is_null() { - *result_data = ptr; - } - let value = v8::Uint8Array::new(&mut env.scope(), value, 0, len).unwrap(); - let value: v8::Local<v8::Value> = value.into(); - *result = value.into(); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(obj) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let mut filter = v8::PropertyFilter::ALL_PROPERTIES; + + if key_filter & napi_key_writable != 0 { + filter = filter | v8::PropertyFilter::ONLY_WRITABLE; + } + if key_filter & napi_key_enumerable != 0 { + filter = filter | v8::PropertyFilter::ONLY_ENUMERABLE; + } + if key_filter & napi_key_configurable != 0 { + filter = filter | v8::PropertyFilter::ONLY_CONFIGURABLE; + } + if key_filter & napi_key_skip_strings != 0 { + filter = filter | v8::PropertyFilter::SKIP_STRINGS; + } + if key_filter & napi_key_skip_symbols != 0 { + filter = filter | v8::PropertyFilter::SKIP_SYMBOLS; + } + + let key_mode = match key_mode { + napi_key_include_prototypes => v8::KeyCollectionMode::IncludePrototypes, + napi_key_own_only => v8::KeyCollectionMode::OwnOnly, + _ => return napi_invalid_arg, + }; + + let key_conversion = match key_conversion { + napi_key_keep_numbers => v8::KeyConversionMode::KeepNumbers, + napi_key_numbers_to_strings => v8::KeyConversionMode::ConvertToString, + _ => return napi_invalid_arg, + }; + + let filter = v8::GetPropertyNamesArgsBuilder::new() + .mode(key_mode) + .property_filter(filter) + .index_filter(v8::IndexFilter::IncludeIndices) + .key_conversion(key_conversion) + .build(); + + let property_names = match obj.get_property_names(scope, filter) { + Some(n) => n, + None => return napi_generic_failure, + }; + + unsafe { + *result = property_names.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_bool( - env: *mut Env, +#[napi_sym] +fn napi_set_property( + env: &mut Env, + object: napi_value, + key: napi_value, value: napi_value, - result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_boolean(&mut env.scope()); - let value: v8::Local<v8::Value> = coerced.into(); - *result = value.into(); + check_arg!(env, key); + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if object.set(scope, key.unwrap(), value.unwrap()).is_none() { + return napi_generic_failure; + }; + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_number( - env: *mut Env, - value: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_has_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Some(coerced) = value.to_number(&mut env.scope()) else { - return napi_number_expected; + check_arg!(env, key); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let value: v8::Local<v8::Value> = coerced.into(); - *result = value.into(); + + let Some(has) = object.has(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_object( - env: *mut Env, - value: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_get_property<'s>( + env: &'s mut Env, + object: napi_value, + key: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_object(&mut env.scope()).unwrap(); - let value: v8::Local<v8::Value> = coerced.into(); - *result = value.into(); + check_arg!(env, key); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(value) = object.get(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_coerce_to_string( - env: *mut Env, - value: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_delete_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let coerced = value.to_string(&mut env.scope()).unwrap(); - let value: v8::Local<v8::Value> = coerced.into(); - *result = value.into(); + check_arg!(env, key); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(deleted) = object.delete(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + if !result.is_null() { + unsafe { + *result = deleted; + } + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_dataview( - env: *mut Env, - len: usize, - data: *mut *mut u8, - byte_offset: usize, - result: *mut napi_value, +#[napi_sym] +fn napi_has_own_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - check_arg!(env, data); - let env = unsafe { &mut *env }; + check_arg!(env, key); check_arg!(env, result); - let value = v8::ArrayBuffer::new(&mut env.scope(), len); - if !data.is_null() { - *data = get_array_buffer_ptr(value); - } - let context = &mut env.scope().get_current_context(); - let global = context.global(&mut env.scope()); - let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); - let data_view = global.get(&mut env.scope(), data_view_name.into()).unwrap(); - let Ok(data_view) = v8::Local::<v8::Function>::try_from(data_view) else { - return napi_function_expected; + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); - let byte_length = v8::Number::new(&mut env.scope(), len as f64); - let value = data_view - .new_instance( - &mut env.scope(), - &[value.into(), byte_offset.into(), byte_length.into()], - ) - .unwrap(); - let value: v8::Local<v8::Value> = value.into(); - *result = value.into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_date( - env: *mut Env, - time: f64, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value: v8::Local<v8::Value> = - v8::Date::new(&mut env.scope(), time).unwrap().into(); - *result = value.into(); + let Ok(key) = v8::Local::<v8::Name>::try_from(key.unwrap()) else { + return napi_name_expected; + }; + + let Some(has_own) = object.has_own_property(scope, key) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_own; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_double( - env: *mut Env, - value: f64, - result: *mut napi_value, +#[napi_sym] +fn napi_has_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut bool, ) -> napi_status { - check_env!(env); + let env_ptr = env as *mut Env; check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value).into(); + + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; + + let Some(has_property) = object.has(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_property; + } + napi_ok } -fn set_error_code( - env: *mut Env, - error: v8::Local<v8::Value>, - code: napi_value, - code_cstring: *const c_char, +#[napi_sym] +fn napi_set_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + value: napi_value<'s>, ) -> napi_status { - if code.is_some() || !code_cstring.is_null() { - let err_object: v8::Local<v8::Object> = error.try_into().unwrap(); + check_arg!(env, value); + let env_ptr = env as *mut Env; - let code_value: v8::Local<v8::Value> = if code.is_some() { - let mut code_value = unsafe { napi_value_unchecked(code) }; - return_status_if_false!( - env, - code_value.is_string(), - napi_string_expected - ); - code_value - } else { - let name = match check_new_from_utf8(env, code_cstring) { - Ok(s) => s, - Err(status) => return status, - }; - name.into() - }; + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; - let mut scope = unsafe { &mut *env }.scope(); - let code_key = v8::String::new(&mut scope, "code").unwrap(); + let value = value.unwrap(); - if err_object - .set(&mut scope, code_key.into(), code_value) - .is_none() - { - return napi_generic_failure; - } + if !object + .set(&mut env.scope(), key.into(), value) + .unwrap_or(false) + { + return napi_generic_failure; } napi_ok } -#[napi_sym::napi_sym] -fn napi_create_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_get_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); + let env_ptr = env as *mut Env; + + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; + + let Some(value) = object.get(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_type_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_set_element<'s>( + env: &'s mut Env, + object: napi_value<'s>, + index: u32, + value: napi_value<'s>, ) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); - check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::type_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_index(scope, index, value.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_range_error( - env: *mut Env, - code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_has_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, ) -> napi_status { - check_env!(env); - check_arg_option!(env, msg); check_arg!(env, result); - let mut message_value = napi_value_unchecked(msg); - return_status_if_false!(env, message_value.is_string(), napi_string_expected); - let error_obj = v8::Exception::range_error( - &mut unsafe { &mut *env }.scope(), - message_value.try_into().unwrap(), - ); - status_call!(set_error_code(env, error_obj, code, std::ptr::null())); - *result = error_obj.into(); - napi_clear_last_error(env); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_external( - env_ptr: *mut Env, - value: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let external: v8::Local<v8::Value> = - v8::External::new(&mut env.scope(), value).into(); + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(has) = object.has_index(scope, index) else { + return napi_generic_failure; + }; - let value = weak_local(env_ptr, external, value, finalize_cb, finalize_hint); + unsafe { + *result = has; + } - *result = transmute(value); napi_ok } -pub type BackingStoreDeleterCallback = unsafe extern "C" fn( - data: *mut c_void, - byte_length: usize, - deleter_data: *mut c_void, -); +#[napi_sym] +fn napi_get_element<'s>( + env: &'s mut Env, + object: napi_value, + index: u32, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); -extern "C" { - fn v8__ArrayBuffer__NewBackingStore__with_data( - data: *mut c_void, - byte_length: usize, - deleter: BackingStoreDeleterCallback, - deleter_data: *mut c_void, - ) -> *mut BackingStore; -} + let scope = &mut env.scope(); -struct BufferFinalizer { - env: *mut Env, - finalize_cb: napi_finalize, - finalize_data: *mut c_void, - finalize_hint: *mut c_void, -} + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; -impl BufferFinalizer { - fn into_raw(self) -> *mut BufferFinalizer { - Box::into_raw(Box::new(self)) + let Some(value) = object.get_index(scope, index) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); } + + napi_ok } -impl Drop for BufferFinalizer { - fn drop(&mut self) { +#[napi_sym] +fn napi_delete_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, +) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(deleted) = object.delete_index(scope, index) else { + return napi_generic_failure; + }; + + if !result.is_null() { unsafe { - (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint); + *result = deleted; } } -} -pub extern "C" fn backing_store_deleter_callback( - data: *mut c_void, - _byte_length: usize, - deleter_data: *mut c_void, -) { - let mut finalizer = - unsafe { Box::from_raw(deleter_data as *mut BufferFinalizer) }; - - finalizer.finalize_data = data; + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_external_arraybuffer( - env_ptr: *mut Env, - data: *mut c_void, - byte_length: usize, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, +#[napi_sym] +fn napi_define_properties( + env: &mut Env, + object: napi_value, + property_count: usize, + properties: *const napi_property_descriptor, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; + let env_ptr = env as *mut Env; - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, + if property_count > 0 { + check_arg!(env, properties); + } + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let store: UniqueRef<BackingStore> = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); + let properties = if property_count == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(properties, property_count) } + }; + for property in properties { + let property_name = + match unsafe { v8_name_from_property_descriptor(env_ptr, property) } { + Ok(name) => name, + Err(status) => return status, + }; - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value: v8::Local<v8::Value> = ab.into(); + let writable = property.attributes & napi_writable != 0; + let enumerable = property.attributes & napi_enumerable != 0; + let configurable = property.attributes & napi_configurable != 0; + + if property.getter.is_some() || property.setter.is_some() { + let local_getter: v8::Local<v8::Value> = if property.getter.is_some() { + unsafe { + create_function(env_ptr, None, property.getter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + let local_setter: v8::Local<v8::Value> = if property.setter.is_some() { + unsafe { + create_function(env_ptr, None, property.setter, property.data).into() + } + } else { + v8::undefined(scope).into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else if property.method.is_some() { + let method: v8::Local<v8::Value> = { + let function = unsafe { + create_function(env_ptr, None, property.method, property.data) + }; + function.into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(method, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_generic_failure; + } + } else { + let value = property.value.unwrap(); + + if enumerable & writable & configurable { + if !object + .create_data_property(scope, property_name, value) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else { + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(value, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } + } + } - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_external_buffer( - env_ptr: *mut Env, - byte_length: usize, - data: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_value, -) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - let finalizer = BufferFinalizer { - env: env_ptr, - finalize_data: ptr::null_mut(), - finalize_cb, - finalize_hint, +#[napi_sym] +fn napi_object_freeze(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; }; - let store: UniqueRef<BackingStore> = - transmute(v8__ArrayBuffer__NewBackingStore__with_data( - data, - byte_length, - backing_store_deleter_callback, - finalizer.into_raw() as _, - )); + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Frozen) + .unwrap_or(false) + { + return napi_generic_failure; + } - let ab = - v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); - let value = - v8::Uint8Array::new(&mut env.scope(), ab, 0, byte_length).unwrap(); - let value: v8::Local<v8::Value> = value.into(); - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_function( +#[napi_sym] +fn napi_object_seal(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Sealed) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_is_array( env: *mut Env, - name: *const c_char, - length: usize, - cb: napi_callback, - cb_info: napi_callback_info, - result: *mut napi_value, + value: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); + let env = check_env!(env); + check_arg!(env, value); check_arg!(env, result); - check_arg_option!(env, cb); - let name = if let Some(name) = name.as_ref() { - match check_new_from_utf8_len(env, name, length) { - Ok(s) => Some(s), - Err(status) => return status, + let value = value.unwrap(); + + unsafe { + *result = value.is_array(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_array_length( + env: &mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + match v8::Local::<v8::Array>::try_from(value) { + Ok(array) => { + unsafe { + *result = array.length(); + } + napi_ok } - } else { - None - }; + Err(_) => napi_array_expected, + } +} + +#[napi_sym] +fn napi_strict_equals( + env: &mut Env, + lhs: napi_value, + rhs: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, lhs); + check_arg!(env, rhs); + check_arg!(env, result); + + unsafe { + *result = lhs.unwrap().strict_equals(rhs.unwrap()); + } - *result = create_function(env, name, cb, cb_info).into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_create_int32( - env: *mut Env, - value: i32, - result: *mut napi_value, +#[napi_sym] +fn napi_get_prototype<'s>( + env: &'s mut Env, + object: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new(&mut env.scope(), value).into(); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(proto) = object.get_prototype(scope) else { + return napi_generic_failure; + }; + + unsafe { + *result = proto.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_create_uint32( - env: *mut Env, - value: u32, +#[napi_sym] +fn napi_create_object( + env_ptr: *mut Env, result: *mut napi_value, ) -> napi_status { - check_env!(env); + let env = check_env!(env_ptr); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); - napi_ok + + unsafe { + *result = v8::Object::new(&mut env.scope()).into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_int64( - env: *mut Env, - value: i64, +#[napi_sym] +fn napi_create_array( + env_ptr: *mut Env, result: *mut napi_value, ) -> napi_status { - check_env!(env); + let env = check_env!(env_ptr); check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Number::new(&mut env.scope(), value as f64).into(); - napi_ok -} -#[napi_sym::napi_sym] -fn napi_create_object(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = v8::Object::new(&mut env.scope()); - *result = object.into(); - napi_ok + unsafe { + *result = v8::Array::new(&mut env.scope(), 0).into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_promise( - env: *mut Env, - deferred: *mut napi_deferred, - promise_out: *mut napi_value, +#[napi_sym] +fn napi_create_array_with_length( + env_ptr: *mut Env, + length: usize, + result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); - let mut global = v8::Global::new(&mut env.scope(), resolver); - let mut global_ptr = global.into_raw(); - let promise = resolver.get_promise(&mut env.scope()); - *deferred = global_ptr.as_mut() as *mut _ as napi_deferred; - *promise_out = promise.into(); + let env = check_env!(env_ptr); + check_arg!(env, result); - napi_ok + unsafe { + *result = v8::Array::new(&mut env.scope(), length as _).into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_reference( - env: *mut Env, - value: napi_value, - _initial_refcount: u32, - result: *mut napi_ref, +#[napi_sym] +fn napi_create_string_latin1( + env_ptr: *mut Env, + string: *const c_char, + length: usize, + result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); - let value = napi_value_unchecked(value); - let global = v8::Global::new(&mut env.scope(), value); - let mut global_ptr = global.into_raw(); - *result = transmute::<NonNull<v8::Value>, napi_ref>(global_ptr); - napi_ok + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } + } else { + &[] + }; + + let Some(string) = v8::String::new_from_one_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Normal, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_string_latin1( - env: *mut Env, - string: *const u8, +#[napi_sym] +fn napi_create_string_utf8( + env_ptr: *mut Env, + string: *const c_char, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() - .as_bytes() + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } } else { - std::slice::from_raw_parts(string, length) + &[] }; - let Some(v8str) = v8::String::new_from_one_byte( + + let Some(string) = v8::String::new_from_utf8( &mut env.scope(), - string, + buffer, v8::NewStringType::Normal, ) else { - return napi_generic_failure; + return napi_set_last_error(env_ptr, napi_generic_failure); }; - *result = v8str.into(); - napi_ok + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_create_string_utf16( - env: *mut Env, + env_ptr: *mut Env, string: *const u16, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - let s = std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap(); - std::slice::from_raw_parts(s.as_ptr() as *const u16, s.len()) + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) + } } else { - std::slice::from_raw_parts(string, length) + &[] }; - match v8::String::new_from_two_byte( + let Some(string) = v8::String::new_from_two_byte( &mut env.scope(), - string, + buffer, v8::NewStringType::Normal, - ) { - Some(v8str) => { - *result = v8str.into(); - } - None => return napi_generic_failure, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); } - napi_ok + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_string_utf8( - env: *mut Env, - string: *const u8, +#[napi_sym] +fn node_api_create_external_string_latin1( + env_ptr: *mut Env, + _string: *const c_char, + _length: usize, + _nogc_finalize_callback: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, +) -> napi_status { + return napi_set_last_error(env_ptr, napi_generic_failure); +} + +#[napi_sym] +fn node_api_create_external_string_utf16( + env_ptr: *mut Env, + _string: *const u16, + _length: usize, + _nogc_finalize_callback: napi_finalize, + _finalize_hint: *mut c_void, + _result: *mut napi_value, + _copied: *mut bool, +) -> napi_status { + return napi_set_last_error(env_ptr, napi_generic_failure); +} + +#[napi_sym] +fn node_api_create_property_key_utf16( + env_ptr: *mut Env, + string: *const u16, length: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env_ptr); if length > 0 { check_arg!(env, string); } - check_arg!(env, result); - return_status_if_false!( + crate::return_status_if_false!( env, (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, napi_invalid_arg ); - let string = if length == NAPI_AUTO_LENGTH { - std::ffi::CStr::from_ptr(string as *const _) - .to_str() - .unwrap() + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) + } } else { - let string = std::slice::from_raw_parts(string, length); - std::str::from_utf8(string).unwrap() + &[] }; - let v8str = v8::String::new(&mut env.scope(), string).unwrap(); - *result = v8str.into(); + + let Some(string) = v8::String::new_from_two_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Internalized, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_double( + env_ptr: *mut Env, + value: f64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int32( + env_ptr: *mut Env, + value: i32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_uint32( + env_ptr: *mut Env, + value: u32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value as _).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_uint64( + env_ptr: *mut Env, + value: u64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_words<'s>( + env: &'s mut Env, + sign_bit: bool, + word_count: usize, + words: *const u64, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, words); + check_arg!(env, result); + + if word_count > INT_MAX as _ { + return napi_invalid_arg; + } + + match v8::BigInt::new_from_words(&mut env.scope(), sign_bit, unsafe { + std::slice::from_raw_parts(words, word_count) + }) { + Some(value) => unsafe { + *result = value.into(); + }, + None => { + return napi_generic_failure; + } + } napi_ok } -#[napi_sym::napi_sym] -fn napi_create_symbol( +#[napi_sym] +fn napi_get_boolean( env: *mut Env, + value: bool, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::Boolean::new(env.isolate(), value).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_create_symbol( + env_ptr: *mut Env, description: napi_value, result: *mut napi_value, ) -> napi_status { - check_env!(env); + let env = check_env!(env_ptr); check_arg!(env, result); - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); let description = if let Some(d) = *description { - let Some(d) = d.to_string(scope) else { - return napi_string_expected; + let Some(d) = d.to_string(&mut env.scope()) else { + return napi_set_last_error(env, napi_string_expected); }; Some(d) } else { None }; - *result = v8::Symbol::new(scope, description).into(); - napi_ok + + unsafe { + *result = v8::Symbol::new(&mut env.scope(), description).into(); + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_create_typedarray( +#[napi_sym] +fn node_api_symbol_for( env: *mut Env, - ty: napi_typedarray_type, + utf8description: *const c_char, length: usize, - arraybuffer: napi_value, - byte_offset: usize, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let ab = napi_value_unchecked(arraybuffer); - let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(ab) else { - return napi_arraybuffer_expected; - }; - let typedarray: v8::Local<v8::Value> = match ty { - napi_uint8_array => { - v8::Uint8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint8_clamped_array => { - v8::Uint8ClampedArray::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int8_array => { - v8::Int8Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint16_array => { - v8::Uint16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int16_array => { - v8::Int16Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_uint32_array => { - v8::Uint32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_int32_array => { - v8::Int32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float32_array => { - v8::Float32Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_float64_array => { - v8::Float64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_bigint64_array => { - v8::BigInt64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() - } - napi_biguint64_array => { - v8::BigUint64Array::new(&mut env.scope(), ab, byte_offset, length) - .unwrap() - .into() + { + let env = check_env!(env); + check_arg!(env, result); + + let description_string = + match unsafe { check_new_from_utf8_len(env, utf8description, length) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + + unsafe { + *result = + v8::Symbol::for_key(&mut env.scope(), description_string).into(); } - _ => { - return napi_invalid_arg; + } + + napi_clear_last_error(env) +} + +macro_rules! napi_create_error_impl { + ($env_ptr:ident, $code:ident, $msg:ident, $result:ident, $error:ident) => {{ + let env_ptr = $env_ptr; + let code = $code; + let msg = $msg; + let result = $result; + + let env = check_env!(env_ptr); + check_arg!(env, msg); + check_arg!(env, result); + + let Some(message) = + msg.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + let error = v8::Exception::$error(&mut env.scope(), message); + + if let Some(code) = *code { + let error_obj: v8::Local<v8::Object> = error.try_into().unwrap(); + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code) + .unwrap_or(false) + { + return napi_set_last_error(env_ptr, napi_generic_failure); + } + } + + unsafe { + *result = error.into(); } + + return napi_clear_last_error(env_ptr); + }}; +} + +#[napi_sym] +fn napi_create_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, error) +} + +#[napi_sym] +fn napi_create_type_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, type_error) +} + +#[napi_sym] +fn napi_create_range_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, range_error) +} + +#[napi_sym] +fn node_api_create_syntax_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, syntax_error) +} + +pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> { + if value.is_undefined() { + Some(napi_undefined) + } else if value.is_null() { + Some(napi_null) + } else if value.is_external() { + Some(napi_external) + } else if value.is_boolean() { + Some(napi_boolean) + } else if value.is_number() { + Some(napi_number) + } else if value.is_big_int() { + Some(napi_bigint) + } else if value.is_string() { + Some(napi_string) + } else if value.is_symbol() { + Some(napi_symbol) + } else if value.is_function() { + Some(napi_function) + } else if value.is_object() { + Some(napi_object) + } else { + None + } +} + +#[napi_sym] +fn napi_typeof( + env: *mut Env, + value: napi_value, + result: *mut napi_valuetype, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(ty) = get_value_type(value.unwrap()) else { + return napi_set_last_error(env, napi_invalid_arg); }; - *result = typedarray.into(); + + unsafe { + *result = ty; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::undefined(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::null(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_cb_info( + env: *mut Env, + cbinfo: napi_callback_info, + argc: *mut i32, + argv: *mut napi_value, + this_arg: *mut napi_value, + data: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + if !argv.is_null() { + check_arg!(env, argc); + let argc = unsafe { *argc as usize }; + for i in 0..argc { + let mut arg = args.get(i as _); + unsafe { + *argv.add(i) = arg.into(); + } + } + } + + if !argc.is_null() { + unsafe { + *argc = args.length(); + } + } + + if !this_arg.is_null() { + unsafe { + *this_arg = args.this().into(); + } + } + + if !data.is_null() { + unsafe { + *data = cbinfo.cb_info; + } + } + + napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn napi_make_callback( +#[napi_sym] +fn napi_get_new_target( env: *mut Env, - async_context: *mut c_void, + cbinfo: napi_callback_info, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + check_arg!(env, result); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + unsafe { + *result = args.new_target().into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_call_function( + env_ptr: *mut Env, recv: napi_value, func: napi_value, - argc: isize, + argc: usize, argv: *const napi_value, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, recv); - if argc > 0 { + let env = check_env!(env_ptr); + check_arg!(env, recv); + let args = if argc > 0 { check_arg!(env, argv); + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc) + } + } else { + &[] + }; + + let Some(func) = + func.and_then(|f| v8::Local::<v8::Function>::try_from(f).ok()) + else { + return napi_set_last_error(env, napi_function_expected); + }; + + let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + if !result.is_null() { + unsafe { + *result = v.into(); + } } - if !async_context.is_null() { - log::info!("napi_make_callback: async_context is not supported"); + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = std::mem::transmute::<NonNull<v8::Value>, v8::Local<v8::Value>>( + env.global, + ) + .into(); } - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); + return napi_clear_last_error(env); +} - let Ok(func) = v8::Local::<v8::Function>::try_from(func) else { - return napi_function_expected; +#[napi_sym] +fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, error); + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let error = error.unwrap(); + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) +} + +macro_rules! napi_throw_error_impl { + ($env:ident, $code:ident, $msg:ident, $error:ident) => {{ + let env = check_env!($env); + let env_ptr = env as *mut Env; + let code = $code; + let msg = $msg; + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let str_ = match unsafe { check_new_from_utf8(env, msg) } { + Ok(s) => s, + Err(status) => return status, + }; + + let error = v8::Exception::$error(&mut env.scope(), str_); + + if !code.is_null() { + let error_obj: v8::Local<v8::Object> = error.try_into().unwrap(); + let code = match unsafe { check_new_from_utf8(env_ptr, code) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code.into()) + .unwrap_or(false) + { + return napi_set_last_error(env, napi_generic_failure); + } + } + + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) + }}; +} + +#[napi_sym] +fn napi_throw_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, error) +} + +#[napi_sym] +fn napi_throw_type_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, type_error) +} + +#[napi_sym] +fn napi_throw_range_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, range_error) +} + +#[napi_sym] +fn node_api_throw_syntax_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, syntax_error) +} + +#[napi_sym] +fn napi_is_error( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_native_error(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_value_double( + env_ptr: *mut Env, + value: napi_value, + result: *mut f64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(number) = + value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); }; - let argv: &[v8::Local<v8::Value>] = - transmute(std::slice::from_raw_parts(argv, argc as usize)); - let ret = func.call(&mut env.scope(), recv, argv); - *result = transmute::<Option<v8::Local<v8::Value>>, napi_value>(ret); - napi_ok + + unsafe { + *result = number.value(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_int32( + env_ptr: *mut Env, + value: napi_value, + result: *mut i32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().int32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); + }; + + unsafe { + *result = value; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_uint32( + env_ptr: *mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().uint32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); + }; + + unsafe { + *result = value; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_int64( + env_ptr: *mut Env, + value: napi_value, + result: *mut i64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(number) = + value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); + }; + + let value = number.value(); + + unsafe { + if value.is_finite() { + *result = value as _; + } else { + *result = 0; + } + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_int64( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut i64, lossless: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + let (result_, lossless_) = bigint.i64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_uint64( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut u64, lossless: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + let (result_, lossless_) = bigint.u64_value(); - *result = result_; - *lossless = lossless_; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bigint_words( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, sign_bit: *mut i32, word_count: *mut usize, words: *mut u64, ) -> napi_status { - check_env!(env); - // TODO(bartlomieju): - // check_arg!(env, value); + let env = check_env!(env_ptr); + check_arg!(env, value); check_arg!(env, word_count); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let big = match value.to_big_int(&mut env.scope()) { - Some(b) => b, - None => return napi_bigint_expected, + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); }; + let word_count_int; if sign_bit.is_null() && words.is_null() { - word_count_int = big.word_count(); + word_count_int = bigint.word_count(); } else { check_arg!(env, sign_bit); check_arg!(env, words); - let out_words = std::slice::from_raw_parts_mut(words, *word_count); - let (sign, slice_) = big.to_words_array(out_words); + let out_words = + unsafe { std::slice::from_raw_parts_mut(words, *word_count) }; + let (sign, slice_) = bigint.to_words_array(out_words); word_count_int = slice_.len(); - *sign_bit = sign as i32; + unsafe { + *sign_bit = sign as i32; + } } - *word_count = word_count_int; - // TODO(bartlomieju): - // napi_clear_last_error() - napi_ok + unsafe { + *word_count = word_count_int; + } + + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_bool( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.boolean_value(&mut env.scope()); - napi_ok -} + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_get_value_double( - env: *mut Env, - value: napi_value, - result: *mut f64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_number(), napi_number_expected); - *result = value.number_value(&mut env.scope()).unwrap(); - napi_ok -} + let Some(boolean) = + value.and_then(|v| v8::Local::<v8::Boolean>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_boolean_expected); + }; -#[napi_sym::napi_sym] -fn napi_get_value_external( - _env: *mut Env, - value: napi_value, - result: *mut *mut c_void, -) -> napi_status { - let value = napi_value_unchecked(value); - let ext = v8::Local::<v8::External>::try_from(value).unwrap(); - *result = ext.value(); - napi_ok -} + unsafe { + *result = boolean.is_true(); + } -#[napi_sym::napi_sym] -fn napi_get_value_int32( - env: *mut Env, - value: napi_value, - result: *mut i32, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.int32_value(&mut env.scope()).unwrap(); - napi_ok + return napi_clear_last_error(env_ptr); } -#[napi_sym::napi_sym] -fn napi_get_value_int64( - env: *mut Env, - value: napi_value, - result: *mut i64, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.integer_value(&mut env.scope()).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_latin1( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, - buf: *mut u8, + buf: *mut c_char, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } + let env = check_env!(env_ptr); + check_arg!(env, value); - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_one_byte( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_one_byte( &mut env.scope(), buffer, 0, v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } } } else if !result.is_null() { - *result = string_len; + unsafe { + *result = 0; + } } - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_utf8( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, buf: *mut u8, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); + let env = check_env!(env_ptr); + check_arg!(env, value); - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } - - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.utf8_length(&mut env.scope()); + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write_utf8( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_utf8( &mut env.scope(), buffer, None, - v8::WriteOptions::NO_NULL_TERMINATION - | v8::WriteOptions::REPLACE_INVALID_UTF8, + v8::WriteOptions::REPLACE_INVALID_UTF8 + | v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } } } else if !result.is_null() { - *result = string_len; + unsafe { + *result = 0; + } } - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_value_string_utf16( - env: *mut Env, + env_ptr: *mut Env, value: napi_value, buf: *mut u16, bufsize: usize, result: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - let value = napi_value_unchecked(value); - - if !value.is_string() && !value.is_string_object() { - return napi_string_expected; - } + let env = check_env!(env_ptr); + check_arg!(env, value); - let v8str = value.to_string(&mut env.scope()).unwrap(); - let string_len = v8str.length(); + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; if buf.is_null() { - *result = string_len; + check_arg!(env, result); + unsafe { + *result = value.length(); + } } else if bufsize != 0 { - let buffer = std::slice::from_raw_parts_mut(buf, bufsize - 1); - let copied = v8str.write( + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write( &mut env.scope(), buffer, 0, v8::WriteOptions::NO_NULL_TERMINATION, ); - buf.add(copied).write(0); + unsafe { + buf.add(copied).write(0); + } if !result.is_null() { - *result = copied; + unsafe { + *result = copied; + } } } else if !result.is_null() { - *result = string_len; + unsafe { + *result = 0; + } } - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_value_uint32( - env: *mut Env, +#[napi_sym] +fn napi_coerce_to_bool<'s>( + env: &'s mut Env, value: napi_value, - result: *mut u32, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - *result = value.uint32_value(&mut env.scope()).unwrap(); + check_arg!(env, value); + check_arg!(env, result); + + let coerced = value.unwrap().to_boolean(&mut env.scope()); + + unsafe { + *result = coerced.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_add_finalizer( - env_ptr: *mut Env, - js_object: napi_value, - native_object: *mut c_void, - finalize_cb: napi_finalize, - finalize_hint: *mut c_void, - result: *mut napi_ref, +#[napi_sym] +fn napi_coerce_to_number<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); - let value = napi_value_unchecked(js_object); - let value = - weak_local(env_ptr, value, native_object, finalize_cb, finalize_hint); + let Some(coerced) = value.unwrap().to_number(&mut env.scope()) else { + return napi_number_expected; + }; - if !result.is_null() { - *result = transmute(value); + unsafe { + *result = coerced.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_adjust_external_memory( - env: *mut Env, - change_in_bytes: i64, - adjusted_value: *mut i64, +#[napi_sym] +fn napi_coerce_to_object<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - check_arg!(env, adjusted_value); + check_arg!(env, value); + check_arg!(env, result); - let env = unsafe { &mut *env }; - let isolate = &mut *env.isolate_ptr; - *adjusted_value = - isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); + let Some(coerced) = value.unwrap().to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + unsafe { + *result = coerced.into(); + } - napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn napi_call_function( - env: *mut Env, - recv: napi_value, - func: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_coerce_to_string<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let recv = napi_value_unchecked(recv); - let func = napi_value_unchecked(func); - let Ok(func) = v8::Local::<v8::Function>::try_from(func) else { - return napi_function_expected; + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_string(&mut env.scope()) else { + return napi_string_expected; }; - let argv: &[v8::Local<v8::Value>] = - transmute(std::slice::from_raw_parts(argv, argc)); - let ret = func.call(&mut env.scope(), recv, argv); - if !result.is_null() { - *result = transmute::<Option<v8::Local<v8::Value>>, napi_value>(ret); + unsafe { + *result = coerced.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_close_escapable_handle_scope( - _env: *mut Env, - _scope: napi_escapable_handle_scope, +#[napi_sym] +fn napi_wrap( + env: &mut Env, + js_object: napi_value, + native_object: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_ref, ) -> napi_status { - // TODO: do this properly - napi_ok -} + check_arg!(env, js_object); + let env_ptr = env as *mut Env; -#[napi_sym::napi_sym] -fn napi_close_handle_scope( - env: *mut Env, - _scope: napi_handle_scope, -) -> napi_status { - let env = &mut *env; - if env.open_handle_scopes == 0 { - return napi_handle_scope_mismatch; + let Some(obj) = + js_object.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + + if obj + .has_private(&mut env.scope(), napi_wrap) + .unwrap_or(false) + { + return napi_invalid_arg; + } + + if !result.is_null() { + check_arg!(env, finalize_cb); } - // TODO: We are not opening a handle scope, therefore we cannot close it - // TODO: this is also not implemented in napi_open_handle_scope - // let _scope = &mut *(scope as *mut v8::HandleScope); - env.open_handle_scopes -= 1; + + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland + }; + let reference = Reference::new( + env_ptr, + obj.into(), + 0, + ownership, + finalize_cb, + native_object, + finalize_hint, + ); + + let reference = Reference::into_raw(reference) as *mut c_void; + + if !result.is_null() { + check_arg!(env, finalize_cb); + unsafe { + *result = reference; + } + } + + let external = v8::External::new(&mut env.scope(), reference); + assert!(obj + .set_private(&mut env.scope(), napi_wrap, external.into()) + .unwrap()); + napi_ok } -#[napi_sym::napi_sym] -fn napi_define_class( - env_ptr: *mut Env, - name: *const c_char, - length: isize, - constructor: napi_callback, - callback_data: *mut c_void, - property_count: usize, - properties: *const napi_property_descriptor, - result: *mut napi_value, +fn unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, + keep: bool, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - check_arg!(env, result); - check_arg_option!(env, constructor); - - if property_count > 0 { - check_arg!(env, properties); + check_arg!(env, obj); + if keep { + check_arg!(env, result); } - let name = if length == -1 { - let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() else { - return napi_invalid_arg; - }; - s - } else { - let slice = std::slice::from_raw_parts(name as *const u8, length as usize); - std::str::from_utf8(slice).unwrap() + let Some(obj) = obj.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + let Some(val) = obj.get_private(&mut env.scope(), napi_wrap) else { + return napi_invalid_arg; }; - let tpl = - create_function_template(env_ptr, Some(name), constructor, callback_data); + let Ok(external) = v8::Local::<v8::External>::try_from(val) else { + return napi_invalid_arg; + }; - let scope = &mut env.scope(); - let napi_properties: &[napi_property_descriptor] = - std::slice::from_raw_parts(properties, property_count); - let mut static_property_count = 0; + let reference = external.value() as *mut Reference; + let reference = unsafe { &mut *reference }; - for p in napi_properties { - if p.attributes & napi_static != 0 { - // Will be handled below - static_property_count += 1; - continue; + if !result.is_null() { + unsafe { + *result = reference.finalize_data; } + } - let name = if !p.utf8name.is_null() { - let name_str = CStr::from_ptr(p.utf8name).to_str().unwrap(); - v8::String::new(scope, name_str).unwrap() - } else { - transmute::<napi_value, v8::Local<v8::String>>(p.name) - }; + if !keep { + assert!(obj + .delete_private(&mut env.scope(), napi_wrap) + .unwrap_or(false)); + unsafe { Reference::remove(reference) }; + } - let method = p.method; - let getter = p.getter; - let setter = p.setter; + napi_ok +} - if getter.is_some() || setter.is_some() { - let getter: Option<v8::Local<v8::FunctionTemplate>> = if getter.is_some() - { - Some(create_function_template(env_ptr, None, p.getter, p.data)) - } else { - None - }; - let setter: Option<v8::Local<v8::FunctionTemplate>> = if setter.is_some() - { - Some(create_function_template(env_ptr, None, p.setter, p.data)) - } else { - None - }; +#[napi_sym] +fn napi_unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, +) -> napi_status { + unwrap(env, obj, result, true) +} - let mut accessor_property = v8::PropertyAttribute::NONE; - if getter.is_some() - && setter.is_some() - && (p.attributes & napi_writable) == 0 - { - accessor_property = - accessor_property | v8::PropertyAttribute::READ_ONLY; - } - if p.attributes & napi_enumerable == 0 { - accessor_property = - accessor_property | v8::PropertyAttribute::DONT_ENUM; - } - if p.attributes & napi_configurable == 0 { - accessor_property = - accessor_property | v8::PropertyAttribute::DONT_DELETE; - } +#[napi_sym] +fn napi_remove_wrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, +) -> napi_status { + unwrap(env, obj, result, false) +} - let proto = tpl.prototype_template(scope); - proto.set_accessor_property( - name.into(), - getter, - setter, - accessor_property, - ); - } else if method.is_some() { - let function = create_function_template(env_ptr, None, p.method, p.data); - let proto = tpl.prototype_template(scope); - proto.set(name.into(), function.into()); - } else { - let proto = tpl.prototype_template(scope); - proto.set( - name.into(), - transmute::<napi_value, v8::Local<v8::Data>>(p.value), - ); - } - } +struct ExternalWrapper { + data: *mut c_void, + type_tag: Option<napi_type_tag>, +} - let value: v8::Local<v8::Value> = tpl.get_function(scope).unwrap().into(); - *result = value.into(); +#[napi_sym] +fn napi_create_external<'s>( + env: &'s mut Env, + data: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + let env_ptr = env as *mut Env; + check_arg!(env, result); - if static_property_count > 0 { - let mut static_descriptors = Vec::with_capacity(static_property_count); + let wrapper = Box::new(ExternalWrapper { + data, + type_tag: None, + }); - for p in napi_properties { - if p.attributes & napi_static != 0 { - static_descriptors.push(*p); - } - } + let wrapper = Box::into_raw(wrapper); + let external = v8::External::new(&mut env.scope(), wrapper as _); - status_call!(napi_define_properties( + if let Some(finalize_cb) = finalize_cb { + Reference::into_raw(Reference::new( env_ptr, - *result, - static_descriptors.len(), - static_descriptors.as_ptr(), + external.into(), + 0, + ReferenceOwnership::Runtime, + Some(finalize_cb), + data, + finalize_hint, )); } + unsafe { + *result = external.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_define_properties( - env_ptr: *mut Env, - obj: napi_value, - property_count: usize, - properties: *const napi_property_descriptor, +#[napi_sym] +fn napi_type_tag_object( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, ) -> napi_status { - check_env!(env_ptr); - let env = unsafe { &mut *env_ptr }; - if property_count > 0 { - check_arg!(env, properties); + check_arg!(env, object_or_external); + check_arg!(env, type_tag); + + let val = object_or_external.unwrap(); + + if let Ok(external) = v8::Local::<v8::External>::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + if wrapper.type_tag.is_some() { + return napi_invalid_arg; + } + wrapper.type_tag = Some(unsafe { *type_tag }); + return napi_ok; } - let scope = &mut env.scope(); + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); - let Ok(object) = v8::Local::<v8::Object>::try_from(napi_value_unchecked(obj)) + if object.has_private(&mut env.scope(), key).unwrap_or(false) { + return napi_invalid_arg; + } + + let slice = unsafe { std::slice::from_raw_parts(type_tag as *const u64, 2) }; + let Some(tag) = v8::BigInt::new_from_words(&mut env.scope(), false, slice) else { - return napi_object_expected; + return napi_generic_failure; }; - let properties = std::slice::from_raw_parts(properties, property_count); - for property in properties { - let name = if !property.utf8name.is_null() { - let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap(); - let Some(name_v8_str) = v8::String::new(scope, name_str) else { - return napi_generic_failure; - }; - name_v8_str.into() - } else { - let property_value = napi_value_unchecked(property.name); - let Ok(prop) = v8::Local::<v8::Name>::try_from(property_value) else { - return napi_name_expected; + if !object + .set_private(&mut env.scope(), key, tag.into()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_check_object_type_tag( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, + result: *mut bool, +) -> napi_status { + check_arg!(env, object_or_external); + check_arg!(env, type_tag); + check_arg!(env, result); + + let type_tag = unsafe { *type_tag }; + + let val = object_or_external.unwrap(); + + if let Ok(external) = v8::Local::<v8::External>::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + unsafe { + *result = match wrapper.type_tag { + Some(t) => t == type_tag, + None => false, }; - prop }; + return napi_ok; + } - if property.getter.is_some() || property.setter.is_some() { - let local_getter: v8::Local<v8::Value> = if property.getter.is_some() { - create_function(env_ptr, None, property.getter, property.data).into() - } else { - v8::undefined(scope).into() - }; - let local_setter: v8::Local<v8::Value> = if property.setter.is_some() { - create_function(env_ptr, None, property.setter, property.data).into() - } else { - v8::undefined(scope).into() - }; + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; + }; - let mut desc = - v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); - desc.set_enumerable(property.attributes & napi_enumerable != 0); - desc.set_configurable(property.attributes & napi_configurable != 0); + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); - let define_maybe = object.define_property(scope, name, &desc); - return_status_if_false!( - env_ptr, - define_maybe.is_some(), - napi_invalid_arg - ); - } else if property.method.is_some() { - let value: v8::Local<v8::Value> = { - let function = - create_function(env_ptr, None, property.method, property.data); - function.into() + let Some(val) = object.get_private(&mut env.scope(), key) else { + return napi_generic_failure; + }; + + unsafe { + *result = false; + } + + if let Ok(bigint) = v8::Local::<v8::BigInt>::try_from(val) { + let mut words = [0u64; 2]; + let (sign, words) = bigint.to_words_array(&mut words); + if !sign { + let pass = if words.len() == 2 { + type_tag.lower == words[0] && type_tag.upper == words[1] + } else if words.len() == 1 { + type_tag.lower == words[0] && type_tag.upper == 0 + } else if words.is_empty() { + type_tag.lower == 0 && type_tag.upper == 0 + } else { + false }; - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); - } else { - let value = napi_value_unchecked(property.value); - return_status_if_false!( - env_ptr, - object.set(scope, name.into(), value).is_some(), - napi_invalid_arg - ); + unsafe { + *result = pass; + } } } napi_ok } -#[napi_sym::napi_sym] -fn napi_delete_element( +#[napi_sym] +fn napi_get_value_external( env: *mut Env, value: napi_value, - index: u32, - result: *mut bool, + result: *mut *mut c_void, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.delete_index(&mut env.scope(), index).unwrap_or(false); - napi_ok + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(external) = + value.and_then(|v| v8::Local::<v8::External>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + let wrapper_ptr = external.value() as *const ExternalWrapper; + let wrapper = unsafe { &*wrapper_ptr }; + + unsafe { + *result = wrapper.data; + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_delete_property( +#[napi_sym] +fn napi_create_reference( env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, + value: napi_value, + initial_refcount: u32, + result: *mut napi_ref, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + let env = check_env!(env); + check_arg!(env, value); check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; + let value = value.unwrap(); - let Some(deleted) = object.delete(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; + let reference = Reference::new( + env, + value, + initial_refcount, + ReferenceOwnership::Userland, + None, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); - *result = deleted; - napi_ok + let ptr = Reference::into_raw(reference); + + unsafe { + *result = ptr as _; + } + + napi_clear_last_error(env) } -// TODO: properly implement ref counting stuff -#[napi_sym::napi_sym] -fn napi_delete_reference(_env: *mut Env, _nref: napi_ref) -> napi_status { - napi_ok +#[napi_sym] +fn napi_delete_reference(env: *mut Env, ref_: napi_ref) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); + + let reference = unsafe { Reference::from_raw(ref_ as _) }; + + drop(reference); + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); +#[napi_sym] +fn napi_reference_ref( + env: *mut Env, + ref_: napi_ref, + result: *mut u32, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); - let value = napi_value_unchecked(value); - let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(value) else { - return napi_arraybuffer_expected; - }; + let reference = unsafe { &mut *(ref_ as *mut Reference) }; - if !ab.is_detachable() { - return napi_detachable_arraybuffer_expected; - } + let count = reference.ref_(); - // Expected to crash for None. - ab.detach(None).unwrap(); + if !result.is_null() { + unsafe { + *result = count; + } + } - napi_clear_last_error(env); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_escape_handle<'s>( - _env: *mut Env, - _handle_scope: napi_escapable_handle_scope, - escapee: napi_value<'s>, - result: *mut napi_value<'s>, +#[napi_sym] +fn napi_reference_unref( + env: *mut Env, + ref_: napi_ref, + result: *mut u32, ) -> napi_status { - // TODO - *result = escapee; - napi_ok -} + let env = check_env!(env); + check_arg!(env, ref_); -#[napi_sym::napi_sym] -fn napi_get_all_property_names(_env: *mut Env) -> napi_status { - // TODO - napi_ok + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + if reference.ref_count == 0 { + return napi_set_last_error(env, napi_generic_failure); + } + + let count = reference.unref(); + + if !result.is_null() { + unsafe { + *result = count; + } + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_and_clear_last_exception( - env: *mut Env, +#[napi_sym] +fn napi_get_reference_value( + env_ptr: *mut Env, + ref_: napi_ref, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - // TODO: just return undefined for now we don't cache - // exceptions in env. - let value: v8::Local<v8::Value> = v8::undefined(&mut env.scope()).into(); - *result = value.into(); - napi_ok + let env = check_env!(env_ptr); + check_arg!(env, ref_); + check_arg!(env, result); + + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + let value = match &reference.state { + ReferenceState::Strong(g) => Some(v8::Local::new(&mut env.scope(), g)), + ReferenceState::Weak(w) => w.to_local(&mut env.scope()), + }; + + unsafe { + *result = value.into(); + } + + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_array_length( - _env: *mut Env, - value: napi_value, - result: *mut u32, +#[napi_sym] +fn napi_open_handle_scope( + env: *mut Env, + _result: *mut napi_handle_scope, ) -> napi_status { - let value = napi_value_unchecked(value); - *result = v8::Local::<v8::Array>::try_from(value).unwrap().length(); - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_arraybuffer_info( - _env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, +#[napi_sym] +fn napi_close_handle_scope( + env: *mut Env, + _scope: napi_handle_scope, ) -> napi_status { - let value = napi_value_unchecked(value); - let buf = v8::Local::<v8::ArrayBuffer>::try_from(value).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf); - } - if !length.is_null() { - *length = buf.byte_length(); - } - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_boolean( +#[napi_sym] +fn napi_open_escapable_handle_scope( env: *mut Env, - value: bool, - result: *mut napi_value, + _result: *mut napi_escapable_handle_scope, ) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::Boolean::new(env.isolate(), value).into(); - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_buffer_info( +#[napi_sym] +fn napi_close_escapable_handle_scope( env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, + _scope: napi_escapable_handle_scope, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::<v8::ArrayBufferView>::try_from(value) else { - return napi_arraybuffer_expected; - }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::<v8::ArrayBuffer>::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_arraybuffer_expected; - }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); - } - if !length.is_null() { - *length = abuf.byte_length(); - } - napi_ok + let env = check_env!(env); + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_cb_info( +#[napi_sym] +fn napi_escape_handle<'s>( env: *mut Env, - cbinfo: napi_callback_info, - argc: *mut i32, - argv: *mut napi_value, - this_arg: *mut napi_value, - data: *mut *mut c_void, + _scope: napi_escapable_handle_scope, + escapee: napi_value<'s>, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg!(env, cbinfo); + let env = check_env!(env); - let cbinfo: &CallbackInfo = &*(cbinfo as *const CallbackInfo); - let args = &*(cbinfo.args as *const v8::FunctionCallbackArguments); - - if !argv.is_null() { - check_arg!(env, argc); - let mut v_argv = std::slice::from_raw_parts_mut(argv, argc as usize); - for i in 0..*argc { - let mut arg = args.get(i); - v_argv[i as usize] = arg.into(); - } + unsafe { + *result = escapee; } - if !argc.is_null() { - *argc = args.length(); - } + napi_clear_last_error(env) +} - if !this_arg.is_null() { - let mut this = args.this(); - *this_arg = this.into(); +#[napi_sym] +fn napi_new_instance<'s>( + env: &'s mut Env, + constructor: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, constructor); + if argc > 0 { + check_arg!(env, argv); } + check_arg!(env, result); - if !data.is_null() { - *data = cbinfo.cb_info; + let Some(func) = + constructor.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let args = if argc > 0 { + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc) + } + } else { + &[] + }; + + let Some(value) = func.new_instance(&mut env.scope(), args) else { + return napi_pending_exception; + }; + + unsafe { + *result = value.into(); } - napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn napi_get_dataview_info( - env: *mut Env, - value: napi_value, - data: *mut *mut u8, - length: *mut usize, +#[napi_sym] +fn napi_instanceof( + env: &mut Env, + object: napi_value, + constructor: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let Ok(buf) = v8::Local::<v8::DataView>::try_from(value) else { - return napi_invalid_arg; + check_arg!(env, object); + check_arg!(env, result); + + let Some(ctor) = constructor.and_then(|v| v.to_object(&mut env.scope())) + else { + return napi_object_expected; }; - let buffer_name = v8::String::new(&mut env.scope(), "buffer").unwrap(); - let Ok(abuf) = v8::Local::<v8::ArrayBuffer>::try_from( - buf.get(&mut env.scope(), buffer_name.into()).unwrap(), - ) else { - return napi_invalid_arg; + + if !ctor.is_function() { + unsafe { + napi_throw_type_error( + env, + "ERR_NAPI_CONS_FUNCTION\0".as_ptr() as _, + "Constructor must be a function\0".as_ptr() as _, + ); + } + return napi_function_expected; + } + + let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else { + return napi_generic_failure; }; - if !data.is_null() { - *data = get_array_buffer_ptr(abuf); + + unsafe { + *result = res; } - *length = abuf.byte_length(); + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_date_value( - env: *mut Env, - value: napi_value, - result: *mut f64, +#[napi_sym] +fn napi_is_exception_pending( + env_ptr: *mut Env, + result: *mut bool, ) -> napi_status { - check_env!(env); - let value = napi_value_unchecked(value); - return_status_if_false!(env, value.is_date(), napi_date_expected); - let env = unsafe { &mut *env }; - let Ok(date) = v8::Local::<v8::Date>::try_from(value) else { - return napi_date_expected; - }; - // TODO: should be value of - *result = date.number_value(&mut env.scope()).unwrap(); - napi_ok + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = env.last_exception.is_some(); + } + + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_element( - env: *mut Env, - object: napi_value, - index: u32, +#[napi_sym] +fn napi_get_and_clear_last_exception( + env_ptr: *mut Env, result: *mut napi_value, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::<v8::Object>::try_from(object) else { - return napi_invalid_arg; - }; - let value: v8::Local<v8::Value> = - object.get_index(&mut env.scope(), index).unwrap(); - *result = value.into(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_global(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); + let env = check_env!(env_ptr); check_arg!(env, result); - let value: v8::Local<v8::Value> = - transmute::<NonNull<v8::Value>, v8::Local<v8::Value>>((*env).global); - *result = value.into(); - napi_clear_last_error(env); - napi_ok + let ex: v8::Local<v8::Value> = + if let Some(last_exception) = env.last_exception.take() { + v8::Local::new(&mut env.scope(), last_exception) + } else { + v8::undefined(&mut env.scope()).into() + }; + + unsafe { + *result = ex.into(); + } + + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_get_instance_data( +#[napi_sym] +fn napi_is_arraybuffer( env: *mut Env, - result: *mut *mut c_void, + value: napi_value, + result: *mut bool, ) -> napi_status { - let env = &mut *env; - let shared = env.shared(); - *result = shared.instance_data; - napi_ok -} + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); -// TODO(bartlomieju): this function is broken -#[napi_sym::napi_sym] -fn napi_get_last_error_info( - _env: *mut Env, - error_code: *mut *const napi_extended_error_info, -) -> napi_status { - let err_info = Box::new(napi_extended_error_info { - error_message: std::ptr::null(), - engine_reserved: std::ptr::null_mut(), - engine_error_code: 0, - error_code: napi_ok, - }); + unsafe { + *result = value.unwrap().is_array_buffer(); + } - *error_code = Box::into_raw(err_info); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_get_named_property( - env: *mut Env, - object: napi_value, - utf8_name: *const c_char, - result: *mut napi_value, +#[napi_sym] +fn napi_create_arraybuffer<'s>( + env: &'s mut Env, + len: usize, + data: *mut *mut c_void, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let utf8_name = std::ffi::CStr::from_ptr(utf8_name); - let name = - v8::String::new(&mut env.scope(), &utf8_name.to_string_lossy()).unwrap(); - let value: v8::Local<v8::Value> = object - .to_object(&mut env.scope()) - .unwrap() - .get(&mut env.scope(), name.into()) - .unwrap(); - *result = value.into(); - napi_ok -} + check_arg!(env, result); -#[napi_sym::napi_sym] -fn napi_get_new_target( - _env: &mut Env, - cbinfo: &CallbackInfo, - result: &mut v8::Local<v8::Value>, -) -> napi_status { - let info = &*(cbinfo.args as *const v8::FunctionCallbackArguments); - *result = info.new_target(); - napi_ok -} + let buffer = v8::ArrayBuffer::new(&mut env.scope(), len); + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buffer) as _; + } + } + + unsafe { + *result = buffer.into(); + } -#[napi_sym::napi_sym] -fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::null(env.isolate()).into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_get_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_create_external_arraybuffer<'s>( + env: &'s mut Env, + data: *mut c_void, + byte_length: usize, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = transmute::<napi_value, v8::Local<v8::Object>>(object); - let key = napi_value_unchecked(key); - let value: v8::Local<v8::Value> = object.get(&mut env.scope(), key).unwrap(); - *result = value.into(); + check_arg!(env, result); + + let store = make_external_backing_store( + env, + data, + byte_length, + std::ptr::null_mut(), + finalize_cb, + finalize_hint, + ); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + let value: v8::Local<v8::Value> = ab.into(); + + unsafe { + *result = value.into(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_property_names( +#[napi_sym] +fn napi_get_arraybuffer_info( env: *mut Env, - object: napi_value, - result: *mut napi_value, + value: napi_value, + data: *mut *mut u8, + length: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let array: v8::Local<v8::Array> = object - .to_object(&mut env.scope()) - .unwrap() - .get_property_names(&mut env.scope(), Default::default()) - .unwrap(); - let value: v8::Local<v8::Value> = array.into(); - *result = value.into(); + let env = check_env!(env); + check_arg!(env, value); + + let Some(buf) = + value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buf); + } + } + + if !length.is_null() { + unsafe { + *length = buf.byte_length(); + } + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_prototype( +#[napi_sym] +fn napi_is_typedarray( env: *mut Env, value: napi_value, - result: *mut napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let proto = obj.get_prototype(&mut env.scope()).unwrap(); - *result = proto.into(); + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_typed_array(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_get_reference_value( - env: *mut Env, - reference: napi_ref, - result: *mut napi_value, +#[napi_sym] +fn napi_create_typedarray<'s>( + env: &'s mut Env, + ty: napi_typedarray_type, + length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let value = transmute::<napi_ref, v8::Local<v8::Value>>(reference); - *result = value.into(); + check_arg!(env, arraybuffer); + check_arg!(env, result); + + let Some(ab) = + arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_arraybuffer_expected; + }; + + macro_rules! create { + ($TypedArray:ident, $size_of_element:expr) => {{ + let soe = $size_of_element; + if soe > 1 && byte_offset % soe != 0 { + let message = v8::String::new( + &mut env.scope(), + format!( + "start offset of {} should be multiple of {}", + stringify!($TypedArray), + soe + ) + .as_str(), + ) + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + if length * soe + byte_offset > ab.byte_length() { + let message = + v8::String::new(&mut env.scope(), "Invalid typed array length") + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + let Some(ta) = + v8::$TypedArray::new(&mut env.scope(), ab, byte_offset, length) + else { + return napi_generic_failure; + }; + ta.into() + }}; + } + + let typedarray: v8::Local<v8::Value> = match ty { + napi_uint8_array => create!(Uint8Array, 1), + napi_uint8_clamped_array => create!(Uint8ClampedArray, 1), + napi_int8_array => create!(Int8Array, 1), + napi_uint16_array => create!(Uint16Array, 2), + napi_int16_array => create!(Int16Array, 2), + napi_uint32_array => create!(Uint32Array, 4), + napi_int32_array => create!(Int32Array, 4), + napi_float32_array => create!(Float32Array, 4), + napi_float64_array => create!(Float64Array, 8), + napi_bigint64_array => create!(BigInt64Array, 8), + napi_biguint64_array => create!(BigUint64Array, 8), + _ => { + return napi_invalid_arg; + } + }; + + unsafe { + *result = typedarray.into(); + } + napi_ok } -#[napi_sym::napi_sym] +#[napi_sym] fn napi_get_typedarray_info( - env: *mut Env, + env_ptr: *mut Env, typedarray: napi_value, type_: *mut napi_typedarray_type, length: *mut usize, @@ -2018,364 +3048,278 @@ fn napi_get_typedarray_info( arraybuffer: *mut napi_value, byte_offset: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(typedarray); - let Ok(array) = v8::Local::<v8::TypedArray>::try_from(value) else { - return napi_invalid_arg; + let env = check_env!(env_ptr); + check_arg!(env, typedarray); + + let Some(array) = + typedarray.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_invalid_arg); }; if !type_.is_null() { - if value.is_int8_array() { - *type_ = napi_int8_array; - } else if value.is_uint8_array() { - *type_ = napi_uint8_array; - } else if value.is_uint8_clamped_array() { - *type_ = napi_uint8_clamped_array; - } else if value.is_int16_array() { - *type_ = napi_int16_array; - } else if value.is_uint16_array() { - *type_ = napi_uint16_array; - } else if value.is_int32_array() { - *type_ = napi_int32_array; - } else if value.is_uint32_array() { - *type_ = napi_uint32_array; - } else if value.is_float32_array() { - *type_ = napi_float32_array; - } else if value.is_float64_array() { - *type_ = napi_float64_array; + let tatype = if array.is_int8_array() { + napi_int8_array + } else if array.is_uint8_array() { + napi_uint8_array + } else if array.is_uint8_clamped_array() { + napi_uint8_clamped_array + } else if array.is_int16_array() { + napi_int16_array + } else if array.is_uint16_array() { + napi_uint16_array + } else if array.is_int32_array() { + napi_int32_array + } else if array.is_uint32_array() { + napi_uint32_array + } else if array.is_float32_array() { + napi_float32_array + } else if array.is_float64_array() { + napi_float64_array + } else if array.is_big_int64_array() { + napi_bigint64_array + } else if array.is_big_uint64_array() { + napi_biguint64_array + } else { + unreachable!(); + }; + + unsafe { + *type_ = tatype; } } if !length.is_null() { - *length = array.length(); + unsafe { + *length = array.length(); + } } - if !data.is_null() || !arraybuffer.is_null() { - let buf = array.buffer(&mut env.scope()).unwrap(); - if !data.is_null() { - *data = get_array_buffer_ptr(buf) as *mut c_void; + if !data.is_null() { + unsafe { + *data = array.data(); } - if !arraybuffer.is_null() { + } + + if !arraybuffer.is_null() { + let buf = array.buffer(&mut env.scope()).unwrap(); + unsafe { *arraybuffer = buf.into(); } } if !byte_offset.is_null() { - *byte_offset = array.byte_offset(); + unsafe { + *byte_offset = array.byte_offset(); + } } - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { - check_env!(env); - check_arg!(env, result); - let env = unsafe { &mut *env }; - *result = v8::undefined(env.isolate()).into(); - napi_ok -} - -pub const NAPI_VERSION: u32 = 8; - -#[napi_sym::napi_sym] -fn napi_get_version(_: napi_env, version: *mut u32) -> napi_status { - *version = NAPI_VERSION; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_element( - env: *mut Env, - value: napi_value, - index: u32, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - *result = obj.has_index(&mut env.scope(), index).unwrap_or(false); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_has_named_property( - env: *mut Env, - value: napi_value, - key: *const c_char, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let key = CStr::from_ptr(key).to_str().unwrap(); - let key = v8::String::new(&mut env.scope(), key).unwrap(); - *result = obj.has(&mut env.scope(), key.into()).unwrap_or(false); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_has_own_property( - env: *mut Env, - object: napi_value, - key: napi_value, - result: *mut bool, +#[napi_sym] +fn napi_create_dataview<'s>( + env: &'s mut Env, + byte_length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + check_arg!(env, arraybuffer); check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { + let Some(buffer) = + arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { return napi_invalid_arg; }; - if key.is_none() { - return napi_invalid_arg; + if byte_length + byte_offset > buffer.byte_length() { + unsafe { + return napi_throw_range_error( + env, + "ERR_NAPI_INVALID_DATAVIEW_ARGS\0".as_ptr() as _, + "byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in\0".as_ptr() as _, + ); + } } - let Ok(key) = v8::Local::<v8::Name>::try_from(key.unwrap()) else { - return napi_name_expected; - }; - let Some(has_own) = object.has_own_property(scope, key) else { - return napi_generic_failure; + // let dataview = v8::DataView::new(&mut env, buffer, byte_offset, byte_length); + let dataview = { + let context = &mut env.scope().get_current_context(); + let global = context.global(&mut env.scope()); + let data_view_name = v8::String::new(&mut env.scope(), "DataView").unwrap(); + let data_view = + global.get(&mut env.scope(), data_view_name.into()).unwrap(); + let Ok(data_view) = v8::Local::<v8::Function>::try_from(data_view) else { + return napi_function_expected; + }; + let byte_offset = v8::Number::new(&mut env.scope(), byte_offset as f64); + let byte_length = v8::Number::new(&mut env.scope(), byte_length as f64); + let Some(dv) = data_view.new_instance( + &mut env.scope(), + &[buffer.into(), byte_offset.into(), byte_length.into()], + ) else { + return napi_generic_failure; + }; + dv }; - *result = has_own; + unsafe { + *result = dataview.into(); + } napi_ok } -#[napi_sym::napi_sym] -fn napi_has_property( +#[napi_sym] +fn napi_is_dataview( env: *mut Env, - object: napi_value, - key: napi_value, + value: napi_value, result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); + let env = check_env!(env); + check_arg!(env, value); check_arg!(env, result); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; - }; + unsafe { + *result = value.unwrap().is_data_view(); + } - let Some(has) = object.has(scope, key.unwrap_unchecked()) else { - return napi_generic_failure; - }; - *result = has; - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_instanceof( - env: *mut Env, - value: napi_value, - constructor: napi_value, - result: *mut bool, +#[napi_sym] +fn napi_get_dataview_info( + env_ptr: *mut Env, + dataview: napi_value, + byte_length: *mut usize, + data: *mut *mut c_void, + arraybuffer: *mut napi_value, + byte_offset: *mut usize, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, constructor); - check_arg_option!(env, value); + let env = check_env!(env_ptr); + check_arg!(env, dataview); - let value = napi_value_unchecked(value); - let constructor = napi_value_unchecked(constructor); - let Some(ctor) = constructor.to_object(&mut env.scope()) else { - return napi_object_expected; - }; - if !ctor.is_function() { - return napi_function_expected; - } - let Some(res) = value.instance_of(&mut env.scope(), ctor) else { - return napi_generic_failure; + let Some(array) = + dataview.and_then(|v| v8::Local::<v8::DataView>::try_from(v).ok()) + else { + return napi_invalid_arg; }; - *result = res; - napi_ok -} + if !byte_length.is_null() { + unsafe { + *byte_length = array.byte_length(); + } + } -#[napi_sym::napi_sym] -fn napi_is_array( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_array(); - napi_ok -} + if !arraybuffer.is_null() { + let Some(buffer) = array.buffer(&mut env.scope()) else { + return napi_generic_failure; + }; -#[napi_sym::napi_sym] -fn napi_is_arraybuffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_array_buffer(); - napi_ok -} + unsafe { + *arraybuffer = buffer.into(); + } + } -#[napi_sym::napi_sym] -fn napi_is_buffer( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - // TODO: should we assume Buffer as Uint8Array in Deno? - // or use std/node polyfill? - *result = value.is_typed_array(); - napi_ok -} + if !data.is_null() { + unsafe { + *data = array.data(); + } + } -#[napi_sym::napi_sym] -fn napi_is_dataview( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_data_view(); - napi_ok -} + if !byte_offset.is_null() { + unsafe { + *byte_offset = array.byte_offset(); + } + } -#[napi_sym::napi_sym] -fn napi_is_date( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_date(); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_is_detached_arraybuffer( - env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); +#[napi_sym] +fn napi_get_version(env: *mut Env, result: *mut u32) -> napi_status { + let env = check_env!(env); check_arg!(env, result); - let value = napi_value_unchecked(value); - - *result = match v8::Local::<v8::ArrayBuffer>::try_from(value) { - Ok(array_buffer) => array_buffer.was_detached(), - Err(_) => false, - }; - - napi_clear_last_error(env); + unsafe { + *result = NAPI_VERSION; + } - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_is_error( - env: *mut Env, - value: napi_value, - result: *mut bool, +#[napi_sym] +fn napi_create_promise<'s>( + env: &'s mut Env, + deferred: *mut napi_deferred, + promise: *mut napi_value<'s>, ) -> napi_status { - { - check_env!(env); - if value.is_none() { - return napi_invalid_arg; - } - check_arg!(env, result); + check_arg!(env, deferred); + check_arg!(env, promise); - let value = napi_value_unchecked(value); - *result = value.is_native_error(); - } - napi_clear_last_error(env); - napi_ok -} + let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); -#[napi_sym::napi_sym] -fn napi_is_exception_pending(_env: *mut Env, result: *mut bool) -> napi_status { - // TODO - *result = false; - napi_ok -} + let global = v8::Global::new(&mut env.scope(), resolver); + let global_ptr = global.into_raw().as_ptr() as napi_deferred; -#[napi_sym::napi_sym] -fn napi_is_promise( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_promise(); - napi_ok -} + let p = resolver.get_promise(&mut env.scope()); -#[napi_sym::napi_sym] -fn napi_is_typedarray( - _env: *mut Env, - value: napi_value, - result: *mut bool, -) -> napi_status { - let value = napi_value_unchecked(value); - *result = value.is_typed_array(); - napi_ok -} + unsafe { + *deferred = global_ptr; + } + + unsafe { + *promise = p.into(); + } -#[napi_sym::napi_sym] -fn napi_new_instance( - env: *mut Env, - constructor: napi_value, - argc: usize, - argv: *const napi_value, - result: *mut napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let constructor = napi_value_unchecked(constructor); - let Ok(constructor) = v8::Local::<v8::Function>::try_from(constructor) else { - return napi_function_expected; - }; - let args: &[v8::Local<v8::Value>] = - transmute(std::slice::from_raw_parts(argv, argc)); - let inst = constructor.new_instance(&mut env.scope(), args).unwrap(); - let value: v8::Local<v8::Value> = inst.into(); - *result = value.into(); napi_ok } -#[napi_sym::napi_sym] -fn napi_object_freeze( +#[napi_sym] +fn napi_resolve_deferred( env: &mut Env, - object: v8::Local<v8::Value>, + deferred: napi_deferred, + result: napi_value, ) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Frozen) - .is_none() + check_arg!(env, result); + check_arg!(env, deferred); + + let isolate = unsafe { &mut *env.isolate_ptr }; + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .resolve(&mut env.scope(), result.unwrap()) + .unwrap_or(false) { return napi_generic_failure; - }; + } napi_ok } -#[napi_sym::napi_sym] -fn napi_object_seal( +#[napi_sym] +fn napi_reject_deferred( env: &mut Env, - object: v8::Local<v8::Value>, + deferred: napi_deferred, + result: napi_value, ) -> napi_status { - let object = object.to_object(&mut env.scope()).unwrap(); - if object - .set_integrity_level(&mut env.scope(), v8::IntegrityLevel::Sealed) - .is_none() + check_arg!(env, result); + check_arg!(env, deferred); + + let isolate = unsafe { &mut *env.isolate_ptr }; + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(isolate, deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .reject(&mut env.scope(), result.unwrap()) + .unwrap_or(false) { return napi_generic_failure; } @@ -2383,453 +3327,260 @@ fn napi_object_seal( napi_ok } -#[napi_sym::napi_sym] -fn napi_open_escapable_handle_scope( - _env: *mut Env, - _result: *mut napi_escapable_handle_scope, +#[napi_sym] +fn napi_is_promise( + env: *mut Env, + value: napi_value, + is_promise: *mut bool, ) -> napi_status { - // TODO: do this properly - napi_ok + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_promise); + + unsafe { + *is_promise = value.unwrap().is_promise(); + } + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_open_handle_scope( - env: *mut Env, - _result: *mut napi_handle_scope, +#[napi_sym] +fn napi_create_date<'s>( + env: &'s mut Env, + time: f64, + result: *mut napi_value<'s>, ) -> napi_status { - let env = &mut *env; + check_arg!(env, result); - // TODO: this is also not implemented in napi_close_handle_scope - // *result = &mut env.scope() as *mut _ as napi_handle_scope; - env.open_handle_scopes += 1; - napi_ok -} + let Some(date) = v8::Date::new(&mut env.scope(), time) else { + return napi_generic_failure; + }; -#[napi_sym::napi_sym] -fn napi_reference_ref() -> napi_status { - // TODO - napi_ok -} + unsafe { + *result = date.into(); + } -#[napi_sym::napi_sym] -fn napi_reference_unref() -> napi_status { - // TODO napi_ok } -#[napi_sym::napi_sym] -fn napi_reject_deferred( +#[napi_sym] +fn napi_is_date( env: *mut Env, - deferred: napi_deferred, - error: napi_value, + value: napi_value, + is_date: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_date); - let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull<v8::PromiseResolver>, - v8::Local<v8::PromiseResolver>, - >(deferred_ptr); - resolver - .reject(&mut env.scope(), napi_value_unchecked(error)) - .unwrap(); - napi_ok -} + unsafe { + *is_date = value.unwrap().is_date(); + } -#[napi_sym::napi_sym] -fn napi_remove_wrap(env: *mut Env, value: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - obj.delete_private(&mut env.scope(), napi_wrap).unwrap(); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_resolve_deferred( - env: *mut Env, - deferred: napi_deferred, - result: napi_value, +#[napi_sym] +fn napi_get_date_value( + env: &mut Env, + value: napi_value, + result: *mut f64, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let deferred_ptr = - NonNull::new_unchecked(deferred as *mut v8::PromiseResolver); - // TODO(@littledivy): Use Global::from_raw instead casting to local. - // SAFETY: Isolate is still alive unless the module is doing something weird, - // global data is the size of a pointer. - // Global pointer is obtained from napi_create_promise - let resolver = transmute::< - NonNull<v8::PromiseResolver>, - v8::Local<v8::PromiseResolver>, - >(deferred_ptr); - resolver - .resolve(&mut env.scope(), napi_value_unchecked(result)) - .unwrap(); + check_arg!(env, result); + + let Some(date) = value.and_then(|v| v8::Local::<v8::Date>::try_from(v).ok()) + else { + return napi_date_expected; + }; + + unsafe { + *result = date.value_of(); + } + napi_ok } -#[napi_sym::napi_sym] -fn napi_run_script( - env: *mut Env, +#[napi_sym] +fn napi_run_script<'s>( + env: &'s mut Env, script: napi_value, - result: *mut napi_value, + result: *mut napi_value<'s>, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; + check_arg!(env, script); + check_arg!(env, result); - let script = napi_value_unchecked(script); - if !script.is_string() { - // TODO: - // napi_set_last_error + let Some(script) = + script.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { return napi_string_expected; - } - let script = script.to_string(&mut env.scope()).unwrap(); + }; - let script = v8::Script::compile(&mut env.scope(), script, None); - if script.is_none() { + let Some(script) = v8::Script::compile(&mut env.scope(), script, None) else { return napi_generic_failure; - } - let script = script.unwrap(); - let rv = script.run(&mut env.scope()); + }; - if let Some(rv) = rv { - *result = rv.into(); - } else { + let Some(rv) = script.run(&mut env.scope()) else { return napi_generic_failure; + }; + + unsafe { + *result = rv.into(); } napi_ok } -#[napi_sym::napi_sym] -fn napi_set_element( - env: *mut Env, - object: napi_value, - index: u32, +#[napi_sym] +fn napi_add_finalizer( + env_ptr: *mut Env, value: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let object = napi_value_unchecked(object); - let Ok(object) = v8::Local::<v8::Object>::try_from(object) else { - return napi_invalid_arg; - }; - let value = napi_value_unchecked(value); - object.set_index(&mut env.scope(), index, value).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_instance_data( - env: *mut Env, - data: *mut c_void, + finalize_data: *mut c_void, finalize_cb: Option<napi_finalize>, finalize_hint: *mut c_void, + result: *mut napi_ref, ) -> napi_status { - let env = &mut *env; - let shared = env.shared_mut(); - shared.instance_data = data; - shared.data_finalize = if finalize_cb.is_some() { - finalize_cb - } else { - None - }; - shared.data_finalize_hint = finalize_hint; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_named_property( - env: *mut Env, - object: napi_value, - name: *const c_char, - value: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let name = CStr::from_ptr(name).to_str().unwrap(); - let object = transmute::<napi_value, v8::Local<v8::Object>>(object); - let value = napi_value_unchecked(value); - let name = v8::String::new(&mut env.scope(), name).unwrap(); - object.set(&mut env.scope(), name.into(), value).unwrap(); - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_set_property( - env: *mut Env, - object: napi_value, - key: napi_value, - value: napi_value, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, key); - check_arg_option!(env, value); + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, finalize_cb); - let scope = &mut env.scope(); - let Some(object) = object.map(|o| o.to_object(scope)).flatten() else { - return napi_invalid_arg; + let Some(value) = + value.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); }; - if object - .set(scope, key.unwrap_unchecked(), value.unwrap_unchecked()) - .is_none() - { - return napi_generic_failure; + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland }; - napi_ok -} - -#[napi_sym::napi_sym] -fn napi_strict_equals( - env: *mut Env, - lhs: napi_value, - rhs: napi_value, - result: *mut bool, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - check_arg_option!(env, lhs); - check_arg_option!(env, rhs); + let reference = Reference::new( + env, + value.into(), + 0, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + ); - *result = lhs.unwrap_unchecked().strict_equals(rhs.unwrap_unchecked()); - napi_ok -} + if !result.is_null() { + unsafe { + *result = Reference::into_raw(reference) as _; + } + } -#[napi_sym::napi_sym] -fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let error = napi_value_unchecked(error); - env.scope().throw_exception(error); - napi_ok + napi_clear_last_error(env_ptr) } -#[napi_sym::napi_sym] -fn napi_throw_error( +#[napi_sym] +fn node_api_post_finalizer( env: *mut Env, - code: *const c_char, - msg: *const c_char, + _finalize_cb: napi_finalize, + _finalize_data: *mut c_void, + _finalize_hint: *mut c_void, ) -> napi_status { - // TODO: add preamble here - - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - - unsafe { &mut *env }.scope().throw_exception(error); - } - napi_clear_last_error(env); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_throw_range_error( +#[napi_sym] +fn napi_adjust_external_memory( env: *mut Env, - code: *const c_char, - msg: *const c_char, + change_in_bytes: i64, + adjusted_value: *mut i64, ) -> napi_status { - // TODO: add preamble here + let env = check_env!(env); + check_arg!(env, adjusted_value); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::range_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); + let isolate = unsafe { &mut *env.isolate_ptr }; + + unsafe { + *adjusted_value = + isolate.adjust_amount_of_external_allocated_memory(change_in_bytes); } - napi_clear_last_error(env); - napi_ok + + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_throw_type_error( +#[napi_sym] +fn napi_set_instance_data( env: *mut Env, - code: *const c_char, - msg: *const c_char, + data: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, ) -> napi_status { - // TODO: add preamble here + let env = check_env!(env); - { - check_env!(env); - let str_ = match check_new_from_utf8(env, msg) { - Ok(s) => s, - Err(status) => return status, - }; - let error = { - let env = unsafe { &mut *env }; - let scope = &mut env.scope(); - v8::Exception::type_error(scope, str_) - }; - status_call!(set_error_code( - env, - error, - transmute::<*mut (), napi_value>(std::ptr::null_mut()), - code, - )); - unsafe { &mut *env }.scope().throw_exception(error); - } - napi_clear_last_error(env); - napi_ok -} + env.shared_mut().instance_data = Some(InstanceData { + data, + finalize_cb, + finalize_hint, + }); -pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> { - if value.is_undefined() { - Some(napi_undefined) - } else if value.is_null() { - Some(napi_null) - } else if value.is_external() { - Some(napi_external) - } else if value.is_boolean() { - Some(napi_boolean) - } else if value.is_number() { - Some(napi_number) - } else if value.is_big_int() { - Some(napi_bigint) - } else if value.is_string() { - Some(napi_string) - } else if value.is_symbol() { - Some(napi_symbol) - } else if value.is_function() { - Some(napi_function) - } else if value.is_object() { - Some(napi_object) - } else { - None - } + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn napi_typeof( +#[napi_sym] +fn napi_get_instance_data( env: *mut Env, - value: napi_value, - result: *mut napi_valuetype, + data: *mut *mut c_void, ) -> napi_status { - check_env!(env); - check_arg_option!(env, value); - check_arg!(env, result); + let env = check_env!(env); + check_arg!(env, data); - let Some(ty) = get_value_type(value.unwrap()) else { - return napi_invalid_arg; + let instance_data = match &env.shared().instance_data { + Some(v) => v.data, + None => std::ptr::null_mut(), }; - *result = ty; - napi_ok -} -#[napi_sym::napi_sym] -fn napi_unwrap( - env: *mut Env, - value: napi_value, - result: *mut *mut c_void, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = obj.get_private(&mut env.scope(), napi_wrap).unwrap(); - let Some(ext) = v8::Local::<v8::External>::try_from(ext).ok() else { - return napi_invalid_arg; - }; - *result = ext.value(); - napi_ok -} + unsafe { *data = instance_data }; -#[napi_sym::napi_sym] -fn napi_wrap( - env: *mut Env, - value: napi_value, - native_object: *mut c_void, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - let value = napi_value_unchecked(value); - let obj = value.to_object(&mut env.scope()).unwrap(); - let shared = &*(env.shared as *const EnvShared); - let napi_wrap = v8::Local::new(&mut env.scope(), &shared.napi_wrap); - let ext = v8::External::new(&mut env.scope(), native_object); - obj.set_private(&mut env.scope(), napi_wrap, ext.into()); - napi_ok + napi_clear_last_error(env) } -#[napi_sym::napi_sym] -fn node_api_throw_syntax_error( - env: *mut Env, - _code: *const c_char, - msg: *const c_char, -) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; +#[napi_sym] +fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); - // let code = CStr::from_ptr(code).to_str().unwrap(); - let msg = CStr::from_ptr(msg).to_str().unwrap(); + let Some(ab) = + value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_arraybuffer_expected); + }; - // let code = v8::String::new(&mut env.scope(), code).unwrap(); - let msg = v8::String::new(&mut env.scope(), msg).unwrap(); + if !ab.is_detachable() { + return napi_set_last_error(env, napi_detachable_arraybuffer_expected); + } - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - env.scope().throw_exception(error); + // Expected to crash for None. + ab.detach(None).unwrap(); + napi_clear_last_error(env); napi_ok } -#[napi_sym::napi_sym] -fn node_api_create_syntax_error( - env: *mut Env, - _code: napi_value, - msg: napi_value, - result: *mut napi_value, +#[napi_sym] +fn napi_is_detached_arraybuffer( + env_ptr: *mut Env, + arraybuffer: napi_value, + result: *mut bool, ) -> napi_status { - check_env!(env); - let env = unsafe { &mut *env }; - - // let code = napi_value_unchecked(code); - let msg = napi_value_unchecked(msg); + let env = check_env!(env_ptr); + check_arg!(env, arraybuffer); + check_arg!(env, result); - let msg = msg.to_string(&mut env.scope()).unwrap(); + let is_detached = match arraybuffer + .and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + { + Some(ab) => ab.was_detached(), + None => false, + }; - let error = v8::Exception::syntax_error(&mut env.scope(), msg); - *result = error.into(); + unsafe { + *result = is_detached; + } - napi_ok + napi_clear_last_error(env) } |