diff options
Diffstat (limited to 'ext/napi/js_native_api.rs')
-rw-r--r-- | ext/napi/js_native_api.rs | 3616 |
1 files changed, 3616 insertions, 0 deletions
diff --git a/ext/napi/js_native_api.rs b/ext/napi/js_native_api.rs new file mode 100644 index 000000000..53a12d6eb --- /dev/null +++ b/ext/napi/js_native_api.rs @@ -0,0 +1,3616 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +#![allow(non_upper_case_globals)] +#![deny(unsafe_op_in_unsafe_fn)] + +const NAPI_VERSION: u32 = 9; + +use crate::*; +use libc::INT_MAX; + +use super::util::check_new_from_utf8; +use super::util::check_new_from_utf8_len; +use super::util::get_array_buffer_ptr; +use super::util::make_external_backing_store; +use super::util::napi_clear_last_error; +use super::util::napi_set_last_error; +use super::util::v8_name_from_property_descriptor; +use crate::check_arg; +use crate::check_env; +use crate::function::create_function; +use crate::function::create_function_template; +use crate::function::CallbackInfo; +use napi_sym::napi_sym; +use std::ptr::NonNull; + +#[derive(Debug, Clone, Copy, PartialEq)] +enum ReferenceOwnership { + Runtime, + Userland, +} + +enum ReferenceState { + Strong(v8::Global<v8::Value>), + Weak(v8::Weak<v8::Value>), +} + +struct Reference { + env: *mut Env, + state: ReferenceState, + ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option<napi_finalize>, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +} + +impl Reference { + fn new( + env: *mut Env, + value: v8::Local<v8::Value>, + initial_ref_count: u32, + ownership: ReferenceOwnership, + finalize_cb: Option<napi_finalize>, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, + ) -> Box<Self> { + let isolate = unsafe { (*env).isolate() }; + + let mut reference = Box::new(Reference { + env, + state: ReferenceState::Strong(v8::Global::new(isolate, value)), + ref_count: initial_ref_count, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + }); + + if initial_ref_count == 0 { + reference.set_weak(); + } + + reference + } + + fn ref_(&mut self) -> u32 { + self.ref_count += 1; + if self.ref_count == 1 { + self.set_strong(); + } + self.ref_count + } + + fn unref(&mut self) -> u32 { + let old_ref_count = self.ref_count; + if self.ref_count > 0 { + self.ref_count -= 1; + } + if old_ref_count == 1 && self.ref_count == 0 { + self.set_weak(); + } + self.ref_count + } + + fn reset(&mut self) { + self.finalize_cb = None; + self.finalize_data = std::ptr::null_mut(); + self.finalize_hint = std::ptr::null_mut(); + } + + fn set_strong(&mut self) { + if let ReferenceState::Weak(w) = &self.state { + let isolate = unsafe { (*self.env).isolate() }; + if let Some(g) = w.to_global(isolate) { + self.state = ReferenceState::Strong(g); + } + } + } + + fn set_weak(&mut self) { + let reference = self as *mut Reference; + if let ReferenceState::Strong(g) = &self.state { + let cb = Box::new(move |_: &mut v8::Isolate| { + Reference::weak_callback(reference) + }); + let isolate = unsafe { (*self.env).isolate() }; + self.state = + ReferenceState::Weak(v8::Weak::with_finalizer(isolate, g, cb)); + } + } + + fn weak_callback(reference: *mut Reference) { + let reference = unsafe { &mut *reference }; + + let finalize_cb = reference.finalize_cb; + let finalize_data = reference.finalize_data; + let finalize_hint = reference.finalize_hint; + reference.reset(); + + // copy this value before the finalize callback, since + // it might free the reference (which would be a UAF) + let ownership = reference.ownership; + if let Some(finalize_cb) = finalize_cb { + unsafe { + finalize_cb(reference.env as _, finalize_data, finalize_hint); + } + } + + if ownership == ReferenceOwnership::Runtime { + unsafe { drop(Reference::from_raw(reference)) } + } + } + + fn into_raw(r: Box<Reference>) -> *mut Reference { + Box::into_raw(r) + } + + unsafe fn from_raw(r: *mut Reference) -> Box<Reference> { + unsafe { Box::from_raw(r) } + } + + unsafe fn remove(r: *mut Reference) { + let r = unsafe { &mut *r }; + if r.ownership == ReferenceOwnership::Userland { + r.reset(); + } else { + unsafe { drop(Reference::from_raw(r)) } + } + } +} + +#[napi_sym] +fn napi_get_last_error_info( + env: *mut Env, + result: *mut *const napi_extended_error_info, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + if env.last_error.error_code == napi_ok { + napi_clear_last_error(env); + } else { + env.last_error.error_message = + ERROR_MESSAGES[env.last_error.error_code as usize].as_ptr(); + } + + unsafe { + *result = &env.last_error; + } + + napi_ok +} + +#[napi_sym] +fn napi_create_function<'s>( + env: &'s mut Env, + name: *const c_char, + length: usize, + cb: Option<napi_callback>, + cb_info: napi_callback_info, + result: *mut napi_value<'s>, +) -> napi_status { + let env_ptr = env as *mut Env; + check_arg!(env, result); + check_arg!(env, cb); + + let name = if !name.is_null() { + match unsafe { check_new_from_utf8_len(env, name, length) } { + Ok(s) => Some(s), + Err(status) => return status, + } + } else { + None + }; + + unsafe { + *result = + create_function(&mut env.scope(), env_ptr, name, cb.unwrap(), cb_info) + .into(); + } + + napi_ok +} + +#[napi_sym] +#[allow(clippy::too_many_arguments)] +fn napi_define_class<'s>( + env: &'s mut Env, + utf8name: *const c_char, + length: usize, + constructor: Option<napi_callback>, + callback_data: *mut c_void, + property_count: usize, + properties: *const napi_property_descriptor, + result: *mut napi_value<'s>, +) -> napi_status { + let env_ptr = env as *mut Env; + check_arg!(env, result); + check_arg!(env, constructor); + + if property_count > 0 { + check_arg!(env, properties); + } + + let name = match unsafe { check_new_from_utf8_len(env, utf8name, length) } { + Ok(string) => string, + Err(status) => return status, + }; + + let tpl = create_function_template( + &mut env.scope(), + env_ptr, + Some(name), + constructor.unwrap(), + callback_data, + ); + + let napi_properties: &[napi_property_descriptor] = if property_count > 0 { + unsafe { std::slice::from_raw_parts(properties, property_count) } + } else { + &[] + }; + let mut static_property_count = 0; + + for p in napi_properties { + if p.attributes & napi_static != 0 { + // Will be handled below + static_property_count += 1; + continue; + } + + let name = match unsafe { v8_name_from_property_descriptor(env_ptr, p) } { + Ok(name) => name, + Err(status) => return status, + }; + + let mut accessor_property = v8::PropertyAttribute::NONE; + + if p.attributes & napi_enumerable == 0 { + accessor_property = accessor_property | v8::PropertyAttribute::DONT_ENUM; + } + if p.attributes & napi_configurable == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::DONT_DELETE; + } + + if p.getter.is_some() || p.setter.is_some() { + let getter = p.getter.map(|g| { + create_function_template(&mut env.scope(), env_ptr, None, g, p.data) + }); + let setter = p.setter.map(|s| { + create_function_template(&mut env.scope(), env_ptr, None, s, p.data) + }); + if getter.is_some() + && setter.is_some() + && (p.attributes & napi_writable) == 0 + { + accessor_property = + accessor_property | v8::PropertyAttribute::READ_ONLY; + } + let proto = tpl.prototype_template(&mut env.scope()); + proto.set_accessor_property(name, getter, setter, accessor_property); + } else if let Some(method) = p.method { + let function = create_function_template( + &mut env.scope(), + env_ptr, + None, + method, + p.data, + ); + let proto = tpl.prototype_template(&mut env.scope()); + proto.set_with_attr(name, function.into(), accessor_property); + } else { + let proto = tpl.prototype_template(&mut env.scope()); + if (p.attributes & napi_writable) == 0 { + accessor_property = + accessor_property | v8::PropertyAttribute::READ_ONLY; + } + proto.set_with_attr(name, p.value.unwrap().into(), accessor_property); + } + } + + let value: v8::Local<v8::Value> = + tpl.get_function(&mut env.scope()).unwrap().into(); + + unsafe { + *result = value.into(); + } + + if static_property_count > 0 { + let mut static_descriptors = Vec::with_capacity(static_property_count); + + for p in napi_properties { + if p.attributes & napi_static != 0 { + static_descriptors.push(*p); + } + } + + crate::status_call!(unsafe { + napi_define_properties( + env_ptr, + *result, + static_descriptors.len(), + static_descriptors.as_ptr(), + ) + }); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_property_names( + env: *mut Env, + object: napi_value, + result: *mut napi_value, +) -> napi_status { + unsafe { + napi_get_all_property_names( + env, + object, + napi_key_include_prototypes, + napi_key_enumerable | napi_key_skip_symbols, + napi_key_numbers_to_strings, + result, + ) + } +} + +#[napi_sym] +fn napi_get_all_property_names<'s>( + env: &'s mut Env, + object: napi_value, + key_mode: napi_key_collection_mode, + key_filter: napi_key_filter, + key_conversion: napi_key_conversion, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(obj) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let mut filter = v8::PropertyFilter::ALL_PROPERTIES; + + if key_filter & napi_key_writable != 0 { + filter = filter | v8::PropertyFilter::ONLY_WRITABLE; + } + if key_filter & napi_key_enumerable != 0 { + filter = filter | v8::PropertyFilter::ONLY_ENUMERABLE; + } + if key_filter & napi_key_configurable != 0 { + filter = filter | v8::PropertyFilter::ONLY_CONFIGURABLE; + } + if key_filter & napi_key_skip_strings != 0 { + filter = filter | v8::PropertyFilter::SKIP_STRINGS; + } + if key_filter & napi_key_skip_symbols != 0 { + filter = filter | v8::PropertyFilter::SKIP_SYMBOLS; + } + + let key_mode = match key_mode { + napi_key_include_prototypes => v8::KeyCollectionMode::IncludePrototypes, + napi_key_own_only => v8::KeyCollectionMode::OwnOnly, + _ => return napi_invalid_arg, + }; + + let key_conversion = match key_conversion { + napi_key_keep_numbers => v8::KeyConversionMode::KeepNumbers, + napi_key_numbers_to_strings => v8::KeyConversionMode::ConvertToString, + _ => return napi_invalid_arg, + }; + + let filter = v8::GetPropertyNamesArgsBuilder::new() + .mode(key_mode) + .property_filter(filter) + .index_filter(v8::IndexFilter::IncludeIndices) + .key_conversion(key_conversion) + .build(); + + let property_names = match obj.get_property_names(scope, filter) { + Some(n) => n, + None => return napi_generic_failure, + }; + + unsafe { + *result = property_names.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_set_property( + env: &mut Env, + object: napi_value, + key: napi_value, + value: napi_value, +) -> napi_status { + check_arg!(env, key); + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if object.set(scope, key.unwrap(), value.unwrap()).is_none() { + return napi_generic_failure; + }; + + napi_ok +} + +#[napi_sym] +fn napi_has_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, key); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(has) = object.has(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has; + } + + napi_ok +} + +#[napi_sym] +fn napi_get_property<'s>( + env: &'s mut Env, + object: napi_value, + key: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, key); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(value) = object.get(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_delete_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, key); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(deleted) = object.delete(scope, key.unwrap()) else { + return napi_generic_failure; + }; + + if !result.is_null() { + unsafe { + *result = deleted; + } + } + + napi_ok +} + +#[napi_sym] +fn napi_has_own_property( + env: &mut Env, + object: napi_value, + key: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, key); + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Ok(key) = v8::Local::<v8::Name>::try_from(key.unwrap()) else { + return napi_name_expected; + }; + + let Some(has_own) = object.has_own_property(scope, key) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_own; + } + + napi_ok +} + +#[napi_sym] +fn napi_has_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut bool, +) -> napi_status { + let env_ptr = env as *mut Env; + check_arg!(env, result); + + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; + + let Some(has_property) = object.has(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = has_property; + } + + napi_ok +} + +#[napi_sym] +fn napi_set_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + value: napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + let env_ptr = env as *mut Env; + + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; + + let value = value.unwrap(); + + if !object + .set(&mut env.scope(), key.into(), value) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_get_named_property<'s>( + env: &'s mut Env, + object: napi_value<'s>, + utf8name: *const c_char, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + let env_ptr = env as *mut Env; + + let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else { + return napi_object_expected; + }; + + let key = match unsafe { check_new_from_utf8(env_ptr, utf8name) } { + Ok(key) => key, + Err(status) => return status, + }; + + let Some(value) = object.get(&mut env.scope(), key.into()) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_set_element<'s>( + env: &'s mut Env, + object: napi_value<'s>, + index: u32, + value: napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_index(scope, index, value.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_has_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, +) -> napi_status { + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(has) = object.has_index(scope, index) else { + return napi_generic_failure; + }; + + unsafe { + *result = has; + } + + napi_ok +} + +#[napi_sym] +fn napi_get_element<'s>( + env: &'s mut Env, + object: napi_value, + index: u32, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(value) = object.get_index(scope, index) else { + return napi_generic_failure; + }; + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_delete_element( + env: &mut Env, + object: napi_value, + index: u32, + result: *mut bool, +) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(deleted) = object.delete_index(scope, index) else { + return napi_generic_failure; + }; + + if !result.is_null() { + unsafe { + *result = deleted; + } + } + + napi_ok +} + +#[napi_sym] +fn napi_define_properties( + env: &mut Env, + object: napi_value, + property_count: usize, + properties: *const napi_property_descriptor, +) -> napi_status { + let env_ptr = env as *mut Env; + + if property_count > 0 { + check_arg!(env, properties); + } + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let properties = if property_count == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(properties, property_count) } + }; + for property in properties { + let property_name = + match unsafe { v8_name_from_property_descriptor(env_ptr, property) } { + Ok(name) => name, + Err(status) => return status, + }; + + let writable = property.attributes & napi_writable != 0; + let enumerable = property.attributes & napi_enumerable != 0; + let configurable = property.attributes & napi_configurable != 0; + + if property.getter.is_some() || property.setter.is_some() { + let local_getter: v8::Local<v8::Value> = if let Some(getter) = + property.getter + { + create_function(&mut env.scope(), env_ptr, None, getter, property.data) + .into() + } else { + v8::undefined(scope).into() + }; + let local_setter: v8::Local<v8::Value> = if let Some(setter) = + property.setter + { + create_function(&mut env.scope(), env_ptr, None, setter, property.data) + .into() + } else { + v8::undefined(scope).into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else if let Some(method) = property.method { + let method: v8::Local<v8::Value> = { + let function = create_function( + &mut env.scope(), + env_ptr, + None, + method, + property.data, + ); + function.into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(method, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_generic_failure; + } + } else { + let value = property.value.unwrap(); + + if enumerable & writable & configurable { + if !object + .create_data_property(scope, property_name, value) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } else { + let mut desc = + v8::PropertyDescriptor::new_from_value_writable(value, writable); + desc.set_enumerable(enumerable); + desc.set_configurable(configurable); + + if !object + .define_property(scope, property_name, &desc) + .unwrap_or(false) + { + return napi_invalid_arg; + } + } + } + } + + napi_ok +} + +#[napi_sym] +fn napi_object_freeze(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Frozen) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_object_seal(env: &mut Env, object: napi_value) -> napi_status { + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + if !object + .set_integrity_level(scope, v8::IntegrityLevel::Sealed) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_is_array( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + unsafe { + *result = value.is_array(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_array_length( + env: &mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + match v8::Local::<v8::Array>::try_from(value) { + Ok(array) => { + unsafe { + *result = array.length(); + } + napi_ok + } + Err(_) => napi_array_expected, + } +} + +#[napi_sym] +fn napi_strict_equals( + env: &mut Env, + lhs: napi_value, + rhs: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, lhs); + check_arg!(env, rhs); + check_arg!(env, result); + + unsafe { + *result = lhs.unwrap().strict_equals(rhs.unwrap()); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_prototype<'s>( + env: &'s mut Env, + object: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let scope = &mut env.scope(); + + let Some(object) = object.and_then(|o| o.to_object(scope)) else { + return napi_object_expected; + }; + + let Some(proto) = object.get_prototype(scope) else { + return napi_generic_failure; + }; + + unsafe { + *result = proto.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_object( + env_ptr: *mut Env, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Object::new(&mut env.scope()).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_array( + env_ptr: *mut Env, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Array::new(&mut env.scope(), 0).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_array_with_length( + env_ptr: *mut Env, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Array::new(&mut env.scope(), length as _).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_string_latin1( + env_ptr: *mut Env, + string: *const c_char, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); + + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } + } else { + &[] + }; + + let Some(string) = v8::String::new_from_one_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Normal, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +pub(crate) fn napi_create_string_utf8( + env_ptr: *mut Env, + string: *const c_char, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); + + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string as _, + if length == NAPI_AUTO_LENGTH { + std::ffi::CStr::from_ptr(string).to_bytes().len() + } else { + length + }, + ) + } + } else { + &[] + }; + + let Some(string) = v8::String::new_from_utf8( + &mut env.scope(), + buffer, + v8::NewStringType::Normal, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_string_utf16( + env_ptr: *mut Env, + string: *const u16, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); + + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) + } + } else { + &[] + }; + + let Some(string) = v8::String::new_from_two_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Normal, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn node_api_create_external_string_latin1( + env_ptr: *mut Env, + string: *const c_char, + length: usize, + nogc_finalize_callback: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_value, + copied: *mut bool, +) -> napi_status { + let status = + unsafe { napi_create_string_latin1(env_ptr, string, length, result) }; + + if status == napi_ok { + unsafe { + *copied = true; + } + + if let Some(finalize) = nogc_finalize_callback { + unsafe { + finalize(env_ptr as napi_env, string as *mut c_void, finalize_hint); + } + } + } + + status +} + +#[napi_sym] +fn node_api_create_external_string_utf16( + env_ptr: *mut Env, + string: *const u16, + length: usize, + nogc_finalize_callback: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_value, + copied: *mut bool, +) -> napi_status { + let status = + unsafe { napi_create_string_utf16(env_ptr, string, length, result) }; + + if status == napi_ok { + unsafe { + *copied = true; + } + + if let Some(finalize) = nogc_finalize_callback { + unsafe { + finalize(env_ptr as napi_env, string as *mut c_void, finalize_hint); + } + } + } + + status +} + +#[napi_sym] +fn node_api_create_property_key_utf16( + env_ptr: *mut Env, + string: *const u16, + length: usize, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + if length > 0 { + check_arg!(env, string); + } + crate::return_status_if_false!( + env, + (length == NAPI_AUTO_LENGTH) || length <= INT_MAX as _, + napi_invalid_arg + ); + + let buffer = if length > 0 { + unsafe { + std::slice::from_raw_parts( + string, + if length == NAPI_AUTO_LENGTH { + let mut length = 0; + while *(string.add(length)) != 0 { + length += 1; + } + length + } else { + length + }, + ) + } + } else { + &[] + }; + + let Some(string) = v8::String::new_from_two_byte( + &mut env.scope(), + buffer, + v8::NewStringType::Internalized, + ) else { + return napi_set_last_error(env_ptr, napi_generic_failure); + }; + + unsafe { + *result = string.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_create_double( + env_ptr: *mut Env, + value: f64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int32( + env_ptr: *mut Env, + value: i32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_uint32( + env_ptr: *mut Env, + value: u32, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Integer::new_from_unsigned(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::Number::new(&mut env.scope(), value as _).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_int64( + env_ptr: *mut Env, + value: i64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_i64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_uint64( + env_ptr: *mut Env, + value: u64, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = v8::BigInt::new_from_u64(&mut env.scope(), value).into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_bigint_words<'s>( + env: &'s mut Env, + sign_bit: bool, + word_count: usize, + words: *const u64, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, words); + check_arg!(env, result); + + if word_count > INT_MAX as _ { + return napi_invalid_arg; + } + + match v8::BigInt::new_from_words(&mut env.scope(), sign_bit, unsafe { + std::slice::from_raw_parts(words, word_count) + }) { + Some(value) => unsafe { + *result = value.into(); + }, + None => { + return napi_generic_failure; + } + } + + napi_ok +} + +#[napi_sym] +fn napi_get_boolean( + env: *mut Env, + value: bool, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::Boolean::new(env.isolate(), value).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_create_symbol( + env_ptr: *mut Env, + description: napi_value, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + let description = if let Some(d) = *description { + let Some(d) = d.to_string(&mut env.scope()) else { + return napi_set_last_error(env, napi_string_expected); + }; + Some(d) + } else { + None + }; + + unsafe { + *result = v8::Symbol::new(&mut env.scope(), description).into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn node_api_symbol_for( + env: *mut Env, + utf8description: *const c_char, + length: usize, + result: *mut napi_value, +) -> napi_status { + { + let env = check_env!(env); + check_arg!(env, result); + + let description_string = + match unsafe { check_new_from_utf8_len(env, utf8description, length) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + + unsafe { + *result = + v8::Symbol::for_key(&mut env.scope(), description_string).into(); + } + } + + napi_clear_last_error(env) +} + +macro_rules! napi_create_error_impl { + ($env_ptr:ident, $code:ident, $msg:ident, $result:ident, $error:ident) => {{ + let env_ptr = $env_ptr; + let code = $code; + let msg = $msg; + let result = $result; + + let env = check_env!(env_ptr); + check_arg!(env, msg); + check_arg!(env, result); + + let Some(message) = + msg.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + let error = v8::Exception::$error(&mut env.scope(), message); + + if let Some(code) = *code { + let error_obj: v8::Local<v8::Object> = error.try_into().unwrap(); + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code) + .unwrap_or(false) + { + return napi_set_last_error(env_ptr, napi_generic_failure); + } + } + + unsafe { + *result = error.into(); + } + + return napi_clear_last_error(env_ptr); + }}; +} + +#[napi_sym] +fn napi_create_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, error) +} + +#[napi_sym] +fn napi_create_type_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, type_error) +} + +#[napi_sym] +fn napi_create_range_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, range_error) +} + +#[napi_sym] +fn node_api_create_syntax_error( + env_ptr: *mut Env, + code: napi_value, + msg: napi_value, + result: *mut napi_value, +) -> napi_status { + napi_create_error_impl!(env_ptr, code, msg, result, syntax_error) +} + +pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> { + if value.is_undefined() { + Some(napi_undefined) + } else if value.is_null() { + Some(napi_null) + } else if value.is_external() { + Some(napi_external) + } else if value.is_boolean() { + Some(napi_boolean) + } else if value.is_number() { + Some(napi_number) + } else if value.is_big_int() { + Some(napi_bigint) + } else if value.is_string() { + Some(napi_string) + } else if value.is_symbol() { + Some(napi_symbol) + } else if value.is_function() { + Some(napi_function) + } else if value.is_object() { + Some(napi_object) + } else { + None + } +} + +#[napi_sym] +fn napi_typeof( + env: *mut Env, + value: napi_value, + result: *mut napi_valuetype, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(ty) = get_value_type(value.unwrap()) else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + unsafe { + *result = ty; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::undefined(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = v8::null(&mut env.scope()).into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_cb_info( + env: *mut Env, + cbinfo: napi_callback_info, + argc: *mut i32, + argv: *mut napi_value, + this_arg: *mut napi_value, + data: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + if !argv.is_null() { + check_arg!(env, argc); + let argc = unsafe { *argc as usize }; + for i in 0..argc { + let arg = args.get(i as _); + unsafe { + *argv.add(i) = arg.into(); + } + } + } + + if !argc.is_null() { + unsafe { + *argc = args.length(); + } + } + + if !this_arg.is_null() { + unsafe { + *this_arg = args.this().into(); + } + } + + if !data.is_null() { + unsafe { + *data = cbinfo.cb_info; + } + } + + napi_clear_last_error(env); + napi_ok +} + +#[napi_sym] +fn napi_get_new_target( + env: *mut Env, + cbinfo: napi_callback_info, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, cbinfo); + check_arg!(env, result); + + let cbinfo: &CallbackInfo = unsafe { &*(cbinfo as *const CallbackInfo) }; + let args = unsafe { &*(cbinfo.args as *const v8::FunctionCallbackArguments) }; + + unsafe { + *result = args.new_target().into(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_call_function<'s>( + env: &'s mut Env, + recv: napi_value<'s>, + func: napi_value<'s>, + argc: usize, + argv: *const napi_value<'s>, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, recv); + let args = if argc > 0 { + check_arg!(env, argv); + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc) + } + } else { + &[] + }; + + let Some(func) = + func.and_then(|f| v8::Local::<v8::Function>::try_from(f).ok()) + else { + return napi_function_expected; + }; + + let Some(v) = func.call(&mut env.scope(), recv.unwrap(), args) else { + return napi_generic_failure; + }; + + if !result.is_null() { + unsafe { + *result = v.into(); + } + } + + napi_ok +} + +#[napi_sym] +fn napi_get_global(env_ptr: *mut Env, result: *mut napi_value) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + let global = v8::Local::new(&mut env.scope(), &env.global); + unsafe { + *result = global.into(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_throw(env: *mut Env, error: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, error); + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let error = error.unwrap(); + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) +} + +macro_rules! napi_throw_error_impl { + ($env:ident, $code:ident, $msg:ident, $error:ident) => {{ + let env = check_env!($env); + let env_ptr = env as *mut Env; + let code = $code; + let msg = $msg; + + if env.last_exception.is_some() { + return napi_pending_exception; + } + + let str_ = match unsafe { check_new_from_utf8(env, msg) } { + Ok(s) => s, + Err(status) => return status, + }; + + let error = v8::Exception::$error(&mut env.scope(), str_); + + if !code.is_null() { + let error_obj: v8::Local<v8::Object> = error.try_into().unwrap(); + let code = match unsafe { check_new_from_utf8(env_ptr, code) } { + Ok(s) => s, + Err(status) => return napi_set_last_error(env, status), + }; + let code_key = v8::String::new(&mut env.scope(), "code").unwrap(); + if !error_obj + .set(&mut env.scope(), code_key.into(), code.into()) + .unwrap_or(false) + { + return napi_set_last_error(env, napi_generic_failure); + } + } + + env.scope().throw_exception(error); + let error = v8::Global::new(&mut env.scope(), error); + env.last_exception = Some(error); + + napi_clear_last_error(env) + }}; +} + +#[napi_sym] +fn napi_throw_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, error) +} + +#[napi_sym] +fn napi_throw_type_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, type_error) +} + +#[napi_sym] +fn napi_throw_range_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, range_error) +} + +#[napi_sym] +fn node_api_throw_syntax_error( + env: *mut Env, + code: *const c_char, + msg: *const c_char, +) -> napi_status { + napi_throw_error_impl!(env, code, msg, syntax_error) +} + +#[napi_sym] +fn napi_is_error( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_native_error(); + } + + return napi_clear_last_error(env); +} + +#[napi_sym] +fn napi_get_value_double( + env_ptr: *mut Env, + value: napi_value, + result: *mut f64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(number) = + value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); + }; + + unsafe { + *result = number.value(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_int32( + env_ptr: *mut Env, + value: napi_value, + result: *mut i32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().int32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); + }; + + unsafe { + *result = value; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_uint32( + env_ptr: *mut Env, + value: napi_value, + result: *mut u32, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(value) = value.unwrap().uint32_value(&mut env.scope()) else { + return napi_set_last_error(env, napi_number_expected); + }; + + unsafe { + *result = value; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_int64( + env_ptr: *mut Env, + value: napi_value, + result: *mut i64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(number) = + value.and_then(|v| v8::Local::<v8::Number>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_number_expected); + }; + + let value = number.value(); + + unsafe { + if value.is_finite() { + *result = value as _; + } else { + *result = 0; + } + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_bigint_int64( + env_ptr: *mut Env, + value: napi_value, + result: *mut i64, + lossless: *mut bool, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + + let (result_, lossless_) = bigint.i64_value(); + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_bigint_uint64( + env_ptr: *mut Env, + value: napi_value, + result: *mut u64, + lossless: *mut bool, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + check_arg!(env, lossless); + + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + + let (result_, lossless_) = bigint.u64_value(); + + unsafe { + *result = result_; + *lossless = lossless_; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_bigint_words( + env_ptr: *mut Env, + value: napi_value, + sign_bit: *mut i32, + word_count: *mut usize, + words: *mut u64, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, word_count); + + let Some(bigint) = + value.and_then(|v| v8::Local::<v8::BigInt>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_bigint_expected); + }; + + let word_count_int; + + if sign_bit.is_null() && words.is_null() { + word_count_int = bigint.word_count(); + } else { + check_arg!(env, sign_bit); + check_arg!(env, words); + let out_words = + unsafe { std::slice::from_raw_parts_mut(words, *word_count) }; + let (sign, slice_) = bigint.to_words_array(out_words); + word_count_int = slice_.len(); + unsafe { + *sign_bit = sign as i32; + } + } + + unsafe { + *word_count = word_count_int; + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_bool( + env_ptr: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, result); + + let Some(boolean) = + value.and_then(|v| v8::Local::<v8::Boolean>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_boolean_expected); + }; + + unsafe { + *result = boolean.is_true(); + } + + return napi_clear_last_error(env_ptr); +} + +#[napi_sym] +fn napi_get_value_string_latin1( + env_ptr: *mut Env, + value: napi_value, + buf: *mut c_char, + bufsize: usize, + result: *mut usize, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + if buf.is_null() { + check_arg!(env, result); + unsafe { + *result = value.length(); + } + } else if bufsize != 0 { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_one_byte( + &mut env.scope(), + buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + unsafe { + buf.add(copied).write(0); + } + if !result.is_null() { + unsafe { + *result = copied; + } + } + } else if !result.is_null() { + unsafe { + *result = 0; + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_get_value_string_utf8( + env_ptr: *mut Env, + value: napi_value, + buf: *mut u8, + bufsize: usize, + result: *mut usize, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + if buf.is_null() { + check_arg!(env, result); + unsafe { + *result = value.utf8_length(env.isolate()); + } + } else if bufsize != 0 { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write_utf8( + &mut env.scope(), + buffer, + None, + v8::WriteOptions::REPLACE_INVALID_UTF8 + | v8::WriteOptions::NO_NULL_TERMINATION, + ); + unsafe { + buf.add(copied).write(0); + } + if !result.is_null() { + unsafe { + *result = copied; + } + } + } else if !result.is_null() { + unsafe { + *result = 0; + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_get_value_string_utf16( + env_ptr: *mut Env, + value: napi_value, + buf: *mut u16, + bufsize: usize, + result: *mut usize, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + + let Some(value) = + value.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_string_expected); + }; + + if buf.is_null() { + check_arg!(env, result); + unsafe { + *result = value.length(); + } + } else if bufsize != 0 { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; + let copied = value.write( + &mut env.scope(), + buffer, + 0, + v8::WriteOptions::NO_NULL_TERMINATION, + ); + unsafe { + buf.add(copied).write(0); + } + if !result.is_null() { + unsafe { + *result = copied; + } + } + } else if !result.is_null() { + unsafe { + *result = 0; + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_coerce_to_bool<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let coerced = value.unwrap().to_boolean(&mut env.scope()); + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_number<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_number(&mut env.scope()) else { + return napi_number_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_object<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_coerce_to_string<'s>( + env: &'s mut Env, + value: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, value); + check_arg!(env, result); + + let Some(coerced) = value.unwrap().to_string(&mut env.scope()) else { + return napi_string_expected; + }; + + unsafe { + *result = coerced.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_wrap( + env: &mut Env, + js_object: napi_value, + native_object: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_ref, +) -> napi_status { + check_arg!(env, js_object); + let env_ptr = env as *mut Env; + + let Some(obj) = + js_object.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + + if obj + .has_private(&mut env.scope(), napi_wrap) + .unwrap_or(false) + { + return napi_invalid_arg; + } + + if !result.is_null() { + check_arg!(env, finalize_cb); + } + + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland + }; + let reference = Reference::new( + env_ptr, + obj.into(), + 0, + ownership, + finalize_cb, + native_object, + finalize_hint, + ); + + let reference = Reference::into_raw(reference) as *mut c_void; + + if !result.is_null() { + check_arg!(env, finalize_cb); + unsafe { + *result = reference; + } + } + + let external = v8::External::new(&mut env.scope(), reference); + assert!(obj + .set_private(&mut env.scope(), napi_wrap, external.into()) + .unwrap()); + + napi_ok +} + +fn unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, + keep: bool, +) -> napi_status { + check_arg!(env, obj); + if keep { + check_arg!(env, result); + } + + let Some(obj) = obj.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap); + let Some(val) = obj.get_private(&mut env.scope(), napi_wrap) else { + return napi_invalid_arg; + }; + + let Ok(external) = v8::Local::<v8::External>::try_from(val) else { + return napi_invalid_arg; + }; + + let reference = external.value() as *mut Reference; + let reference = unsafe { &mut *reference }; + + if !result.is_null() { + unsafe { + *result = reference.finalize_data; + } + } + + if !keep { + assert!(obj + .delete_private(&mut env.scope(), napi_wrap) + .unwrap_or(false)); + unsafe { Reference::remove(reference) }; + } + + napi_ok +} + +#[napi_sym] +fn napi_unwrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, +) -> napi_status { + unwrap(env, obj, result, true) +} + +#[napi_sym] +fn napi_remove_wrap( + env: &mut Env, + obj: napi_value, + result: *mut *mut c_void, +) -> napi_status { + unwrap(env, obj, result, false) +} + +struct ExternalWrapper { + data: *mut c_void, + type_tag: Option<napi_type_tag>, +} + +#[napi_sym] +fn napi_create_external<'s>( + env: &'s mut Env, + data: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + let env_ptr = env as *mut Env; + check_arg!(env, result); + + let wrapper = Box::new(ExternalWrapper { + data, + type_tag: None, + }); + + let wrapper = Box::into_raw(wrapper); + let external = v8::External::new(&mut env.scope(), wrapper as _); + + if let Some(finalize_cb) = finalize_cb { + Reference::into_raw(Reference::new( + env_ptr, + external.into(), + 0, + ReferenceOwnership::Runtime, + Some(finalize_cb), + data, + finalize_hint, + )); + } + + unsafe { + *result = external.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_type_tag_object( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, +) -> napi_status { + check_arg!(env, object_or_external); + check_arg!(env, type_tag); + + let val = object_or_external.unwrap(); + + if let Ok(external) = v8::Local::<v8::External>::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + if wrapper.type_tag.is_some() { + return napi_invalid_arg; + } + wrapper.type_tag = Some(unsafe { *type_tag }); + return napi_ok; + } + + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); + + if object.has_private(&mut env.scope(), key).unwrap_or(false) { + return napi_invalid_arg; + } + + let slice = unsafe { std::slice::from_raw_parts(type_tag as *const u64, 2) }; + let Some(tag) = v8::BigInt::new_from_words(&mut env.scope(), false, slice) + else { + return napi_generic_failure; + }; + + if !object + .set_private(&mut env.scope(), key, tag.into()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_check_object_type_tag( + env: &mut Env, + object_or_external: napi_value, + type_tag: *const napi_type_tag, + result: *mut bool, +) -> napi_status { + check_arg!(env, object_or_external); + check_arg!(env, type_tag); + check_arg!(env, result); + + let type_tag = unsafe { *type_tag }; + + let val = object_or_external.unwrap(); + + if let Ok(external) = v8::Local::<v8::External>::try_from(val) { + let wrapper_ptr = external.value() as *mut ExternalWrapper; + let wrapper = unsafe { &mut *wrapper_ptr }; + unsafe { + *result = match wrapper.type_tag { + Some(t) => t == type_tag, + None => false, + }; + }; + return napi_ok; + } + + let Some(object) = val.to_object(&mut env.scope()) else { + return napi_object_expected; + }; + + let key = v8::Local::new(&mut env.scope(), &env.shared().type_tag); + + let Some(val) = object.get_private(&mut env.scope(), key) else { + return napi_generic_failure; + }; + + unsafe { + *result = false; + } + + if let Ok(bigint) = v8::Local::<v8::BigInt>::try_from(val) { + let mut words = [0u64; 2]; + let (sign, words) = bigint.to_words_array(&mut words); + if !sign { + let pass = if words.len() == 2 { + type_tag.lower == words[0] && type_tag.upper == words[1] + } else if words.len() == 1 { + type_tag.lower == words[0] && type_tag.upper == 0 + } else if words.is_empty() { + type_tag.lower == 0 && type_tag.upper == 0 + } else { + false + }; + unsafe { + *result = pass; + } + } + } + + napi_ok +} + +#[napi_sym] +fn napi_get_value_external( + env: *mut Env, + value: napi_value, + result: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let Some(external) = + value.and_then(|v| v8::Local::<v8::External>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + let wrapper_ptr = external.value() as *const ExternalWrapper; + let wrapper = unsafe { &*wrapper_ptr }; + + unsafe { + *result = wrapper.data; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_create_reference( + env: *mut Env, + value: napi_value, + initial_refcount: u32, + result: *mut napi_ref, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + let value = value.unwrap(); + + let reference = Reference::new( + env, + value, + initial_refcount, + ReferenceOwnership::Userland, + None, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + + let ptr = Reference::into_raw(reference); + + unsafe { + *result = ptr as _; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_delete_reference(env: *mut Env, ref_: napi_ref) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); + + let reference = unsafe { Reference::from_raw(ref_ as _) }; + + drop(reference); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_reference_ref( + env: *mut Env, + ref_: napi_ref, + result: *mut u32, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); + + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + let count = reference.ref_(); + + if !result.is_null() { + unsafe { + *result = count; + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_reference_unref( + env: *mut Env, + ref_: napi_ref, + result: *mut u32, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, ref_); + + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + if reference.ref_count == 0 { + return napi_set_last_error(env, napi_generic_failure); + } + + let count = reference.unref(); + + if !result.is_null() { + unsafe { + *result = count; + } + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_reference_value( + env_ptr: *mut Env, + ref_: napi_ref, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, ref_); + check_arg!(env, result); + + let reference = unsafe { &mut *(ref_ as *mut Reference) }; + + let value = match &reference.state { + ReferenceState::Strong(g) => Some(v8::Local::new(&mut env.scope(), g)), + ReferenceState::Weak(w) => w.to_local(&mut env.scope()), + }; + + unsafe { + *result = value.into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_open_handle_scope( + env: *mut Env, + _result: *mut napi_handle_scope, +) -> napi_status { + let env = check_env!(env); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_close_handle_scope( + env: *mut Env, + _scope: napi_handle_scope, +) -> napi_status { + let env = check_env!(env); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_open_escapable_handle_scope( + env: *mut Env, + _result: *mut napi_escapable_handle_scope, +) -> napi_status { + let env = check_env!(env); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_close_escapable_handle_scope( + env: *mut Env, + _scope: napi_escapable_handle_scope, +) -> napi_status { + let env = check_env!(env); + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_escape_handle<'s>( + env: *mut Env, + _scope: napi_escapable_handle_scope, + escapee: napi_value<'s>, + result: *mut napi_value<'s>, +) -> napi_status { + let env = check_env!(env); + + unsafe { + *result = escapee; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_new_instance<'s>( + env: &'s mut Env, + constructor: napi_value, + argc: usize, + argv: *const napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, constructor); + if argc > 0 { + check_arg!(env, argv); + } + check_arg!(env, result); + + let Some(func) = + constructor.and_then(|v| v8::Local::<v8::Function>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + let args = if argc > 0 { + unsafe { + std::slice::from_raw_parts(argv as *mut v8::Local<v8::Value>, argc) + } + } else { + &[] + }; + + let Some(value) = func.new_instance(&mut env.scope(), args) else { + return napi_pending_exception; + }; + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_instanceof( + env: &mut Env, + object: napi_value, + constructor: napi_value, + result: *mut bool, +) -> napi_status { + check_arg!(env, object); + check_arg!(env, result); + + let Some(ctor) = constructor.and_then(|v| v.to_object(&mut env.scope())) + else { + return napi_object_expected; + }; + + if !ctor.is_function() { + unsafe { + napi_throw_type_error( + env, + c"ERR_NAPI_CONS_FUNCTION".as_ptr(), + c"Constructor must be a function".as_ptr(), + ); + } + return napi_function_expected; + } + + let Some(res) = object.unwrap().instance_of(&mut env.scope(), ctor) else { + return napi_generic_failure; + }; + + unsafe { + *result = res; + } + + napi_ok +} + +#[napi_sym] +fn napi_is_exception_pending( + env_ptr: *mut Env, + result: *mut bool, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + unsafe { + *result = env.last_exception.is_some(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_get_and_clear_last_exception( + env_ptr: *mut Env, + result: *mut napi_value, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, result); + + let ex: v8::Local<v8::Value> = + if let Some(last_exception) = env.last_exception.take() { + v8::Local::new(&mut env.scope(), last_exception) + } else { + v8::undefined(&mut env.scope()).into() + }; + + unsafe { + *result = ex.into(); + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_is_arraybuffer( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_array_buffer(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_create_arraybuffer<'s>( + env: &'s mut Env, + len: usize, + data: *mut *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let buffer = v8::ArrayBuffer::new(&mut env.scope(), len); + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buffer); + } + } + + unsafe { + *result = buffer.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_external_arraybuffer<'s>( + env: &'s mut Env, + data: *mut c_void, + byte_length: usize, + finalize_cb: napi_finalize, + finalize_hint: *mut c_void, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let store = make_external_backing_store( + env, + data, + byte_length, + std::ptr::null_mut(), + finalize_cb, + finalize_hint, + ); + + let ab = + v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); + let value: v8::Local<v8::Value> = ab.into(); + + unsafe { + *result = value.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_arraybuffer_info( + env: *mut Env, + value: napi_value, + data: *mut *mut c_void, + length: *mut usize, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + + let Some(buf) = + value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + if !data.is_null() { + unsafe { + *data = get_array_buffer_ptr(buf); + } + } + + if !length.is_null() { + unsafe { + *length = buf.byte_length(); + } + } + + napi_ok +} + +#[napi_sym] +fn napi_is_typedarray( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_typed_array(); + } + + napi_ok +} + +#[napi_sym] +fn napi_create_typedarray<'s>( + env: &'s mut Env, + ty: napi_typedarray_type, + length: usize, + arraybuffer: napi_value, + byte_offset: usize, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, arraybuffer); + check_arg!(env, result); + + let Some(ab) = + arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_arraybuffer_expected; + }; + + macro_rules! create { + ($TypedArray:ident, $size_of_element:expr) => {{ + let soe = $size_of_element; + if soe > 1 && byte_offset % soe != 0 { + let message = v8::String::new( + &mut env.scope(), + format!( + "start offset of {} should be multiple of {}", + stringify!($TypedArray), + soe + ) + .as_str(), + ) + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + if length * soe + byte_offset > ab.byte_length() { + let message = + v8::String::new(&mut env.scope(), "Invalid typed array length") + .unwrap(); + let exc = v8::Exception::range_error(&mut env.scope(), message); + env.scope().throw_exception(exc); + return napi_pending_exception; + } + + let Some(ta) = + v8::$TypedArray::new(&mut env.scope(), ab, byte_offset, length) + else { + return napi_generic_failure; + }; + ta.into() + }}; + } + + let typedarray: v8::Local<v8::Value> = match ty { + napi_uint8_array => create!(Uint8Array, 1), + napi_uint8_clamped_array => create!(Uint8ClampedArray, 1), + napi_int8_array => create!(Int8Array, 1), + napi_uint16_array => create!(Uint16Array, 2), + napi_int16_array => create!(Int16Array, 2), + napi_uint32_array => create!(Uint32Array, 4), + napi_int32_array => create!(Int32Array, 4), + napi_float32_array => create!(Float32Array, 4), + napi_float64_array => create!(Float64Array, 8), + napi_bigint64_array => create!(BigInt64Array, 8), + napi_biguint64_array => create!(BigUint64Array, 8), + _ => { + return napi_invalid_arg; + } + }; + + unsafe { + *result = typedarray.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_get_typedarray_info( + env_ptr: *mut Env, + typedarray: napi_value, + type_: *mut napi_typedarray_type, + length: *mut usize, + data: *mut *mut c_void, + arraybuffer: *mut napi_value, + byte_offset: *mut usize, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, typedarray); + + let Some(array) = + typedarray.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok()) + else { + return napi_set_last_error(env_ptr, napi_invalid_arg); + }; + + if !type_.is_null() { + let tatype = if array.is_int8_array() { + napi_int8_array + } else if array.is_uint8_array() { + napi_uint8_array + } else if array.is_uint8_clamped_array() { + napi_uint8_clamped_array + } else if array.is_int16_array() { + napi_int16_array + } else if array.is_uint16_array() { + napi_uint16_array + } else if array.is_int32_array() { + napi_int32_array + } else if array.is_uint32_array() { + napi_uint32_array + } else if array.is_float32_array() { + napi_float32_array + } else if array.is_float64_array() { + napi_float64_array + } else if array.is_big_int64_array() { + napi_bigint64_array + } else if array.is_big_uint64_array() { + napi_biguint64_array + } else { + unreachable!(); + }; + + unsafe { + *type_ = tatype; + } + } + + if !length.is_null() { + unsafe { + *length = array.length(); + } + } + + if !data.is_null() { + unsafe { + *data = array.data(); + } + } + + if !arraybuffer.is_null() { + let buf = array.buffer(&mut env.scope()).unwrap(); + unsafe { + *arraybuffer = buf.into(); + } + } + + if !byte_offset.is_null() { + unsafe { + *byte_offset = array.byte_offset(); + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_create_dataview<'s>( + env: &'s mut Env, + byte_length: usize, + arraybuffer: napi_value<'s>, + byte_offset: usize, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, arraybuffer); + check_arg!(env, result); + + let Some(buffer) = + arraybuffer.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + if byte_length + byte_offset > buffer.byte_length() { + unsafe { + return napi_throw_range_error( + env, + c"ERR_NAPI_INVALID_DATAVIEW_ARGS".as_ptr(), + c"byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in".as_ptr(), + ); + } + } + + let dataview = + v8::DataView::new(&mut env.scope(), buffer, byte_offset, byte_length); + + unsafe { + *result = dataview.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_is_dataview( + env: *mut Env, + value: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, result); + + unsafe { + *result = value.unwrap().is_data_view(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_dataview_info( + env_ptr: *mut Env, + dataview: napi_value, + byte_length: *mut usize, + data: *mut *mut c_void, + arraybuffer: *mut napi_value, + byte_offset: *mut usize, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, dataview); + + let Some(array) = + dataview.and_then(|v| v8::Local::<v8::DataView>::try_from(v).ok()) + else { + return napi_invalid_arg; + }; + + if !byte_length.is_null() { + unsafe { + *byte_length = array.byte_length(); + } + } + + if !arraybuffer.is_null() { + let Some(buffer) = array.buffer(&mut env.scope()) else { + return napi_generic_failure; + }; + + unsafe { + *arraybuffer = buffer.into(); + } + } + + if !data.is_null() { + unsafe { + *data = array.data(); + } + } + + if !byte_offset.is_null() { + unsafe { + *byte_offset = array.byte_offset(); + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn napi_get_version(env: *mut Env, result: *mut u32) -> napi_status { + let env = check_env!(env); + check_arg!(env, result); + + unsafe { + *result = NAPI_VERSION; + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_create_promise<'s>( + env: &'s mut Env, + deferred: *mut napi_deferred, + promise: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, deferred); + check_arg!(env, promise); + + let resolver = v8::PromiseResolver::new(&mut env.scope()).unwrap(); + + let global = v8::Global::new(&mut env.scope(), resolver); + let global_ptr = global.into_raw().as_ptr() as napi_deferred; + + let p = resolver.get_promise(&mut env.scope()); + + unsafe { + *deferred = global_ptr; + } + + unsafe { + *promise = p.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_resolve_deferred( + env: &mut Env, + deferred: napi_deferred, + result: napi_value, +) -> napi_status { + check_arg!(env, result); + check_arg!(env, deferred); + + // Make sure microtasks don't run and call back into JS + env + .scope() + .set_microtasks_policy(v8::MicrotasksPolicy::Explicit); + + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(env.isolate(), deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + let success = resolver + .resolve(&mut env.scope(), result.unwrap()) + .unwrap_or(false); + + // Restore policy + env + .scope() + .set_microtasks_policy(v8::MicrotasksPolicy::Auto); + + if success { + napi_ok + } else { + napi_generic_failure + } +} + +#[napi_sym] +fn napi_reject_deferred( + env: &mut Env, + deferred: napi_deferred, + result: napi_value, +) -> napi_status { + check_arg!(env, result); + check_arg!(env, deferred); + + let deferred_ptr = + unsafe { NonNull::new_unchecked(deferred as *mut v8::PromiseResolver) }; + let global = unsafe { v8::Global::from_raw(env.isolate(), deferred_ptr) }; + let resolver = v8::Local::new(&mut env.scope(), global); + + if !resolver + .reject(&mut env.scope(), result.unwrap()) + .unwrap_or(false) + { + return napi_generic_failure; + } + + napi_ok +} + +#[napi_sym] +fn napi_is_promise( + env: *mut Env, + value: napi_value, + is_promise: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_promise); + + unsafe { + *is_promise = value.unwrap().is_promise(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_create_date<'s>( + env: &'s mut Env, + time: f64, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, result); + + let Some(date) = v8::Date::new(&mut env.scope(), time) else { + return napi_generic_failure; + }; + + unsafe { + *result = date.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_is_date( + env: *mut Env, + value: napi_value, + is_date: *mut bool, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + check_arg!(env, is_date); + + unsafe { + *is_date = value.unwrap().is_date(); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_date_value( + env: &mut Env, + value: napi_value, + result: *mut f64, +) -> napi_status { + check_arg!(env, result); + + let Some(date) = value.and_then(|v| v8::Local::<v8::Date>::try_from(v).ok()) + else { + return napi_date_expected; + }; + + unsafe { + *result = date.value_of(); + } + + napi_ok +} + +#[napi_sym] +fn napi_run_script<'s>( + env: &'s mut Env, + script: napi_value, + result: *mut napi_value<'s>, +) -> napi_status { + check_arg!(env, script); + check_arg!(env, result); + + let Some(script) = + script.and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + else { + return napi_string_expected; + }; + + let Some(script) = v8::Script::compile(&mut env.scope(), script, None) else { + return napi_generic_failure; + }; + + let Some(rv) = script.run(&mut env.scope()) else { + return napi_generic_failure; + }; + + unsafe { + *result = rv.into(); + } + + napi_ok +} + +#[napi_sym] +fn napi_add_finalizer( + env_ptr: *mut Env, + value: napi_value, + finalize_data: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, + result: *mut napi_ref, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, value); + check_arg!(env, finalize_cb); + + let Some(value) = + value.and_then(|v| v8::Local::<v8::Object>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_invalid_arg); + }; + + let ownership = if result.is_null() { + ReferenceOwnership::Runtime + } else { + ReferenceOwnership::Userland + }; + let reference = Reference::new( + env, + value.into(), + 0, + ownership, + finalize_cb, + finalize_data, + finalize_hint, + ); + + if !result.is_null() { + unsafe { + *result = Reference::into_raw(reference) as _; + } + } + + napi_clear_last_error(env_ptr) +} + +#[napi_sym] +fn node_api_post_finalizer( + env: *mut Env, + _finalize_cb: napi_finalize, + _finalize_data: *mut c_void, + _finalize_hint: *mut c_void, +) -> napi_status { + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_adjust_external_memory( + env: *mut Env, + change_in_bytes: i64, + adjusted_value: *mut i64, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, adjusted_value); + + unsafe { + *adjusted_value = env + .isolate() + .adjust_amount_of_external_allocated_memory(change_in_bytes); + } + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_set_instance_data( + env: *mut Env, + data: *mut c_void, + finalize_cb: Option<napi_finalize>, + finalize_hint: *mut c_void, +) -> napi_status { + let env = check_env!(env); + + env.shared_mut().instance_data = Some(InstanceData { + data, + finalize_cb, + finalize_hint, + }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_instance_data( + env: *mut Env, + data: *mut *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, data); + + let instance_data = match &env.shared().instance_data { + Some(v) => v.data, + None => std::ptr::null_mut(), + }; + + unsafe { *data = instance_data }; + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_detach_arraybuffer(env: *mut Env, value: napi_value) -> napi_status { + let env = check_env!(env); + check_arg!(env, value); + + let Some(ab) = + value.and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + else { + return napi_set_last_error(env, napi_arraybuffer_expected); + }; + + if !ab.is_detachable() { + return napi_set_last_error(env, napi_detachable_arraybuffer_expected); + } + + // Expected to crash for None. + ab.detach(None).unwrap(); + + napi_clear_last_error(env); + napi_ok +} + +#[napi_sym] +fn napi_is_detached_arraybuffer( + env_ptr: *mut Env, + arraybuffer: napi_value, + result: *mut bool, +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, arraybuffer); + check_arg!(env, result); + + let is_detached = match arraybuffer + .and_then(|v| v8::Local::<v8::ArrayBuffer>::try_from(v).ok()) + { + Some(ab) => ab.was_detached(), + None => false, + }; + + unsafe { + *result = is_detached; + } + + napi_clear_last_error(env) +} |