diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 352aa7ab..c50c1b25 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -49,7 +49,8 @@ impl TryToTokens for NapiFn { quote! { // constructor function is called from class `factory` // so we should skip the original `constructor` logic - if napi::bindgen_prelude::___CALL_FROM_FACTORY.with(|inner| inner.load(std::sync::atomic::Ordering::Relaxed)) { + let inner = napi::__private::___CALL_FROM_FACTORY.get_or_default(); + if inner.load(std::sync::atomic::Ordering::Relaxed) { return std::ptr::null_mut(); } napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| { diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index 8a8d6b85..54659e59 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -276,12 +276,14 @@ impl NapiStruct { )?; let mut result = std::ptr::null_mut(); - napi::bindgen_prelude::___CALL_FROM_FACTORY.with(|inner| inner.store(true, std::sync::atomic::Ordering::Relaxed)); + let inner = napi::__private::___CALL_FROM_FACTORY.get_or_default(); + inner.store(true, std::sync::atomic::Ordering::Relaxed); napi::check_status!( napi::sys::napi_new_instance(env, ctor, 0, std::ptr::null_mut(), &mut result), "Failed to construct class `{}`", #js_name_raw )?; + inner.store(false, std::sync::atomic::Ordering::Relaxed); let mut object_ref = std::ptr::null_mut(); let initial_finalize: Box = Box::new(|| {}); let finalize_callbacks_ptr = std::rc::Rc::into_raw(std::rc::Rc::new(std::cell::Cell::new(Box::into_raw(initial_finalize)))); @@ -298,7 +300,6 @@ impl NapiStruct { #js_name_raw )?; napi::bindgen_prelude::Reference::<#name>::add_ref(wrapped_value, (wrapped_value, object_ref, finalize_callbacks_ptr)); - napi::bindgen_prelude::___CALL_FROM_FACTORY.with(|inner| inner.store(false, std::sync::atomic::Ordering::Relaxed)); Ok(result) } } @@ -316,6 +317,7 @@ impl NapiStruct { fn gen_to_napi_value_ctor_impl(&self) -> TokenStream { let name = &self.name; + let js_name_without_null = &self.js_name; let js_name_str = format!("{}\0", &self.js_name); let mut field_conversions = vec![]; @@ -362,7 +364,7 @@ impl NapiStruct { napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_get_reference_value(env, ctor_ref, &mut ctor), "Failed to get constructor reference of class `{}`", - #js_name_str + #js_name_without_null )?; let mut instance_value = std::ptr::null_mut(); @@ -372,7 +374,7 @@ impl NapiStruct { napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut instance_value), "Failed to construct class `{}`", - #js_name_str + #js_name_without_null )?; Ok(instance_value) diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs index b10fe3f0..dac8ba27 100644 --- a/crates/build/src/lib.rs +++ b/crates/build/src/lib.rs @@ -2,8 +2,7 @@ mod macos; pub fn setup() { println!("cargo:rerun-if-env-changed=DEBUG_GENERATED_CODE"); - match std::env::var("CARGO_CFG_TARGET_OS").as_deref() { - Ok("macos") => macos::setup(), - _ => {} + if let Ok("macos") = std::env::var("CARGO_CFG_TARGET_OS").as_deref() { + macos::setup(); } } diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 9e641a72..42f30ae3 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -49,6 +49,7 @@ tokio_time = ["tokio/time"] [dependencies] ctor = "0.1" lazy_static = "1" +thread_local = "1" [dependencies.napi-sys] version = "2.1.1" diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index 7f3d33a2..10ec5770 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -4,12 +4,15 @@ use std::ptr; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; +use lazy_static::lazy_static; +use thread_local::ThreadLocal; + use crate::{bindgen_prelude::*, check_status, sys, Result}; -thread_local! { +lazy_static! { #[doc(hidden)] /// Determined is `constructor` called from Class `factory` - pub static ___CALL_FROM_FACTORY: AtomicBool = AtomicBool::new(false); + pub static ref ___CALL_FROM_FACTORY: ThreadLocal = ThreadLocal::new(); } pub struct CallbackInfo { @@ -113,9 +116,10 @@ impl CallbackInfo { let this = self.this(); let mut instance = ptr::null_mut(); unsafe { - ___CALL_FROM_FACTORY.with(|inner| inner.store(true, Ordering::Relaxed)); + let inner = ___CALL_FROM_FACTORY.get_or_default(); + inner.store(true, Ordering::Relaxed); let status = sys::napi_new_instance(self.env, this, 0, ptr::null_mut(), &mut instance); - ___CALL_FROM_FACTORY.with(|inner| inner.store(false, Ordering::Relaxed)); + inner.store(false, Ordering::Relaxed); // Error thrown in `constructor` if status == sys::Status::napi_pending_exception { let mut exception = ptr::null_mut(); diff --git a/crates/napi/src/bindgen_runtime/env.rs b/crates/napi/src/bindgen_runtime/env.rs index f1e05145..01bfa578 100644 --- a/crates/napi/src/bindgen_runtime/env.rs +++ b/crates/napi/src/bindgen_runtime/env.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::ptr; use crate::{check_status, sys, JsGlobal, JsNull, JsUndefined, NapiValue, Result}; @@ -7,11 +6,6 @@ use super::Array; pub use crate::Env; -thread_local! { - static JS_UNDEFINED: RefCell> = RefCell::default(); - static JS_NULL: RefCell> = RefCell::default(); -} - impl Env { pub fn create_array(&self, len: u32) -> Result { Array::new(self.0, len) @@ -19,26 +13,16 @@ impl Env { /// Get [JsUndefined](./struct.JsUndefined.html) value pub fn get_undefined(&self) -> Result { - if let Some(js_undefined) = JS_UNDEFINED.with(|x| *x.borrow()) { - return Ok(js_undefined); - } let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?; let js_undefined = unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) }; - JS_UNDEFINED.with(|x| x.borrow_mut().replace(js_undefined)); Ok(js_undefined) } pub fn get_null(&self) -> Result { - if let Some(js_null) = JS_NULL.with(|cell| *cell.borrow()) { - return Ok(js_null); - } let mut raw_value = ptr::null_mut(); check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?; let js_null = unsafe { JsNull::from_raw_unchecked(self.0, raw_value) }; - JS_NULL.with(|js_null_cell| { - js_null_cell.borrow_mut().replace(js_null); - }); Ok(js_null) } diff --git a/crates/napi/src/bindgen_runtime/js_values/value_ref.rs b/crates/napi/src/bindgen_runtime/js_values/value_ref.rs index 981996ff..aacb809a 100644 --- a/crates/napi/src/bindgen_runtime/js_values/value_ref.rs +++ b/crates/napi/src/bindgen_runtime/js_values/value_ref.rs @@ -1,10 +1,14 @@ -use std::cell::{Cell, RefCell}; -use std::collections::HashMap; +use std::cell::Cell; use std::ffi::c_void; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -use crate::{bindgen_runtime::ToNapiValue, check_status, Env, Error, Result, Status}; +use lazy_static::lazy_static; + +use crate::{ + bindgen_runtime::{PersistedSingleThreadHashMap, ToNapiValue}, + check_status, Env, Error, Result, Status, +}; type RefInformation = ( *mut c_void, @@ -12,8 +16,9 @@ type RefInformation = ( *const Cell<*mut dyn FnOnce()>, ); -thread_local! { - pub(crate) static REFERENCE_MAP: RefCell> = Default::default(); +lazy_static! { + pub(crate) static ref REFERENCE_MAP: PersistedSingleThreadHashMap<*mut c_void, RefInformation> = + Default::default(); } /// ### Experimental feature @@ -57,15 +62,15 @@ impl Drop for Reference { impl Reference { #[doc(hidden)] pub fn add_ref(t: *mut c_void, value: RefInformation) { - REFERENCE_MAP.with(|map| { - map.borrow_mut().insert(t, value); + REFERENCE_MAP.borrow_mut(|map| { + map.insert(t, value); }); } #[doc(hidden)] pub unsafe fn from_value_ptr(t: *mut c_void, env: crate::sys::napi_env) -> Result { if let Some((wrapped_value, napi_ref, finalize_callbacks_ptr)) = - REFERENCE_MAP.with(|map| map.borrow().get(&t).cloned()) + REFERENCE_MAP.borrow_mut(|map| map.get(&t).cloned()) { check_status!( unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut 0) }, diff --git a/crates/napi/src/bindgen_runtime/mod.rs b/crates/napi/src/bindgen_runtime/mod.rs index ba4c2c4b..807375a4 100644 --- a/crates/napi/src/bindgen_runtime/mod.rs +++ b/crates/napi/src/bindgen_runtime/mod.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn raw_finalize_unchecked( ) { unsafe { Box::from_raw(finalize_data as *mut T) }; if let Some((_, ref_val, finalize_callbacks_ptr)) = - REFERENCE_MAP.with(|reference_map| reference_map.borrow_mut().remove(&finalize_data)) + REFERENCE_MAP.borrow_mut(|reference_map| reference_map.remove(&finalize_data)) { let finalize_callbacks_rc = unsafe { Rc::from_raw(finalize_callbacks_ptr) }; diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index 468a461a..499a79aa 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -1,11 +1,10 @@ -use std::cell::RefCell; use std::collections::{HashMap, HashSet}; #[cfg(all(feature = "tokio_rt", feature = "napi4"))] use std::ffi::c_void; use std::ffi::CStr; use std::mem; use std::ptr; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{AtomicBool, AtomicPtr}; use std::sync::{atomic::Ordering, Mutex}; use lazy_static::lazy_static; @@ -56,11 +55,11 @@ impl PersistedSingleThreadVec { unsafe impl Send for PersistedSingleThreadVec {} unsafe impl Sync for PersistedSingleThreadVec {} -struct PersistedSingleThreadHashMap(Mutex>); +pub(crate) struct PersistedSingleThreadHashMap(Mutex>); impl PersistedSingleThreadHashMap { #[allow(clippy::mut_from_ref)] - fn borrow_mut(&self, f: F) -> R + pub(crate) fn borrow_mut(&self, f: F) -> R where F: FnOnce(&mut HashMap) -> R, { @@ -89,11 +88,17 @@ type ModuleClassProperty = PersistedSingleThreadHashMap< unsafe impl Send for PersistedSingleThreadHashMap {} unsafe impl Sync for PersistedSingleThreadHashMap {} +type FnRegisterMap = + PersistedSingleThreadHashMap; + lazy_static! { static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default(); static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default(); static ref MODULE_REGISTER_LOCK: Mutex<()> = Mutex::new(()); static ref REGISTERED: AtomicBool = AtomicBool::new(false); + static ref REGISTERED_CLASSES: thread_local::ThreadLocal> = + thread_local::ThreadLocal::new(); + static ref FN_REGISTER_MAP: FnRegisterMap = Default::default(); } #[inline] @@ -103,27 +108,22 @@ fn wait_first_thread_registered() { } } +type RegisteredClasses = + HashMap; + #[cfg(feature = "compat-mode")] // compatibility for #[module_exports] lazy_static! { static ref MODULE_EXPORTS: PersistedSingleThreadVec = Default::default(); } -thread_local! { - static REGISTERED_CLASSES: RefCell> = Default::default(); - static FN_REGISTER_MAP: RefCell> = Default::default(); -} - #[doc(hidden)] pub fn get_class_constructor(js_name: &'static str) -> Option { wait_first_thread_registered(); - REGISTERED_CLASSES.with(|registered_classes| { - let classes = registered_classes.borrow(); - classes.get(js_name).copied() - }) + let registered_classes = REGISTERED_CLASSES.get().unwrap(); + let registered_classes = + Box::leak(unsafe { Box::from_raw(registered_classes.load(Ordering::Relaxed)) }); + registered_classes.get(js_name).copied() } #[doc(hidden)] @@ -148,8 +148,8 @@ pub fn register_js_function( cb: ExportRegisterCallback, c_fn: sys::napi_callback, ) { - FN_REGISTER_MAP.with(|inner| { - inner.borrow_mut().insert(cb, (c_fn, name.to_owned())); + FN_REGISTER_MAP.borrow_mut(|inner| { + inner.insert(cb, (c_fn, name)); }); } @@ -188,9 +188,8 @@ pub fn register_class( /// pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result { wait_first_thread_registered(); - FN_REGISTER_MAP.with(|inner| { + FN_REGISTER_MAP.borrow_mut(|inner| { inner - .borrow() .get(&raw_fn) .and_then(|(cb, name)| { let mut function = ptr::null_mut(); @@ -243,9 +242,8 @@ pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result Result { wait_first_thread_registered(); - FN_REGISTER_MAP.with(|inner| { + FN_REGISTER_MAP.borrow_mut(|inner| { inner - .borrow() .get(&raw_fn) .and_then(|(cb, _name)| *cb) .ok_or_else(|| { @@ -266,6 +264,8 @@ unsafe extern "C" fn napi_register_module_v1( unsafe { sys::setup(); } + crate::__private::___CALL_FROM_FACTORY.get_or_default(); + let registered_classes_ptr = REGISTERED_CLASSES.get_or_default(); let lock = MODULE_REGISTER_LOCK .lock() .expect("Failed to acquire module register lock"); @@ -344,6 +344,9 @@ unsafe extern "C" fn napi_register_module_v1( }) }); + let mut registered_classes: RegisteredClasses = + HashMap::with_capacity(MODULE_CLASS_PROPERTIES.borrow_mut(|inner| inner.len())); + MODULE_CLASS_PROPERTIES.borrow_mut(|inner| { inner.iter().for_each(|(rust_name, js_mods)| { for (js_mod, (js_name, props)) in js_mods { @@ -407,10 +410,7 @@ unsafe extern "C" fn napi_register_module_v1( let mut ctor_ref = ptr::null_mut(); sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref); - REGISTERED_CLASSES.with(|registered_classes| { - let mut registered_class = registered_classes.borrow_mut(); - registered_class.insert(js_name.to_string(), ctor_ref); - }); + registered_classes.insert(js_name.to_string(), ctor_ref); check_status_or_throw!( env, @@ -430,7 +430,11 @@ unsafe extern "C" fn napi_register_module_v1( ); } } - }) + }); + registered_classes_ptr.store( + Box::into_raw(Box::new(registered_classes)), + Ordering::Relaxed, + ); }); #[cfg(feature = "compat-mode")] @@ -462,7 +466,8 @@ pub(crate) unsafe extern "C" fn noop( env: sys::napi_env, _info: sys::napi_callback_info, ) -> sys::napi_value { - if !crate::bindgen_runtime::___CALL_FROM_FACTORY.with(|inner| inner.load(Ordering::Relaxed)) { + let inner = crate::bindgen_runtime::___CALL_FROM_FACTORY.get_or_default(); + if !inner.load(Ordering::Relaxed) { unsafe { sys::napi_throw_error( env, diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index a813102b..631338bc 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -75,7 +75,6 @@ #[cfg(feature = "napi8")] mod async_cleanup_hook; - #[cfg(feature = "napi8")] pub use async_cleanup_hook::AsyncCleanupHook; mod async_work; @@ -86,7 +85,6 @@ mod cleanup_env; mod env; mod error; mod js_values; - #[cfg(all(feature = "tokio_rt", feature = "napi4"))] mod promise; mod status; @@ -169,7 +167,7 @@ pub mod bindgen_prelude { #[doc(hidden)] pub mod __private { pub use crate::bindgen_runtime::{ - get_class_constructor, iterator::create_iterator, register_class, + get_class_constructor, iterator::create_iterator, register_class, ___CALL_FROM_FACTORY, }; use crate::sys; diff --git a/crates/napi/src/win_delay_load_hook.rs b/crates/napi/src/win_delay_load_hook.rs deleted file mode 100644 index 98c3856c..00000000 --- a/crates/napi/src/win_delay_load_hook.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! The following directly was copied from [neon][]. -//! -//! Rust port of [win_delay_load_hook.cc][]. -//! -//! When the addon tries to load the "node.exe" DLL module, this module gives it the pointer to the -//! .exe we are running in instead. Typically, that will be the same value. But if the node executable -//! was renamed, you would not otherwise get the correct DLL. -//! -//! [neon]: https://github.com/neon-bindings/neon/blob/5ffa2d282177b63094c46e92b20b8e850d122e65/src/win_delay_load_hook.rs -//! [win_delay_load_hook.cc]: https://github.com/nodejs/node-gyp/blob/e18a61afc1669d4897e6c5c8a6694f4995a0f4d6/src/win_delay_load_hook.cc - -use std::ffi::CStr; -use std::os::raw::c_char; - -use windows::core::PCSTR; -use windows::Win32::Foundation::HINSTANCE; -use windows::Win32::System::LibraryLoader::GetModuleHandleA; -use windows::Win32::System::WindowsProgramming::{DELAYLOAD_INFO, PDELAYLOAD_FAILURE_DLL_CALLBACK}; - -// Structures hand-copied from -// https://docs.microsoft.com/en-us/cpp/build/reference/structure-and-constant-definitions - -const HOST_BINARIES: &[&[u8]] = &[b"node.exe", b"electron.exe"]; - -unsafe extern "C" fn load_exe_hook(event: u32, info: *const DELAYLOAD_INFO) -> HINSTANCE { - if event != 0x01 - /* dliNotePreLoadLibrary */ - { - return HINSTANCE::default(); - } - - let dll_name = unsafe { CStr::from_ptr((*info).TargetDllName.0 as *mut i8) }; - if !HOST_BINARIES - .iter() - .any(|&host_name| host_name == dll_name.to_bytes()) - { - return HINSTANCE::default(); - } - - match unsafe { GetModuleHandleA(PCSTR::default()) } { - Ok(h) => h, - Err(e) => unsafe { - let location = "win_delay_load_hook.rs\0"; - let err = format!("{}", e); - crate::sys::napi_fatal_error( - location.as_ptr() as *const c_char, - 22, - format!("{}\0", err).as_ptr() as *const c_char, - err.len(), - ); - unreachable!(); - }, - } -} - -#[no_mangle] -static mut __pfnDliNotifyHook2: *mut PDELAYLOAD_FAILURE_DLL_CALLBACK = - load_exe_hook as *mut PDELAYLOAD_FAILURE_DLL_CALLBACK; diff --git a/crates/sys/src/lib.rs b/crates/sys/src/lib.rs index 23962bf3..a0d12621 100644 --- a/crates/sys/src/lib.rs +++ b/crates/sys/src/lib.rs @@ -35,7 +35,19 @@ macro_rules! generate { ) -> Result<(), libloading::Error> { NAPI = Napi { $( - $name: *host.get(stringify!($name).as_bytes())?, + $name: { + let symbol: Result $rtype)*>, libloading::Error> = host.get(stringify!($name).as_bytes()); + match symbol { + Ok(f) => *f, + Err(e) => { + debug_assert!({ + println!("Load Node-API [{}] from host runtime failed: {}", stringify!($name), e); + true + }); + return Ok(()); + } + } + }, )* }; @@ -44,6 +56,7 @@ macro_rules! generate { $( #[inline] + #[allow(clippy::missing_safety_doc)] pub unsafe fn $name($($param: $ptype,)*)$( -> $rtype)* { (NAPI.$name)($($param,)*) } @@ -81,6 +94,7 @@ static SETUP: Once = Once::new(); /// they will panic. /// Safety: `env` must be a valid `napi_env` for the current thread #[cfg(windows)] +#[allow(clippy::missing_safety_doc)] pub unsafe fn setup() { SETUP.call_once(|| { if let Err(err) = load() { diff --git a/examples/napi/__test__/strict.spect.ts b/examples/napi/__test__/strict.spec.ts similarity index 100% rename from examples/napi/__test__/strict.spect.ts rename to examples/napi/__test__/strict.spec.ts