napi_h/bindgen_runtime/
module_register.rs

1use std::collections::HashMap;
2#[cfg(not(feature = "noop"))]
3use std::collections::HashSet;
4use std::ffi::CStr;
5use std::ptr;
6#[cfg(all(feature = "napi4", not(target_family = "wasm")))]
7use std::sync::atomic::AtomicPtr;
8#[cfg(all(
9  not(any(target_os = "macos", target_family = "wasm")),
10  feature = "napi4",
11  feature = "tokio_rt"
12))]
13use std::sync::atomic::AtomicUsize;
14#[cfg(not(feature = "noop"))]
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::RwLock;
17use std::thread::ThreadId;
18
19use once_cell::sync::Lazy;
20
21use crate::{check_status, sys, Env, JsFunction, Property, Result, Value, ValueType};
22#[cfg(not(feature = "noop"))]
23use crate::{check_status_or_throw, JsError};
24
25pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>;
26pub type ModuleExportsCallback =
27  unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
28
29#[repr(transparent)]
30pub(crate) struct PersistedPerInstanceHashMap<K, V>(RwLock<HashMap<K, V>>);
31
32impl<K, V> PersistedPerInstanceHashMap<K, V> {
33  #[cfg(not(feature = "noop"))]
34  pub(crate) fn from_hashmap(hashmap: HashMap<K, V>) -> Self {
35    Self(RwLock::new(hashmap))
36  }
37
38  #[allow(clippy::mut_from_ref)]
39  pub(crate) fn borrow_mut<F, R>(&self, f: F) -> R
40  where
41    F: FnOnce(&mut HashMap<K, V>) -> R,
42  {
43    let mut write_lock = self.0.write().unwrap();
44    f(&mut *write_lock)
45  }
46}
47
48impl<K, V> Default for PersistedPerInstanceHashMap<K, V> {
49  fn default() -> Self {
50    Self(RwLock::new(HashMap::default()))
51  }
52}
53
54type ModuleRegisterCallback =
55  RwLock<Vec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>>;
56
57type ModuleClassProperty = PersistedPerInstanceHashMap<
58  &'static str,
59  HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
60>;
61
62unsafe impl<K, V> Send for PersistedPerInstanceHashMap<K, V> {}
63unsafe impl<K, V> Sync for PersistedPerInstanceHashMap<K, V> {}
64
65type FnRegisterMap =
66  PersistedPerInstanceHashMap<ExportRegisterCallback, (sys::napi_callback, &'static str)>;
67type RegisteredClassesMap = PersistedPerInstanceHashMap<ThreadId, RegisteredClasses>;
68
69static MODULE_REGISTER_CALLBACK: Lazy<ModuleRegisterCallback> = Lazy::new(Default::default);
70static MODULE_CLASS_PROPERTIES: Lazy<ModuleClassProperty> = Lazy::new(Default::default);
71#[cfg(not(feature = "noop"))]
72static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true);
73#[cfg(not(feature = "noop"))]
74static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false);
75static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default);
76static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default);
77#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
78pub(crate) static CUSTOM_GC_TSFN: AtomicPtr<sys::napi_threadsafe_function__> =
79  AtomicPtr::new(ptr::null_mut());
80#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
81pub(crate) static CUSTOM_GC_TSFN_DESTROYED: AtomicBool = AtomicBool::new(false);
82#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
83// Store thread id of the thread that created the CustomGC ThreadsafeFunction.
84pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::Lazy<
85  PersistedPerInstanceHashMap<ThreadId, bool>,
86> = once_cell::sync::Lazy::new(Default::default);
87
88type RegisteredClasses =
89  PersistedPerInstanceHashMap</* export name */ String, /* constructor */ sys::napi_ref>;
90
91#[cfg(all(feature = "compat-mode", not(feature = "noop")))]
92// compatibility for #[module_exports]
93static MODULE_EXPORTS: Lazy<RwLock<Vec<ModuleExportsCallback>>> = Lazy::new(Default::default);
94
95#[cfg(not(feature = "noop"))]
96#[inline]
97fn wait_first_thread_registered() {
98  while !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) {
99    std::hint::spin_loop();
100  }
101}
102
103#[doc(hidden)]
104pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
105  let current_id = std::thread::current().id();
106  REGISTERED_CLASSES.borrow_mut(|map| {
107    map
108      .get(&current_id)
109      .map(|m| m.borrow_mut(|map| map.get(js_name).copied()))
110  })?
111}
112
113#[doc(hidden)]
114#[cfg(all(feature = "compat-mode", not(feature = "noop")))]
115// compatibility for #[module_exports]
116pub fn register_module_exports(callback: ModuleExportsCallback) {
117  MODULE_EXPORTS
118    .write()
119    .expect("Register module exports failed")
120    .push(callback);
121}
122
123#[doc(hidden)]
124pub fn register_module_export(
125  js_mod: Option<&'static str>,
126  name: &'static str,
127  cb: ExportRegisterCallback,
128) {
129  MODULE_REGISTER_CALLBACK
130    .write()
131    .expect("Register module export failed")
132    .push((js_mod, (name, cb)));
133}
134
135#[doc(hidden)]
136pub fn register_js_function(
137  name: &'static str,
138  cb: ExportRegisterCallback,
139  c_fn: sys::napi_callback,
140) {
141  FN_REGISTER_MAP.borrow_mut(|inner| {
142    inner.insert(cb, (c_fn, name));
143  });
144}
145
146#[doc(hidden)]
147pub fn register_class(
148  rust_name: &'static str,
149  js_mod: Option<&'static str>,
150  js_name: &'static str,
151  props: Vec<Property>,
152) {
153  MODULE_CLASS_PROPERTIES.borrow_mut(|inner| {
154    let val = inner.entry(rust_name).or_default();
155    let val = val.entry(js_mod).or_default();
156    val.0 = js_name;
157    val.1.extend(props);
158  });
159}
160
161#[inline]
162/// Get `JsFunction` from defined Rust `fn`
163/// ```rust
164/// #[napi]
165/// fn some_fn() -> u32 {
166///     1
167/// }
168///
169/// #[napi]
170/// fn return_some_fn() -> Result<JsFunction> {
171///     get_js_function(some_fn_js_function)
172/// }
173/// ```
174///
175/// ```js
176/// returnSomeFn()(); // 1
177/// ```
178///
179pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result<JsFunction> {
180  FN_REGISTER_MAP.borrow_mut(|inner| {
181    inner
182      .get(&raw_fn)
183      .and_then(|(cb, name)| {
184        let mut function = ptr::null_mut();
185        let name_len = name.len() - 1;
186        let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };
187        check_status!(unsafe {
188          sys::napi_create_function(
189            env.0,
190            fn_name.as_ptr(),
191            name_len,
192            *cb,
193            ptr::null_mut(),
194            &mut function,
195          )
196        })
197        .ok()?;
198        Some(JsFunction(Value {
199          env: env.0,
200          value: function,
201          value_type: ValueType::Function,
202        }))
203      })
204      .ok_or_else(|| {
205        crate::Error::new(
206          crate::Status::InvalidArg,
207          "JavaScript function does not exist".to_owned(),
208        )
209      })
210  })
211}
212
213/// Get `C Callback` from defined Rust `fn`
214/// ```rust
215/// #[napi]
216/// fn some_fn() -> u32 {
217///     1
218/// }
219///
220/// #[napi]
221/// fn create_obj(env: Env) -> Result<JsObject> {
222///     let mut obj = env.create_object()?;
223///     obj.define_property(&[Property::new("getter")?.with_getter(get_c_callback(some_fn_js_function)?)])?;
224///     Ok(obj)
225/// }
226/// ```
227///
228/// ```js
229/// console.log(createObj().getter) // 1
230/// ```
231///
232pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result<crate::Callback> {
233  FN_REGISTER_MAP.borrow_mut(|inner| {
234    inner
235      .get(&raw_fn)
236      .and_then(|(cb, _name)| *cb)
237      .ok_or_else(|| {
238        crate::Error::new(
239          crate::Status::InvalidArg,
240          "JavaScript function does not exist".to_owned(),
241        )
242      })
243  })
244}
245
246#[cfg(all(windows, not(feature = "noop")))]
247#[ctor::ctor]
248fn load_host() {
249  unsafe {
250    sys::setup();
251  }
252}
253
254#[cfg(all(target_family = "wasm", not(feature = "noop")))]
255#[no_mangle]
256unsafe extern "C" fn napi_register_wasm_v1(
257  env: sys::napi_env,
258  exports: sys::napi_value,
259) -> sys::napi_value {
260  unsafe { napi_register_module_v1(env, exports) }
261}
262
263#[cfg(not(feature = "noop"))]
264#[no_mangle]
265/// Register the n-api module exports.
266///
267/// # Safety
268/// This method is meant to be called by Node.js while importing the n-api module.
269/// Only call this method if the current module is **not** imported by a node-like runtime.
270///
271/// Arguments `env` and `exports` must **not** be null.
272pub unsafe extern "C" fn napi_register_module_v1(
273  env: sys::napi_env,
274  exports: sys::napi_value,
275) -> sys::napi_value {
276  if IS_FIRST_MODULE.load(Ordering::SeqCst) {
277    IS_FIRST_MODULE.store(false, Ordering::SeqCst);
278  } else {
279    wait_first_thread_registered();
280  }
281  let mut exports_objects: HashSet<String> = HashSet::default();
282
283  {
284    let mut register_callback = MODULE_REGISTER_CALLBACK
285      .write()
286      .expect("Write MODULE_REGISTER_CALLBACK in napi_register_module_v1 failed");
287    register_callback
288      .iter_mut()
289      .fold(
290        HashMap::<Option<&'static str>, Vec<(&'static str, ExportRegisterCallback)>>::new(),
291        |mut acc, (js_mod, item)| {
292          if let Some(k) = acc.get_mut(js_mod) {
293            k.push(*item);
294          } else {
295            acc.insert(*js_mod, vec![*item]);
296          }
297          acc
298        },
299      )
300      .iter()
301      .for_each(|(js_mod, items)| {
302        let mut exports_js_mod = ptr::null_mut();
303        if let Some(js_mod_str) = js_mod {
304          let mod_name_c_str =
305            unsafe { CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes()) };
306          if exports_objects.contains(*js_mod_str) {
307            check_status_or_throw!(
308              env,
309              unsafe {
310                sys::napi_get_named_property(
311                  env,
312                  exports,
313                  mod_name_c_str.as_ptr(),
314                  &mut exports_js_mod,
315                )
316              },
317              "Get mod {} from exports failed",
318              js_mod_str,
319            );
320          } else {
321            check_status_or_throw!(
322              env,
323              unsafe { sys::napi_create_object(env, &mut exports_js_mod) },
324              "Create export JavaScript Object [{}] failed",
325              js_mod_str
326            );
327            check_status_or_throw!(
328              env,
329              unsafe {
330                sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod)
331              },
332              "Set exports Object [{}] into exports object failed",
333              js_mod_str
334            );
335            exports_objects.insert(js_mod_str.to_string());
336          }
337        }
338        for (name, callback) in items {
339          unsafe {
340            let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
341            if let Err(e) = callback(env).and_then(|v| {
342              let exported_object = if exports_js_mod.is_null() {
343                exports
344              } else {
345                exports_js_mod
346              };
347              check_status!(
348                sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v),
349                "Failed to register export `{}`",
350                name,
351              )
352            }) {
353              JsError::from(e).throw_into(env)
354            }
355          }
356        }
357      });
358  }
359
360  let mut registered_classes = HashMap::new();
361
362  MODULE_CLASS_PROPERTIES.borrow_mut(|inner| {
363    inner.iter().for_each(|(rust_name, js_mods)| {
364      for (js_mod, (js_name, props)) in js_mods {
365        let mut exports_js_mod = ptr::null_mut();
366        unsafe {
367          if let Some(js_mod_str) = js_mod {
368            let mod_name_c_str = CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes());
369            if exports_objects.contains(*js_mod_str) {
370              check_status_or_throw!(
371                env,
372                sys::napi_get_named_property(
373                  env,
374                  exports,
375                  mod_name_c_str.as_ptr(),
376                  &mut exports_js_mod,
377                ),
378                "Get mod {} from exports failed",
379                js_mod_str,
380              );
381            } else {
382              check_status_or_throw!(
383                env,
384                sys::napi_create_object(env, &mut exports_js_mod),
385                "Create export JavaScript Object [{}] failed",
386                js_mod_str
387              );
388              check_status_or_throw!(
389                env,
390                sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod),
391                "Set exports Object [{}] into exports object failed",
392                js_mod_str
393              );
394              exports_objects.insert(js_mod_str.to_string());
395            }
396          }
397          let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor);
398
399          let ctor = ctor
400            .first()
401            .map(|c| c.raw().method.unwrap())
402            .unwrap_or(noop);
403          let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
404
405          let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
406          let mut class_ptr = ptr::null_mut();
407
408          check_status_or_throw!(
409            env,
410            sys::napi_define_class(
411              env,
412              js_class_name.as_ptr(),
413              js_name.len() - 1,
414              Some(ctor),
415              ptr::null_mut(),
416              raw_props.len(),
417              raw_props.as_ptr(),
418              &mut class_ptr,
419            ),
420            "Failed to register class `{}` generate by struct `{}`",
421            &js_name,
422            &rust_name
423          );
424
425          let mut ctor_ref = ptr::null_mut();
426          sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
427
428          registered_classes.insert(js_name.to_string(), ctor_ref);
429
430          check_status_or_throw!(
431            env,
432            sys::napi_set_named_property(
433              env,
434              if exports_js_mod.is_null() {
435                exports
436              } else {
437                exports_js_mod
438              },
439              js_class_name.as_ptr(),
440              class_ptr
441            ),
442            "Failed to register class `{}` generate by struct `{}`",
443            &js_name,
444            &rust_name
445          );
446        }
447      }
448    });
449
450    REGISTERED_CLASSES.borrow_mut(|map| {
451      map.insert(
452        std::thread::current().id(),
453        PersistedPerInstanceHashMap::from_hashmap(registered_classes),
454      )
455    });
456  });
457
458  #[cfg(feature = "compat-mode")]
459  {
460    let module_exports = MODULE_EXPORTS.read().expect("Read MODULE_EXPORTS failed");
461    module_exports.iter().for_each(|callback| unsafe {
462      if let Err(e) = callback(env, exports) {
463        JsError::from(e).throw_into(env);
464      }
465    })
466  }
467
468  #[cfg(all(
469    not(any(target_os = "macos", target_family = "wasm")),
470    feature = "napi4",
471    feature = "tokio_rt"
472  ))]
473  {
474    crate::tokio_runtime::ensure_runtime();
475
476    static init_counter: AtomicUsize = AtomicUsize::new(0);
477    let cleanup_hook_payload =
478      init_counter.fetch_add(1, Ordering::Relaxed) as *mut std::ffi::c_void;
479
480    unsafe {
481      sys::napi_add_env_cleanup_hook(
482        env,
483        Some(crate::tokio_runtime::drop_runtime),
484        cleanup_hook_payload,
485      )
486    };
487  }
488  #[cfg(all(feature = "napi4", not(target_family = "wasm")))]
489  create_custom_gc(env);
490  FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst);
491  exports
492}
493
494#[cfg(not(feature = "noop"))]
495pub(crate) unsafe extern "C" fn noop(
496  env: sys::napi_env,
497  _info: sys::napi_callback_info,
498) -> sys::napi_value {
499  if !crate::bindgen_runtime::___CALL_FROM_FACTORY.with(|s| s.load(Ordering::Relaxed)) {
500    unsafe {
501      sys::napi_throw_error(
502        env,
503        ptr::null_mut(),
504        CStr::from_bytes_with_nul_unchecked(b"Class contains no `constructor`, can not new it!\0")
505          .as_ptr(),
506      );
507    }
508  }
509  ptr::null_mut()
510}
511
512#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
513fn create_custom_gc(env: sys::napi_env) {
514  use std::os::raw::c_char;
515
516  if !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) {
517    let mut custom_gc_fn = ptr::null_mut();
518    check_status_or_throw!(
519      env,
520      unsafe {
521        sys::napi_create_function(
522          env,
523          "custom_gc".as_ptr().cast(),
524          9,
525          Some(empty),
526          ptr::null_mut(),
527          &mut custom_gc_fn,
528        )
529      },
530      "Create Custom GC Function in napi_register_module_v1 failed"
531    );
532    let mut async_resource_name = ptr::null_mut();
533    check_status_or_throw!(
534      env,
535      unsafe {
536        sys::napi_create_string_utf8(
537          env,
538          "CustomGC".as_ptr() as *const c_char,
539          8,
540          &mut async_resource_name,
541        )
542      },
543      "Create async resource string in napi_register_module_v1"
544    );
545    let mut custom_gc_tsfn = ptr::null_mut();
546    check_status_or_throw!(
547      env,
548      unsafe {
549        sys::napi_create_threadsafe_function(
550          env,
551          custom_gc_fn,
552          ptr::null_mut(),
553          async_resource_name,
554          0,
555          1,
556          ptr::null_mut(),
557          Some(custom_gc_finalize),
558          ptr::null_mut(),
559          Some(custom_gc),
560          &mut custom_gc_tsfn,
561        )
562      },
563      "Create Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
564    );
565    check_status_or_throw!(
566      env,
567      unsafe { sys::napi_unref_threadsafe_function(env, custom_gc_tsfn) },
568      "Unref Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
569    );
570    CUSTOM_GC_TSFN.store(custom_gc_tsfn, Ordering::Relaxed);
571  }
572
573  let current_thread_id = std::thread::current().id();
574  THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(current_thread_id, true));
575  check_status_or_throw!(
576    env,
577    unsafe {
578      sys::napi_add_env_cleanup_hook(
579        env,
580        Some(remove_thread_id),
581        Box::into_raw(Box::new(current_thread_id)).cast(),
582      )
583    },
584    "Failed to add remove thread id cleanup hook"
585  );
586}
587
588#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
589unsafe extern "C" fn remove_thread_id(id: *mut std::ffi::c_void) {
590  let thread_id = unsafe { Box::from_raw(id.cast::<ThreadId>()) };
591  THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(*thread_id, false));
592}
593
594#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
595#[allow(unused)]
596unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value {
597  ptr::null_mut()
598}
599
600#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
601#[allow(unused_variables)]
602unsafe extern "C" fn custom_gc_finalize(
603  env: sys::napi_env,
604  finalize_data: *mut std::ffi::c_void,
605  finalize_hint: *mut std::ffi::c_void,
606) {
607  CUSTOM_GC_TSFN_DESTROYED.store(true, Ordering::SeqCst);
608}
609
610#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
611// recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread
612extern "C" fn custom_gc(
613  env: sys::napi_env,
614  _js_callback: sys::napi_value,
615  _context: *mut std::ffi::c_void,
616  data: *mut std::ffi::c_void,
617) {
618  // current thread was destroyed
619  if THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.get(&std::thread::current().id()) == Some(&false)) {
620    return;
621  }
622  let mut ref_count = 0;
623  check_status_or_throw!(
624    env,
625    unsafe { sys::napi_reference_unref(env, data.cast(), &mut ref_count) },
626    "Failed to unref Buffer reference in Custom GC"
627  );
628  debug_assert!(
629    ref_count == 0,
630    "Buffer reference count in Custom GC is not 0"
631  );
632  check_status_or_throw!(
633    env,
634    unsafe { sys::napi_delete_reference(env, data.cast()) },
635    "Failed to delete Buffer reference in Custom GC"
636  );
637}