/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/napi/js_native_api.rs
4 367 строк
97 KB
Nathan Whitaker
fix(napi): synchronize external string finalizers (#36078)
20 июл 2026, 21:45
Не верифицирован
20 июл 2026, 21:45
e471f87
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. #![allow(non_upper_case_globals, reason = "native code")] #![deny(unsafe_op_in_unsafe_fn)] const NAPI_VERSION: u32 = 10; use std::ptr::NonNull; use libc::INT_MAX; use napi_sym::napi_sym; use super::util::SendPtr; 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::CallbackInfo; use crate::function::CallbackInfoScope; use crate::function::create_function; use crate::function::create_function_template; use crate::*; #[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 }; // Copy out the callback fields before resetting; the finalize_cb may free // the Reference (UAF if we kept borrowing it). let finalize_cb = reference.finalize_cb; let finalize_data = reference.finalize_data; let finalize_hint = reference.finalize_hint; let ownership = reference.ownership; let env_ptr = reference.env; reference.reset(); // Note: we are NOT inside a V8 GC pause here. `v8::Weak::with_finalizer` // (rusty_v8) schedules this closure as V8's *second-pass* weak callback, // which V8 invokes after the GC cycle completes, with the isolate in a // fully usable state. This is rusty_v8's equivalent of Node.js's // `node_napi_env__::DrainFinalizerQueue` (driven by SetImmediate): both // amount to "run the finalizer after GC, with a live Isolate." Calling // user-provided `napi_finalize` callbacks here is therefore safe. // // History: a previous attempt (#33260) deferred this work through // `V8CrossThreadTaskSpawner::spawn` under the (incorrect) belief that // rusty_v8 invoked us inside a GC pause. The cross-thread spawner takes a // `Mutex` and pokes a tokio `AtomicWaker` synchronously on every // finalizer; doing that from V8's second-pass callback is what produced // the intermittent `napi_unwrap` failure reported in #33924 / #34008 // ("Failed to unwrap exclusive reference of `...` type from napi value"). if let Some(finalize_cb) = finalize_cb { // Deregister before dispatching so the finalizer is not called again // at env shutdown (matches Node's `Unlink` ordering in // `Reference::Finalize`). unsafe { &*env_ptr }.remove_ref_finalizer(finalize_data); unsafe { finalize_cb(env_ptr as _, finalize_data, finalize_hint); } } if ownership == ReferenceOwnership::Runtime { // Runtime-owned: free the Reference now. Userland-owned references are // freed by the addon via `napi_delete_reference` (or by napi-rs's // REFERENCE_MAP overwrite path). 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.get() == napi_ok { napi_clear_last_error(env); } else { env.last_error.error_message = ERROR_MESSAGES[env.last_error.error_code.get() 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>, data: *mut c_void, 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 { v8::callback_scope!(unsafe scope, env.context()); *result = create_function(scope, env_ptr, name, cb.unwrap(), data).into(); } napi_ok } #[napi_sym] #[allow(clippy::too_many_arguments, reason = "matches Node-API signature")] 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, }; v8::callback_scope!(unsafe scope, env.context()); let tpl = { create_function_template( 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(scope, env_ptr, None, g, p.data)); let setter = p .setter .map(|s| create_function_template(scope, env_ptr, None, s, p.data)); let proto = tpl.prototype_template(scope); proto.set_accessor_property(name, getter, setter, accessor_property); } else if let Some(method) = p.method { let function = create_function_template(scope, env_ptr, None, method, p.data); let proto = tpl.prototype_template(scope); proto.set_with_attr(name, function.into(), accessor_property); } else { let proto = tpl.prototype_template(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(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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); let Some(object) = object.and_then(|o| o.to_object(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(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; v8::callback_scope!(unsafe scope, env.context()); let Some(object) = object.and_then(|o| o.to_object(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(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; v8::callback_scope!(unsafe scope, env.context()); let Some(object) = object.and_then(|o| o.to_object(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(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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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 { v8::callback_scope!(unsafe scope, env.context()); 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); } v8::callback_scope!(unsafe scope, env.context()); 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 { v8::callback_scope!(unsafe scope, env.context()); create_function(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 { v8::callback_scope!(unsafe scope, env.context()); create_function(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> = { v8::callback_scope!(unsafe scope, env.context()); let function = create_function(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 { v8::callback_scope!(unsafe scope, env.context()); 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 { v8::callback_scope!(unsafe scope, env.context()); 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); v8::callback_scope!(unsafe scope, env.context()); 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 { v8::callback_scope!(unsafe scope, env.context()); *result = v8::Object::new(scope).into(); } return napi_clear_last_error(env_ptr); } #[napi_sym] fn node_api_create_object_with_properties<'s>( env_ptr: *mut Env, prototype_or_null: napi_value<'s>, property_names: *const napi_value<'s>, property_values: *const napi_value<'s>, property_count: usize, result: *mut napi_value<'s>, ) -> napi_status { let env = check_env!(env_ptr); check_arg!(env, result); if property_count > 0 { check_arg!(env, property_names); check_arg!(env, property_values); } unsafe { v8::callback_scope!(unsafe scope, env.context()); let prototype = if let Some(proto) = *prototype_or_null { proto } else { v8::null(scope).into() }; let (names, values): (&[napi_value<'s>], &[napi_value<'s>]) = if property_count == 0 { (&[], &[]) } else { ( std::slice::from_raw_parts(property_names, property_count), std::slice::from_raw_parts(property_values, property_count), ) }; for name in names { if let Some(name_val) = **name { if !name_val.is_name() { return napi_name_expected; } } else { return napi_invalid_arg; } } let mut v8_names = Vec::with_capacity(property_count); let mut v8_values = Vec::with_capacity(property_count); for i in 0..property_count { if let Some(name) = *names[i] { v8_names.push(name.try_cast::<v8::Name>().unwrap()); } if let Some(value) = *values[i] { v8_values.push(value); } } *result = v8::Object::with_prototype_and_properties( scope, prototype, &v8_names, &v8_values, ) .into(); } return napi_clear_last_error(env_ptr); } #[napi_sym] fn node_api_create_object_with_named_properties<'s>( env_ptr: *mut Env, result: *mut napi_value<'s>, property_count: usize, property_names: *const *const c_char, property_values: *const napi_value<'s>, ) -> napi_status { let env = check_env!(env_ptr); check_arg!(env, result); if property_count > 0 { check_arg!(env, property_names); check_arg!(env, property_values); } unsafe { v8::callback_scope!(unsafe scope, env.context()); let names = if property_count == 0 { &[] } else { std::slice::from_raw_parts(property_names, property_count) }; let values = if property_count == 0 { &[] } else { std::slice::from_raw_parts(property_values, property_count) }; let mut v8_names = Vec::with_capacity(property_count); let mut v8_values = Vec::with_capacity(property_count); for i in 0..property_count { let c_str = names[i]; if c_str.is_null() { return napi_invalid_arg; } let name_str = std::ffi::CStr::from_ptr(c_str); let Some(v8_name) = v8::String::new_from_utf8( scope, name_str.to_bytes(), v8::NewStringType::Internalized, ) else { return napi_set_last_error(env_ptr, napi_generic_failure); }; v8_names.push(v8_name.into()); if let Some(value) = *values[i] { v8_values.push(value); } else { return napi_invalid_arg; } } let prototype = v8::null(scope).into(); *result = v8::Object::with_prototype_and_properties( scope, prototype, &v8_names, &v8_values, ) .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 { v8::callback_scope!(unsafe scope, env.context()); *result = v8::Array::new(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 { v8::callback_scope!(unsafe scope, env.context()); *result = v8::Array::new(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::callback_scope!(unsafe scope, env.context()); v8::String::new_from_one_byte(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::callback_scope!(unsafe scope, env.context()); v8::String::new_from_utf8(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 { &[] }; v8::callback_scope!(unsafe scope, env.context()); let Some(string) = v8::String::new_from_two_byte(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); } #[derive(Clone, Copy, Eq, Hash, PartialEq)] enum ExternalStringEncoding { OneByte, TwoByte, } #[derive(Clone, Copy, Eq, Hash, PartialEq)] struct ExternalStringKey { data: usize, len: usize, encoding: ExternalStringEncoding, } struct ExternalStringFinalizer { env: Option<usize>, callback: napi_finalize, data: usize, hint: usize, sender: deno_core::V8CrossThreadTaskSpawner, ready: bool, } #[derive(Default)] struct ExternalStringFinalizers { next_id: u64, active: std::collections::HashMap<ExternalStringKey, u64>, entries: std::collections::HashMap<u64, ExternalStringFinalizer>, } static EXTERNAL_STRING_FINALIZERS: std::sync::LazyLock< std::sync::Mutex<ExternalStringFinalizers>, > = std::sync::LazyLock::new(|| { std::sync::Mutex::new(ExternalStringFinalizers::default()) }); fn register_external_string_finalizer( key: ExternalStringKey, env: *mut Env, callback: napi_finalize, data: *mut c_void, hint: *mut c_void, ) -> Option<u64> { let mut finalizers = EXTERNAL_STRING_FINALIZERS.lock().unwrap(); // rusty_v8's raw destructor callback only identifies a resource by its // data pointer and length. Copy an indistinguishable second string instead // of risking delivery of the wrong callback or hint. if finalizers.active.contains_key(&key) { return None; } let id = finalizers.next_id; finalizers.next_id = finalizers .next_id .checked_add(1) .expect("external string finalizer id overflow"); finalizers.active.insert(key, id); finalizers.entries.insert( id, ExternalStringFinalizer { env: Some(env as usize), callback, data: data as usize, hint: hint as usize, sender: unsafe { &*env }.async_work_sender.clone(), ready: false, }, ); Some(id) } fn cancel_external_string_finalizer(key: ExternalStringKey, id: u64) -> bool { let mut finalizers = EXTERNAL_STRING_FINALIZERS.lock().unwrap(); if finalizers.active.get(&key) == Some(&id) { finalizers.active.remove(&key); finalizers.entries.remove(&id); true } else { false } } fn call_external_string_finalizer(finalizer: ExternalStringFinalizer) { let env = finalizer .env .map_or(std::ptr::null_mut(), |env| env as *mut Env as napi_env); unsafe { (finalizer.callback)( env, finalizer.data as *mut c_void, finalizer.hint as *mut c_void, ); } } fn complete_external_string_finalizer(id: u64) { let finalizer = EXTERNAL_STRING_FINALIZERS .lock() .unwrap() .entries .remove(&id); if let Some(finalizer) = finalizer { call_external_string_finalizer(finalizer); } } fn queue_external_string_finalizer(key: ExternalStringKey) { let (id, sender, direct) = { let mut finalizers = EXTERNAL_STRING_FINALIZERS.lock().unwrap(); let Some(id) = finalizers.active.remove(&key) else { return; }; let Some(finalizer) = finalizers.entries.get_mut(&id) else { return; }; finalizer.ready = true; if finalizer.env.is_some() { (id, Some(finalizer.sender.clone()), None) } else { let finalizer = finalizers.entries.remove(&id).unwrap(); (id, None, Some(finalizer)) } }; if let Some(finalizer) = direct { call_external_string_finalizer(finalizer); } else if let Some(sender) = sender { sender.spawn(move |_| complete_external_string_finalizer(id)); } } /// Stop delivering non-null Env pointers for an environment being torn down. /// Resources already disposed by V8 are finalized immediately with the Env /// they were registered with; still-live resources receive a null Env when V8 /// disposes them later. /// /// Non-ready entries stay in the registry rather than being swept: their /// buffers still back a live V8 string, so calling their finalizers here would /// free memory V8 is still reading. They are reclaimed when the isolate is /// disposed, which fires the destructors that drain them through /// `queue_external_string_finalizer`. If the isolate is never disposed the /// entries last until the process exits. pub(crate) fn detach_external_string_env(env: *mut Env) { let ready = { let mut finalizers = EXTERNAL_STRING_FINALIZERS.lock().unwrap(); let env = env as usize; let ready: Vec<u64> = finalizers .entries .iter_mut() .filter_map(|(id, finalizer)| { if finalizer.env != Some(env) { return None; } if finalizer.ready { // V8 disposed this resource while the Env was still alive and its // deferred completion has not drained yet. Keep the Env so this // delivers what that task would have; it is removed below, so the // task no-ops. Some(*id) } else { finalizer.env = None; None } }) .collect(); ready .into_iter() .filter_map(|id| finalizers.entries.remove(&id)) .collect::<Vec<_>>() }; for finalizer in ready { call_external_string_finalizer(finalizer); } } /// V8 destructor for external one-byte strings. V8 may call this away from /// the runtime thread, so it only updates synchronized metadata and queues the /// addon callback onto the runtime task queue. unsafe extern "C" fn external_onebyte_destructor( data: *mut std::ffi::c_char, len: usize, ) { queue_external_string_finalizer(ExternalStringKey { data: data as usize, len, encoding: ExternalStringEncoding::OneByte, }); } /// V8 destructor for external two-byte strings. This follows the same /// synchronized handoff as the one-byte destructor. unsafe extern "C" fn external_twobyte_destructor(data: *mut u16, len: usize) { queue_external_string_finalizer(ExternalStringKey { data: data as usize, len, encoding: ExternalStringEncoding::TwoByte, }); } /// The encoding-specific pieces of `node_api_create_external_string_*`. The /// finalizer handoff around them is shared, so the two encodings cannot drift /// apart. trait ExternalStringChar: Sized { const ENCODING: ExternalStringEncoding; /// Resolve `NAPI_AUTO_LENGTH` by finding the null terminator. /// /// # Safety /// /// `data` must point to a null-terminated sequence of `Self`. unsafe fn auto_len(data: *const Self) -> usize; /// Create a V8 string backed by `data` itself. V8 calls the encoding's /// destructor once it collects the string. /// /// # Safety /// /// `data` must be valid for `len` code units and stay valid until the /// destructor runs. unsafe fn new_external<'s>( scope: &v8::PinScope<'s, '_, ()>, data: *mut Self, len: usize, ) -> Option<v8::Local<'s, v8::String>>; /// Copy `data` into a V8-owned string. /// /// # Safety /// /// `data` must be valid for `length` code units and `result` must be /// writable. unsafe fn copy( env: *mut Env, data: *const Self, length: usize, result: *mut napi_value, ) -> napi_status; } impl ExternalStringChar for c_char { const ENCODING: ExternalStringEncoding = ExternalStringEncoding::OneByte; unsafe fn auto_len(data: *const Self) -> usize { unsafe { std::ffi::CStr::from_ptr(data).to_bytes().len() } } unsafe fn new_external<'s>( scope: &v8::PinScope<'s, '_, ()>, data: *mut Self, len: usize, ) -> Option<v8::Local<'s, v8::String>> { unsafe { v8::String::new_external_onebyte_raw( scope, data, len, external_onebyte_destructor, ) } } unsafe fn copy( env: *mut Env, data: *const Self, length: usize, result: *mut napi_value, ) -> napi_status { unsafe { napi_create_string_latin1(env, data, length, result) } } } impl ExternalStringChar for u16 { const ENCODING: ExternalStringEncoding = ExternalStringEncoding::TwoByte; unsafe fn auto_len(data: *const Self) -> usize { let mut p = data; unsafe { while *p != 0 { p = p.add(1); } p.offset_from(data) as usize } } unsafe fn new_external<'s>( scope: &v8::PinScope<'s, '_, ()>, data: *mut Self, len: usize, ) -> Option<v8::Local<'s, v8::String>> { unsafe { v8::String::new_external_twobyte_raw( scope, data, len, external_twobyte_destructor, ) } } unsafe fn copy( env: *mut Env, data: *const Self, length: usize, result: *mut napi_value, ) -> napi_status { unsafe { napi_create_string_utf16(env, data, length, result) } } } /// Shared implementation of `node_api_create_external_string_latin1` and /// `node_api_create_external_string_utf16`. /// /// # Safety /// /// `string` must be valid for `length` code units, and `result` and `copied` /// must be writable or null. unsafe fn create_external_string<T: ExternalStringChar>( env_ptr: *mut Env, string: *const T, length: usize, nogc_finalize_callback: Option<napi_finalize>, finalize_hint: *mut c_void, result: *mut napi_value, copied: *mut bool, ) -> napi_status { let env = check_env!(env_ptr); check_arg!(env, string); check_arg!(env, result); let len = if length == NAPI_AUTO_LENGTH { unsafe { T::auto_len(string) } } else { length }; let mut finalize_on_copy = true; if let Some(finalize) = nogc_finalize_callback && len > 0 { let key = ExternalStringKey { data: string as usize, len, encoding: T::ENCODING, }; let finalizer_id = register_external_string_finalizer( key, env_ptr, finalize, string as *mut c_void, finalize_hint, ); // Try zero-copy: create a V8 external string backed by the // caller's buffer. V8 will call our destructor when the string // is GC'd, which in turn calls the NAPI finalize callback. if let Some(finalizer_id) = finalizer_id { let v8_str = { v8::callback_scope!(unsafe scope, env.context()); unsafe { T::new_external(scope, string as *mut T, len) } }; if let Some(v8_str) = v8_str { unsafe { *result = v8::Local::<v8::Value>::from(v8_str).into(); if !copied.is_null() { *copied = false; } } return napi_clear_last_error(check_env!(env_ptr)); } finalize_on_copy = cancel_external_string_finalizer(key, finalizer_id); } // V8 rejected the external string or an indistinguishable resource is // already live. Fall through to the copy path. } // Fallback: copy the string and call finalize immediately let status = unsafe { T::copy(env_ptr, string, length, result) }; if status == napi_ok { unsafe { if !copied.is_null() { *copied = true; } } if finalize_on_copy && 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_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 { unsafe { create_external_string( env_ptr, string, length, nogc_finalize_callback, finalize_hint, result, copied, ) } } #[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 { unsafe { create_external_string( env_ptr, string, length, nogc_finalize_callback, finalize_hint, result, copied, ) } } #[napi_sym] fn node_api_create_property_key_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::callback_scope!(unsafe scope, env.context()); v8::String::new_from_one_byte( 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 node_api_create_property_key_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::callback_scope!(unsafe scope, env.context()); v8::String::new_from_utf8(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 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 { &[] }; v8::callback_scope!(unsafe scope, env.context()); let Some(string) = v8::String::new_from_two_byte( 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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Number::new(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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Integer::new(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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Integer::new_from_unsigned(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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Number::new(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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::BigInt::new_from_i64(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); v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::BigInt::new_from_u64(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; } v8::callback_scope!(unsafe scope, env.context()); match v8::BigInt::new_from_words(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 d = { v8::callback_scope!(unsafe scope, env.context()); d.to_string(scope) }; let Some(d) = d else { return napi_set_last_error(env, napi_string_expected); }; Some(d) } else { None }; v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Symbol::new(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), }; v8::callback_scope!(unsafe scope, env.context()); unsafe { *result = v8::Symbol::for_key(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); }; v8::callback_scope!(unsafe scope, env.context()); let error = v8::Exception::$error(scope, message); if let Some(code) = *code { let error_obj: v8::Local<v8::Object> = error.try_into().unwrap(); let code_key = v8::String::new(scope, "code").unwrap(); if !error_obj .set(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_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_function() { Some(napi_function) } else if value.is_external() { Some(napi_external) } else if value.is_object() { Some(napi_object) } else if value.is_boolean() { Some(napi_boolean) } else if value.is_undefined() { Some(napi_undefined) } else if value.is_symbol() { Some(napi_symbol) } else if value.is_null() { Some(napi_null) } 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(env.isolate()).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(env.isolate()).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: &CallbackInfoScope = unsafe { &*(cbinfo as *const CallbackInfoScope) }; let function_info: &CallbackInfo = unsafe { &*cbinfo.function_info }; 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 = function_info.data; } } 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: &CallbackInfoScope = unsafe { &*(cbinfo as *const CallbackInfoScope) }; 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; }; v8::callback_scope!(unsafe scope, env.context()); let Some(v) = func.call(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); v8::callback_scope!(unsafe scope, env.context()); let global = v8::Local::new(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; } // Store in last_exception only. Do NOT call throw_exception() here. // The NAPI callback wrapper (call_fn) checks last_exception after // the callback returns and throws via V8 then. Throwing here would // make it impossible to clear with napi_get_and_clear_last_exception // since V8 would still have a pending exception. let error = { let error = error.unwrap(); v8::callback_scope!(unsafe scope, env.context()); v8::Global::new(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 = { let mut scope_storage = unsafe { v8::CallbackScope::new(env.context()) }; let mut scope_pin = unsafe { std::pin::Pin::new_unchecked(&mut scope_storage) }; let scope = &mut scope_pin.as_mut().init(); let error = v8::Exception::$error(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) => { drop(scope_storage); return napi_set_last_error(env, status); } }; let code_key = v8::String::new(scope, "code").unwrap(); if !error_obj .set(scope, code_key.into(), code.into()) .unwrap_or(false) { drop(scope_storage); return napi_set_last_error(env, napi_generic_failure); } } v8::Global::new(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); v8::callback_scope!(unsafe scope, env.context()); let value = value.unwrap(); let value = if let Ok(value) = value.try_cast::<v8::Int32>() { value.value() } else if let Some(value) = value.int32_value(scope) { value } else { return napi_set_last_error(env_ptr, 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); v8::callback_scope!(unsafe scope, env.context()); let value = value.unwrap(); let num = if let Ok(value) = value.try_cast::<v8::Uint32>() { value.value() } else if let Some(value) = value.uint32_value(scope) { value } else { return napi_set_last_error(env_ptr, napi_number_expected); }; unsafe { *result = num; } 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); } #[allow(deprecated, reason = "needed for compatibility")] #[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); v8::callback_scope!(unsafe scope, env.context()); 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() { unsafe { *result = value.length(); } } else if bufsize != 0 { let length = value.length().min(bufsize - 1); let buffer = unsafe { std::slice::from_raw_parts_mut(buf as _, length) }; value.write_one_byte_v2(scope, 0, buffer, v8::WriteFlags::kNullTerminate); if !result.is_null() { unsafe { *result = length; } } } else if !result.is_null() { unsafe { *result = 0; } } napi_clear_last_error(env_ptr) } #[allow(deprecated, reason = "needed for compatibility")] #[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); v8::callback_scope!(unsafe scope, env.context()); 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(scope); } } else if bufsize != 0 { let buffer = unsafe { std::slice::from_raw_parts_mut(buf as _, bufsize - 1) }; let copied = value.write_utf8_v2( scope, buffer, v8::WriteFlags::kReplaceInvalidUtf8, None, ); 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) } #[allow(deprecated, reason = "needed for compatibility")] #[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); v8::callback_scope!(unsafe scope, env.context()); 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 length = value.length().min(bufsize - 1); let buffer = unsafe { std::slice::from_raw_parts_mut(buf as _, length) }; value.write_v2(scope, 0, buffer, v8::WriteFlags::kNullTerminate); if !result.is_null() { unsafe { *result = length; } } } 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); v8::callback_scope!(unsafe scope, env.context()); let coerced = value.unwrap().to_boolean(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); v8::callback_scope!(unsafe scope, env.context()); let Some(coerced) = value.unwrap().to_number(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); v8::callback_scope!(unsafe scope, env.context()); let val = value.unwrap(); // Check for null/undefined before calling to_object() to avoid // V8 generating a TypeError exception that would get stored in // env.last_exception and later re-thrown as a spurious unhandled // rejection (ECMAScript's ToObject throws for null/undefined). if val.is_null_or_undefined() { return napi_invalid_arg; } let Some(coerced) = val.to_object(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); v8::callback_scope!(unsafe scope, env.context()); let Some(coerced) = value.unwrap().to_string(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; }; v8::callback_scope!(unsafe scope, env.context()); let napi_wrap = v8::Local::new(scope, &env.shared().napi_wrap); if obj.has_private(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, ); if let Some(cb) = finalize_cb { env.add_ref_finalizer( env_ptr as napi_env, 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(scope, reference); assert!(obj.set_private(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; }; v8::callback_scope!(unsafe scope, env.context()); let napi_wrap = v8::Local::new(scope, &env.shared().napi_wrap); let Some(val) = obj.get_private(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 { env.remove_ref_finalizer(reference.finalize_data); assert!(obj.delete_private(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); v8::callback_scope!(unsafe scope, env.context()); let external = v8::External::new(scope, wrapper as _); if let Some(finalize_cb) = finalize_cb { env.add_ref_finalizer( env_ptr as napi_env, finalize_cb, data, finalize_hint, ); 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; } v8::callback_scope!(unsafe scope, env.context()); let Some(object) = val.to_object(scope) else { return napi_object_expected; }; let key = v8::Local::new(scope, &env.shared().type_tag); if object.has_private(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(scope, false, slice) else { return napi_generic_failure; }; if !object.set_private(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; } v8::callback_scope!(unsafe scope, env.context()); let Some(object) = val.to_object(scope) else { return napi_object_expected; }; let key = v8::Local::new(scope, &env.shared().type_tag); let Some(val) = object.get_private(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) }; v8::callback_scope!(unsafe scope, env.context()); let value = match &reference.state { ReferenceState::Strong(g) => Some(v8::Local::new(scope, g)), ReferenceState::Weak(w) => w.to_local(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); check_arg!(env, result); // Allocate a bool to track whether escape has been called. // The pointer is cast to napi_escapable_handle_scope (opaque). let escaped_flag = Box::into_raw(Box::new(false)); unsafe { *result = escaped_flag as napi_escapable_handle_scope; } 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); if !scope.is_null() { // Reclaim the bool we allocated in open. unsafe { let _ = Box::from_raw(scope as *mut bool); } } 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); check_arg!(env, scope); check_arg!(env, result); let escaped_flag = scope as *mut bool; unsafe { if *escaped_flag { return napi_set_last_error(env, napi_escape_called_twice); } *escaped_flag = true; *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 { &[] }; v8::callback_scope!(unsafe scope, env.context()); // Native constructors are allowed to call back into JavaScript during // construction. For example, zeromq's `Context` constructor calls // `Object.seal(this)` while its addon is being initialized. let value_opt = func.new_instance(scope, args); let Some(value) = value_opt 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 mut scope_storage = unsafe { v8::CallbackScope::new(env.context()) }; let mut scope_pin = unsafe { std::pin::Pin::new_unchecked(&mut scope_storage) }; let scope = &mut scope_pin.as_mut().init(); let Some(ctor) = constructor.and_then(|v| v.to_object(scope)) else { return napi_object_expected; }; if !ctor.is_function() { drop(scope_storage); 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(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 last_exception = env.last_exception.take(); v8::callback_scope!(unsafe scope, env.context()); let ex: v8::Local<v8::Value> = match last_exception { Some(last_exception) => v8::Local::new(scope, last_exception), _ => v8::undefined(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); v8::callback_scope!(unsafe scope, env.context()); let buffer = v8::ArrayBuffer::new(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: Option<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, ); v8::callback_scope!(unsafe scope, env.context()); let ab = v8::ArrayBuffer::with_backing_store(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) => {{ v8::callback_scope!(unsafe scope, env.context()); let soe = $size_of_element; if soe > 1 && byte_offset % soe != 0 { let message = v8::String::new( scope, format!( "start offset of {} should be multiple of {}", stringify!($TypedArray), soe ) .as_str(), ) .unwrap(); let exc = v8::Exception::range_error(scope, message); scope.throw_exception(exc); return napi_pending_exception; } if length * soe + byte_offset > ab.byte_length() { let message = v8::String::new(scope, "Invalid typed array length") .unwrap(); let exc = v8::Exception::range_error(scope, message); scope.throw_exception(exc); return napi_pending_exception; } let Some(ta) = v8::$TypedArray::new(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() { v8::callback_scope!(unsafe scope, env.context()); let buf = array.buffer(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(), ); } } v8::callback_scope!(unsafe scope, env.context()); let dataview = v8::DataView::new(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(); } } v8::callback_scope!(unsafe scope, env.context()); if !arraybuffer.is_null() { let Some(buffer) = array.buffer(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); v8::callback_scope!(unsafe scope, env.context()); let resolver = v8::PromiseResolver::new(scope).unwrap(); let global = v8::Global::new(scope, resolver); let global_ptr = global.into_raw().as_ptr() as napi_deferred; let p = resolver.get_promise(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 v8::callback_scope!(unsafe scope, env.context()); 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(scope, deferred_ptr) }; let resolver = v8::Local::new(scope, global); let success = resolver.resolve(scope, result.unwrap()).unwrap_or(false); // Restore policy. The runtime runs with the Explicit microtask policy (see // `deno_core` isolate setup); microtasks are drained deliberately on event // loop turns. This used to restore `Auto` (V8's old isolate default), which // permanently flipped the whole isolate to Auto and let V8 auto-drain // microtasks mid-callback - the trigger for #33924. scope.set_microtasks_policy(v8::MicrotasksPolicy::Explicit); 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) }; v8::callback_scope!(unsafe scope, env.context()); let resolver = v8::Local::new(scope, global); if !resolver.reject(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); v8::callback_scope!(unsafe scope, env.context()); let Some(date) = v8::Date::new(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; }; v8::callback_scope!(unsafe scope, env.context()); let Some(script) = v8::Script::compile(scope, script, None) else { return napi_generic_failure; }; let Some(rv) = script.run(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 }; if let Some(cb) = finalize_cb { env.add_ref_finalizer( env_ptr as napi_env, cb, finalize_data, finalize_hint, ); } 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 { let env = check_env!(env); // Schedule the finalizer to run on the next event loop tick, // matching Node.js behavior. This is the public API equivalent // of the internal deferred-finalizer mechanism. let sender = env.async_work_sender.clone(); let env_send = SendPtr(env as *mut Env); let data_send = SendPtr(finalize_data); let hint_send = SendPtr(finalize_hint); sender.spawn(move |_scope| unsafe { finalize_cb( env_send.take() as _, data_send.take() as *mut c_void, hint_send.take() as *mut c_void, ); }); 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); // Note: instance data finalizers are NOT registered in ref_tracker because // they already have their own teardown path in NapiState::Drop which calls // EnvShared instance_data finalize_cb directly. 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) }