diff options
Diffstat (limited to 'ext/napi/node_api.rs')
-rw-r--r-- | ext/napi/node_api.rs | 1009 |
1 files changed, 1009 insertions, 0 deletions
diff --git a/ext/napi/node_api.rs b/ext/napi/node_api.rs new file mode 100644 index 000000000..2ca5c8d0b --- /dev/null +++ b/ext/napi/node_api.rs @@ -0,0 +1,1009 @@ +// 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 crate::*; +use deno_core::parking_lot::Condvar; +use deno_core::parking_lot::Mutex; +use deno_core::V8CrossThreadTaskSpawner; +use napi_sym::napi_sym; +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<napi_cleanup_hook>, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + env.add_cleanup_hook(fun, arg); + + napi_ok +} + +#[napi_sym] +fn napi_remove_env_cleanup_hook( + env: *mut Env, + fun: Option<napi_cleanup_hook>, + arg: *mut c_void, +) -> napi_status { + let env = check_env!(env); + check_arg!(env, fun); + + let fun = fun.unwrap(); + + env.remove_cleanup_hook(fun, arg); + + napi_ok +} + +struct AsyncCleanupHandle { + env: *mut Env, + hook: napi_async_cleanup_hook, + data: *mut c_void, +} + +unsafe extern "C" fn async_cleanup_handler(arg: *mut c_void) { + unsafe { + let handle = Box::<AsyncCleanupHandle>::from_raw(arg as _); + (handle.hook)(arg, handle.data); + } +} + +#[napi_sym] +fn napi_add_async_cleanup_hook( + env: *mut Env, + hook: Option<napi_async_cleanup_hook>, + 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(AsyncCleanupHandle { + env, + hook, + data: arg, + })) as *mut c_void; + + env.add_cleanup_hook(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 { Box::<AsyncCleanupHandle>::from_raw(remove_handle as _) }; + + let env = unsafe { &mut *handle.env }; + + env.remove_cleanup_hook(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 = v8::Local::new(&mut env.scope(), &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] +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 { + log::error!("NODE API FATAL ERROR: {} {}", location, message); + } else { + log::error!("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<'s>, + 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::<v8::Function>::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<v8::Value>, 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 = + v8::Local::new(&mut env.scope(), &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); + } + } + + 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 = + v8::Local::new(&mut env.scope(), &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 = + v8::Local::new(&mut env.scope(), &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); + 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 = + v8::Local::new(&mut env.scope(), &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); + + // NB: Any TypedArray instance seems to be accepted by this function + // in Node.js. + let Some(ta) = + value.and_then(|v| v8::Local::<v8::TypedArray>::try_from(v).ok()) + else { + 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<v8::Object>, + _async_resource_name: String, + execute: napi_async_execute_callback, + complete: Option<napi_async_complete_callback>, + data: *mut c_void, +} + +impl AsyncWork { + const IDLE: u8 = 0; + const QUEUED: u8 = 1; + const RUNNING: u8 = 2; +} + +#[napi_sym] +pub(crate) fn napi_create_async_work( + env: *mut Env, + async_resource: napi_value, + async_resource_name: napi_value, + execute: Option<napi_async_execute_callback>, + complete: Option<napi_async_complete_callback>, + 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] +pub(crate) 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::<AsyncWork>::from_raw(work as _) }); + + napi_clear_last_error(env) +} + +#[napi_sym] +fn napi_get_uv_event_loop( + env_ptr: *mut Env, + uv_loop: *mut *mut (), +) -> napi_status { + let env = check_env!(env_ptr); + check_arg!(env, uv_loop); + unsafe { + *uv_loop = env_ptr.cast(); + } + 0 +} + +#[napi_sym] +pub(crate) 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::<v8::Function>::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<v8::Global<v8::Function>>, + max_queue_size: usize, + queue_size: Mutex<usize>, + queue_cond: Condvar, + thread_count: AtomicUsize, + thread_finalize_data: *mut c_void, + thread_finalize_cb: Option<napi_finalize>, + context: *mut c_void, + call_js_cb: napi_threadsafe_function_call_js, + _resource: v8::Global<v8::Object>, + _resource_name: String, + is_closing: AtomicBool, + is_closed: Arc<AtomicBool>, + 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, self.context); + } + } + } +} + +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() + { + tsfn.queue_cond.notify_all(); + 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 { + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + + if self.max_queue_size > 0 { + let mut queue_size = self.queue_size.lock(); + while *queue_size >= self.max_queue_size { + if mode == napi_tsfn_blocking { + self.queue_cond.wait(&mut queue_size); + + if self.is_closing.load(Ordering::SeqCst) { + return napi_closing; + } + } else { + return napi_queue_full; + } + } + *queue_size += 1; + } + + 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::<v8::Local<v8::Value>>.into(), + context.take() as _, + data as _, + ); + } + } else { + let tsfn = tsfn.take(); + + let tsfn = unsafe { &*tsfn }; + + if tsfn.max_queue_size > 0 { + let mut queue_size = tsfn.queue_size.lock(); + let size = *queue_size; + *queue_size -= 1; + if size == tsfn.max_queue_size { + tsfn.queue_cond.notify_one(); + } + } + + 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<napi_finalize>, + context: *mut c_void, + call_js_cb: Option<napi_threadsafe_function_call_js>, + 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::<v8::Function>::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 tsfn = Box::new(TsFn { + env, + func, + max_queue_size, + queue_size: Mutex::new(0), + queue_cond: Condvar::new(), + 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) +} |