// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. #![deny(unsafe_op_in_unsafe_fn)] use super::util::get_array_buffer_ptr; use super::util::make_external_backing_store; use super::util::napi_clear_last_error; use super::util::napi_set_last_error; use super::util::SendPtr; use crate::check_arg; use crate::check_env; use deno_core::V8CrossThreadTaskSpawner; use deno_runtime::deno_napi::*; use napi_sym::napi_sym; use std::ptr::NonNull; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU8; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; #[napi_sym] fn napi_module_register(module: *const NapiModule) -> napi_status { MODULE_TO_REGISTER.with(|cell| { let mut slot = cell.borrow_mut(); let prev = slot.replace(module); assert!(prev.is_none()); }); napi_ok } #[napi_sym] fn napi_add_env_cleanup_hook( env: *mut Env, fun: Option, arg: *mut c_void, ) -> napi_status { let env = check_env!(env); check_arg!(env, fun); let fun = fun.unwrap(); let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); if env_cleanup_hooks .iter() .any(|pair| pair.0 == fun && pair.1 == arg) { panic!("Cleanup hook with this data already registered"); } env_cleanup_hooks.push((fun, arg)); napi_ok } #[napi_sym] fn napi_remove_env_cleanup_hook( env: *mut Env, fun: Option, arg: *mut c_void, ) -> napi_status { let env = check_env!(env); check_arg!(env, fun); let fun = fun.unwrap(); let mut env_cleanup_hooks = env.cleanup_hooks.borrow_mut(); // Hooks are supposed to be removed in LIFO order let maybe_index = env_cleanup_hooks .iter() .rposition(|&pair| pair.0 == fun && pair.1 == arg); if let Some(index) = maybe_index { env_cleanup_hooks.remove(index); } else { panic!("Cleanup hook with this data not found"); } napi_ok } type AsyncCleanupHandle = (*mut Env, napi_async_cleanup_hook, *mut c_void); unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) { unsafe { let (env, hook, arg) = *Box::::from_raw(arg as _); hook(env as _, arg); } } #[napi_sym] fn napi_add_async_cleanup_hook( env: *mut Env, hook: Option, arg: *mut c_void, remove_handle: *mut napi_async_cleanup_hook_handle, ) -> napi_status { let env = check_env!(env); check_arg!(env, hook); let hook = hook.unwrap(); let handle = Box::into_raw(Box::::new((env, hook, arg))) as _; unsafe { napi_add_env_cleanup_hook(env, Some(async_cleanup_handler), handle); } if !remove_handle.is_null() { unsafe { *remove_handle = handle; } } napi_clear_last_error(env) } #[napi_sym] fn napi_remove_async_cleanup_hook( remove_handle: napi_async_cleanup_hook_handle, ) -> napi_status { if remove_handle.is_null() { return napi_invalid_arg; } let handle = unsafe { &*(remove_handle as *mut AsyncCleanupHandle) }; let env = unsafe { &mut *handle.0 }; unsafe { napi_remove_env_cleanup_hook( env, Some(async_cleanup_handler), remove_handle, ); } napi_ok } #[napi_sym] fn napi_fatal_exception(env: &mut Env, err: napi_value) -> napi_status { check_arg!(env, err); let report_error = unsafe { std::mem::transmute::, v8::Local>( env.report_error, ) }; let this = v8::undefined(&mut env.scope()); if report_error .call(&mut env.scope(), this.into(), &[err.unwrap()]) .is_none() { return napi_generic_failure; } napi_ok } #[napi_sym] #[allow(clippy::print_stderr)] fn napi_fatal_error( location: *const c_char, location_len: usize, message: *const c_char, message_len: usize, ) -> napi_status { let location = if location.is_null() { None } else { unsafe { Some(if location_len == NAPI_AUTO_LENGTH { std::ffi::CStr::from_ptr(location).to_str().unwrap() } else { let slice = std::slice::from_raw_parts( location as *const u8, location_len as usize, ); std::str::from_utf8(slice).unwrap() }) } }; let message = if message_len == NAPI_AUTO_LENGTH { unsafe { std::ffi::CStr::from_ptr(message).to_str().unwrap() } } else { let slice = unsafe { std::slice::from_raw_parts(message as *const u8, message_len as usize) }; std::str::from_utf8(slice).unwrap() }; if let Some(location) = location { eprintln!("NODE API FATAL ERROR: {} {}", location, message); } else { eprintln!("NODE API FATAL ERROR: {}", message); } std::process::abort(); } #[napi_sym] fn napi_open_callback_scope( env: *mut Env, _resource_object: napi_value, _context: napi_value, result: *mut napi_callback_scope, ) -> napi_status { let env = check_env!(env); check_arg!(env, result); // we open scope automatically when it's needed unsafe { *result = std::ptr::null_mut(); } napi_clear_last_error(env) } #[napi_sym] fn napi_close_callback_scope( env: *mut Env, scope: napi_callback_scope, ) -> napi_status { let env = check_env!(env); // we close scope automatically when it's needed assert!(scope.is_null()); napi_clear_last_error(env) } // NOTE: we don't support "async_hooks::AsyncContext" so these APIs are noops. #[napi_sym] fn napi_async_init( env: *mut Env, _async_resource: napi_value, _async_resource_name: napi_value, result: *mut napi_async_context, ) -> napi_status { let env = check_env!(env); unsafe { *result = ptr::null_mut(); } napi_clear_last_error(env) } #[napi_sym] fn napi_async_destroy( env: *mut Env, async_context: napi_async_context, ) -> napi_status { let env = check_env!(env); assert!(async_context.is_null()); napi_clear_last_error(env) } #[napi_sym] fn napi_make_callback<'s>( env: &'s mut Env, _async_context: napi_async_context, recv: napi_value, func: napi_value, argc: usize, argv: *const napi_value, result: *mut napi_value<'s>, ) -> napi_status { check_arg!(env, recv); if argc > 0 { check_arg!(env, argv); } let Some(recv) = recv.and_then(|v| v.to_object(&mut env.scope())) else { return napi_object_expected; }; let Some(func) = func.and_then(|v| v8::Local::::try_from(v).ok()) else { return napi_function_expected; }; let args = if argc > 0 { unsafe { std::slice::from_raw_parts(argv as *mut v8::Local, argc) } } else { &[] }; // TODO: async_context let Some(v) = func.call(&mut env.scope(), recv.into(), args) else { return napi_generic_failure; }; unsafe { *result = v.into(); } napi_ok } #[napi_sym] fn napi_create_buffer<'s>( env: &'s mut Env, length: usize, data: *mut *mut c_void, result: *mut napi_value<'s>, ) -> napi_status { check_arg!(env, result); let ab = v8::ArrayBuffer::new(&mut env.scope(), length); let buffer_constructor = unsafe { std::mem::transmute::, v8::Local>( env.buffer_constructor, ) }; let Some(buffer) = buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) else { return napi_generic_failure; }; if !data.is_null() { unsafe { *data = get_array_buffer_ptr(ab) as _; } } unsafe { *result = buffer.into(); } napi_ok } #[napi_sym] fn napi_create_external_buffer<'s>( env: &'s mut Env, length: usize, data: *mut c_void, finalize_cb: napi_finalize, finalize_hint: *mut c_void, result: *mut napi_value<'s>, ) -> napi_status { check_arg!(env, result); let store = make_external_backing_store( env, data, length, ptr::null_mut(), finalize_cb, finalize_hint, ); let ab = v8::ArrayBuffer::with_backing_store(&mut env.scope(), &store.make_shared()); let buffer_constructor = unsafe { std::mem::transmute::, v8::Local>( env.buffer_constructor, ) }; let Some(buffer) = buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) else { return napi_generic_failure; }; unsafe { *result = buffer.into(); } napi_ok } #[napi_sym] fn napi_create_buffer_copy<'s>( env: &'s mut Env, length: usize, data: *mut c_void, result_data: *mut *mut c_void, result: *mut napi_value<'s>, ) -> napi_status { check_arg!(env, result); let ab = v8::ArrayBuffer::new(&mut env.scope(), length); let buffer_constructor = unsafe { std::mem::transmute::, v8::Local>( env.buffer_constructor, ) }; let Some(buffer) = buffer_constructor.new_instance(&mut env.scope(), &[ab.into()]) else { return napi_generic_failure; }; let ptr = get_array_buffer_ptr(ab) as *mut c_void; unsafe { std::ptr::copy(data, ptr, length); } if !result_data.is_null() { unsafe { *result_data = ptr; } } unsafe { *result = buffer.into(); } napi_ok } #[napi_sym] fn napi_is_buffer( env: *mut Env, value: napi_value, result: *mut bool, ) -> napi_status { let env = check_env!(env); check_arg!(env, value); check_arg!(env, result); let buffer_constructor = unsafe { std::mem::transmute::, v8::Local>( env.buffer_constructor, ) }; let Some(is_buffer) = value .unwrap() .instance_of(&mut env.scope(), buffer_constructor.into()) else { return napi_set_last_error(env, napi_generic_failure); }; unsafe { *result = is_buffer; } napi_clear_last_error(env) } #[napi_sym] fn napi_get_buffer_info( env: *mut Env, value: napi_value, data: *mut *mut c_void, length: *mut usize, ) -> napi_status { let env = check_env!(env); check_arg!(env, value); let Some(ta) = value.and_then(|v| v8::Local::::try_from(v).ok()) else { return napi_set_last_error(env, napi_invalid_arg); }; let buffer_constructor = unsafe { std::mem::transmute::, v8::Local>( env.buffer_constructor, ) }; if !ta .instance_of(&mut env.scope(), buffer_constructor.into()) .unwrap_or(false) { return napi_set_last_error(env, napi_invalid_arg); } if !data.is_null() { unsafe { *data = ta.data(); } } if !length.is_null() { unsafe { *length = ta.byte_length(); } } napi_clear_last_error(env) } #[napi_sym] fn napi_get_node_version( env: *mut Env, result: *mut *const napi_node_version, ) -> napi_status { let env = check_env!(env); check_arg!(env, result); const NODE_VERSION: napi_node_version = napi_node_version { major: 20, minor: 11, patch: 1, release: c"Deno".as_ptr(), }; unsafe { *result = &NODE_VERSION as *const napi_node_version; } napi_clear_last_error(env) } struct AsyncWork { state: AtomicU8, env: *mut Env, _async_resource: v8::Global, _async_resource_name: String, execute: napi_async_execute_callback, complete: Option, data: *mut c_void, } impl AsyncWork { const IDLE: u8 = 0; const QUEUED: u8 = 1; const RUNNING: u8 = 2; } #[napi_sym] fn napi_create_async_work( env: *mut Env, async_resource: napi_value, async_resource_name: napi_value, execute: Option, complete: Option, data: *mut c_void, result: *mut napi_async_work, ) -> napi_status { let env_ptr = env; let env = check_env!(env); check_arg!(env, execute); check_arg!(env, result); let resource = if let Some(v) = *async_resource { let Some(resource) = v.to_object(&mut env.scope()) else { return napi_set_last_error(env, napi_object_expected); }; resource } else { v8::Object::new(&mut env.scope()) }; let Some(resource_name) = async_resource_name.and_then(|v| v.to_string(&mut env.scope())) else { return napi_set_last_error(env, napi_string_expected); }; let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); let work = Box::new(AsyncWork { state: AtomicU8::new(AsyncWork::IDLE), env: env_ptr, _async_resource: v8::Global::new(&mut env.scope(), resource), _async_resource_name: resource_name, execute: execute.unwrap(), complete, data, }); unsafe { *result = Box::into_raw(work) as _; } napi_clear_last_error(env) } #[napi_sym] fn napi_delete_async_work(env: *mut Env, work: napi_async_work) -> napi_status { let env = check_env!(env); check_arg!(env, work); drop(unsafe { Box::::from_raw(work as _) }); napi_clear_last_error(env) } #[napi_sym] fn napi_get_uv_event_loop(env: *mut Env, uv_loop: *mut *mut ()) -> napi_status { let env = check_env!(env); check_arg!(env, uv_loop); // There is no uv_loop in Deno napi_set_last_error(env, napi_generic_failure) } #[napi_sym] fn napi_queue_async_work(env: *mut Env, work: napi_async_work) -> napi_status { let env = check_env!(env); check_arg!(env, work); let work = unsafe { &*(work as *mut AsyncWork) }; let result = work .state .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { // allow queue if idle or if running, but not if already queued. if state == AsyncWork::IDLE || state == AsyncWork::RUNNING { Some(AsyncWork::QUEUED) } else { None } }); if result.is_err() { return napi_clear_last_error(env); } let work = SendPtr(work); env.add_async_work(move || { let work = work.take(); let work = unsafe { &*work }; let state = work.state.compare_exchange( AsyncWork::QUEUED, AsyncWork::RUNNING, Ordering::SeqCst, Ordering::SeqCst, ); if state.is_ok() { unsafe { (work.execute)(work.env as _, work.data); } // reset back to idle if its still marked as running let _ = work.state.compare_exchange( AsyncWork::RUNNING, AsyncWork::IDLE, Ordering::SeqCst, Ordering::Relaxed, ); } if let Some(complete) = work.complete { let status = if state.is_ok() { napi_ok } else if state == Err(AsyncWork::IDLE) { napi_cancelled } else { napi_generic_failure }; unsafe { complete(work.env as _, status, work.data); } } // `complete` probably deletes this `work`, so don't use it here. }); napi_clear_last_error(env) } #[napi_sym] fn napi_cancel_async_work(env: *mut Env, work: napi_async_work) -> napi_status { let env = check_env!(env); check_arg!(env, work); let work = unsafe { &*(work as *mut AsyncWork) }; let _ = work.state.compare_exchange( AsyncWork::QUEUED, AsyncWork::IDLE, Ordering::SeqCst, Ordering::Relaxed, ); napi_clear_last_error(env) } extern "C" fn default_call_js_cb( env: napi_env, js_callback: napi_value, _context: *mut c_void, _data: *mut c_void, ) { if let Some(js_callback) = *js_callback { if let Ok(js_callback) = v8::Local::::try_from(js_callback) { let env = unsafe { &mut *(env as *mut Env) }; let scope = &mut env.scope(); let recv = v8::undefined(scope); js_callback.call(scope, recv.into(), &[]); } } } struct TsFn { env: *mut Env, func: Option>, _max_queue_size: usize, thread_count: AtomicUsize, thread_finalize_data: *mut c_void, thread_finalize_cb: Option, context: *mut c_void, call_js_cb: napi_threadsafe_function_call_js, _resource: v8::Global, _resource_name: String, is_closing: AtomicBool, is_closed: Arc, sender: V8CrossThreadTaskSpawner, is_ref: AtomicBool, } impl Drop for TsFn { fn drop(&mut self) { assert!(self .is_closed .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok()); self.unref(); if let Some(finalizer) = self.thread_finalize_cb { unsafe { (finalizer)(self.env as _, self.thread_finalize_data, ptr::null_mut()); } } } } impl TsFn { pub fn acquire(&self) -> napi_status { if self.is_closing.load(Ordering::SeqCst) { return napi_closing; } self.thread_count.fetch_add(1, Ordering::Relaxed); napi_ok } pub fn release( tsfn: *mut TsFn, mode: napi_threadsafe_function_release_mode, ) -> napi_status { let tsfn = unsafe { &mut *tsfn }; let result = tsfn.thread_count.fetch_update( Ordering::Relaxed, Ordering::Relaxed, |x| { if x == 0 { None } else { Some(x - 1) } }, ); if result.is_err() { return napi_invalid_arg; } if (result == Ok(1) || mode == napi_tsfn_abort) && tsfn .is_closing .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok() { let tsfnptr = SendPtr(tsfn); // drop must be queued in order to preserve ordering consistent // with Node.js and so that the finalizer runs on the main thread. tsfn.sender.spawn(move |_| { let tsfn = unsafe { Box::from_raw(tsfnptr.take() as *mut TsFn) }; drop(tsfn); }); } napi_ok } pub fn ref_(&self) -> napi_status { if self .is_ref .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok() { let env = unsafe { &mut *self.env }; env.threadsafe_function_ref(); } napi_ok } pub fn unref(&self) -> napi_status { if self .is_ref .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .is_ok() { let env = unsafe { &mut *self.env }; env.threadsafe_function_unref(); } napi_ok } pub fn call( &self, data: *mut c_void, _mode: napi_threadsafe_function_call_mode, ) -> napi_status { // TODO: // if self.max_queue_size > 0 && queued >= self.max_queue_size { // if mode == napi_tsfn_blocking { // wait somehow // } else { // return napi_queue_full; // } // } if self.is_closing.load(Ordering::SeqCst) { return napi_closing; } let is_closed = self.is_closed.clone(); let tsfn = SendPtr(self); let data = SendPtr(data); let context = SendPtr(self.context); let call_js_cb = self.call_js_cb; self.sender.spawn(move |scope: &mut v8::HandleScope| { let data = data.take(); // if is_closed then tsfn is freed, don't read from it. if is_closed.load(Ordering::Relaxed) { unsafe { call_js_cb( std::ptr::null_mut(), None::>.into(), context.take() as _, data as _, ); } } else { let tsfn = tsfn.take(); let tsfn = unsafe { &*tsfn }; let func = tsfn.func.as_ref().map(|f| v8::Local::new(scope, f)); unsafe { (tsfn.call_js_cb)( tsfn.env as _, func.into(), tsfn.context, data as _, ); } } }); napi_ok } } #[napi_sym] #[allow(clippy::too_many_arguments)] fn napi_create_threadsafe_function( env: *mut Env, func: napi_value, async_resource: napi_value, async_resource_name: napi_value, max_queue_size: usize, initial_thread_count: usize, thread_finalize_data: *mut c_void, thread_finalize_cb: Option, context: *mut c_void, call_js_cb: Option, result: *mut napi_threadsafe_function, ) -> napi_status { let env = check_env!(env); check_arg!(env, async_resource_name); if initial_thread_count == 0 { return napi_set_last_error(env, napi_invalid_arg); } check_arg!(env, result); let func = if let Some(value) = *func { let Ok(func) = v8::Local::::try_from(value) else { return napi_set_last_error(env, napi_function_expected); }; Some(v8::Global::new(&mut env.scope(), func)) } else { check_arg!(env, call_js_cb); None }; let resource = if let Some(v) = *async_resource { let Some(resource) = v.to_object(&mut env.scope()) else { return napi_set_last_error(env, napi_object_expected); }; resource } else { v8::Object::new(&mut env.scope()) }; let resource = v8::Global::new(&mut env.scope(), resource); let Some(resource_name) = async_resource_name.and_then(|v| v.to_string(&mut env.scope())) else { return napi_set_last_error(env, napi_string_expected); }; let resource_name = resource_name.to_rust_string_lossy(&mut env.scope()); let mut tsfn = Box::new(TsFn { env, func, _max_queue_size: max_queue_size, thread_count: AtomicUsize::new(initial_thread_count), thread_finalize_data, thread_finalize_cb, context, call_js_cb: call_js_cb.unwrap_or(default_call_js_cb), _resource: resource, _resource_name: resource_name, is_closing: AtomicBool::new(false), is_closed: Arc::new(AtomicBool::new(false)), is_ref: AtomicBool::new(false), sender: env.async_work_sender.clone(), }); tsfn.ref_(); unsafe { *result = Box::into_raw(tsfn) as _; } napi_clear_last_error(env) } /// Maybe called from any thread. #[napi_sym] fn napi_get_threadsafe_function_context( func: napi_threadsafe_function, result: *mut *const c_void, ) -> napi_status { assert!(!func.is_null()); let tsfn = unsafe { &*(func as *const TsFn) }; unsafe { *result = tsfn.context; } napi_ok } #[napi_sym] fn napi_call_threadsafe_function( func: napi_threadsafe_function, data: *mut c_void, is_blocking: napi_threadsafe_function_call_mode, ) -> napi_status { assert!(!func.is_null()); let tsfn = unsafe { &*(func as *mut TsFn) }; tsfn.call(data, is_blocking) } #[napi_sym] fn napi_acquire_threadsafe_function( tsfn: napi_threadsafe_function, ) -> napi_status { assert!(!tsfn.is_null()); let tsfn = unsafe { &*(tsfn as *mut TsFn) }; tsfn.acquire() } #[napi_sym] fn napi_release_threadsafe_function( tsfn: napi_threadsafe_function, mode: napi_threadsafe_function_release_mode, ) -> napi_status { assert!(!tsfn.is_null()); TsFn::release(tsfn as _, mode) } #[napi_sym] fn napi_unref_threadsafe_function( _env: &mut Env, func: napi_threadsafe_function, ) -> napi_status { assert!(!func.is_null()); let tsfn = unsafe { &*(func as *mut TsFn) }; tsfn.unref() } #[napi_sym] fn napi_ref_threadsafe_function( _env: &mut Env, func: napi_threadsafe_function, ) -> napi_status { assert!(!func.is_null()); let tsfn = unsafe { &*(func as *mut TsFn) }; tsfn.ref_() } #[napi_sym] fn node_api_get_module_file_name( env: *mut Env, result: *mut *const c_char, ) -> napi_status { let env = check_env!(env); check_arg!(env, result); unsafe { *result = env.shared().filename.as_ptr() as _; } napi_clear_last_error(env) }