Skip to main content

deno_napi/
lib.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3#![allow(non_camel_case_types)]
4#![allow(non_upper_case_globals)]
5#![allow(clippy::undocumented_unsafe_blocks)]
6#![deny(clippy::missing_safety_doc)]
7
8//! Symbols to be exported are now defined in this JSON file.
9//! The `#[napi_sym]` macro checks for missing entries and panics.
10//!
11//! `./tools/napi/generate_symbols_list.js` is used to generate the LINK `cli/exports.def` on Windows,
12//! which is also checked into git.
13//!
14//! To add a new napi function:
15//! 1. Place `#[napi_sym]` on top of your implementation.
16//! 2. Add the function's identifier to this JSON list.
17//! 3. Finally, run `tools/napi/generate_symbols_list.js` to update `ext/napi/generated_symbol_exports_list_*.def`.
18
19pub mod js_native_api;
20pub mod node_api;
21pub mod util;
22pub mod uv;
23
24use core::ptr::NonNull;
25use std::borrow::Cow;
26use std::cell::Cell;
27use std::cell::RefCell;
28use std::collections::HashMap;
29pub use std::ffi::CStr;
30pub use std::os::raw::c_char;
31pub use std::os::raw::c_void;
32use std::path::Path;
33use std::path::PathBuf;
34pub use std::ptr;
35use std::rc::Rc;
36use std::thread_local;
37
38use deno_core::ExternalOpsTracker;
39use deno_core::OpState;
40use deno_core::V8CrossThreadTaskSpawner;
41use deno_core::op2;
42use deno_core::parking_lot::RwLock;
43use deno_core::url::Url;
44// Expose common stuff for ease of use.
45// `use deno_napi::*`
46pub use deno_core::v8;
47use deno_permissions::PermissionCheckError;
48pub use denort_helper::DenoRtNativeAddonLoader;
49pub use denort_helper::DenoRtNativeAddonLoaderRc;
50#[cfg(unix)]
51use libloading::os::unix::*;
52#[cfg(windows)]
53use libloading::os::windows::*;
54pub use value::napi_value;
55
56pub mod function;
57mod value;
58
59#[derive(Debug, thiserror::Error, deno_error::JsError)]
60pub enum NApiError {
61  #[class(type)]
62  #[error("Invalid path")]
63  InvalidPath,
64  #[class(type)]
65  #[error(transparent)]
66  DenoRtLoad(#[from] denort_helper::LoadError),
67  #[class(type)]
68  #[error(transparent)]
69  LibLoading(#[from] libloading::Error),
70  #[class(type)]
71  #[error("Unable to find register Node-API module at {}", .0.display())]
72  ModuleNotFound(PathBuf),
73  #[class(inherit)]
74  #[error(transparent)]
75  Permission(#[from] PermissionCheckError),
76}
77
78pub type napi_status = i32;
79pub type napi_env = *mut c_void;
80pub type napi_callback_info = *mut c_void;
81pub type napi_deferred = *mut c_void;
82pub type napi_ref = *mut c_void;
83pub type napi_threadsafe_function = *mut c_void;
84pub type napi_handle_scope = *mut c_void;
85pub type napi_callback_scope = *mut c_void;
86pub type napi_escapable_handle_scope = *mut c_void;
87pub type napi_async_cleanup_hook_handle = *mut c_void;
88pub type napi_async_work = *mut c_void;
89pub type napi_async_context = *mut c_void;
90
91pub const napi_ok: napi_status = 0;
92pub const napi_invalid_arg: napi_status = 1;
93pub const napi_object_expected: napi_status = 2;
94pub const napi_string_expected: napi_status = 3;
95pub const napi_name_expected: napi_status = 4;
96pub const napi_function_expected: napi_status = 5;
97pub const napi_number_expected: napi_status = 6;
98pub const napi_boolean_expected: napi_status = 7;
99pub const napi_array_expected: napi_status = 8;
100pub const napi_generic_failure: napi_status = 9;
101pub const napi_pending_exception: napi_status = 10;
102pub const napi_cancelled: napi_status = 11;
103pub const napi_escape_called_twice: napi_status = 12;
104pub const napi_handle_scope_mismatch: napi_status = 13;
105pub const napi_callback_scope_mismatch: napi_status = 14;
106pub const napi_queue_full: napi_status = 15;
107pub const napi_closing: napi_status = 16;
108pub const napi_bigint_expected: napi_status = 17;
109pub const napi_date_expected: napi_status = 18;
110pub const napi_arraybuffer_expected: napi_status = 19;
111pub const napi_detachable_arraybuffer_expected: napi_status = 20;
112pub const napi_would_deadlock: napi_status = 21;
113pub const napi_no_external_buffers_allowed: napi_status = 22;
114pub const napi_cannot_run_js: napi_status = 23;
115
116pub static ERROR_MESSAGES: &[&CStr] = &[
117  c"",
118  c"Invalid argument",
119  c"An object was expected",
120  c"A string was expected",
121  c"A string or symbol was expected",
122  c"A function was expected",
123  c"A number was expected",
124  c"A boolean was expected",
125  c"An array was expected",
126  c"Unknown failure",
127  c"An exception is pending",
128  c"The async work item was cancelled",
129  c"napi_escape_handle already called on scope",
130  c"Invalid handle scope usage",
131  c"Invalid callback scope usage",
132  c"Thread-safe function queue is full",
133  c"Thread-safe function handle is closing",
134  c"A bigint was expected",
135  c"A date was expected",
136  c"An arraybuffer was expected",
137  c"A detachable arraybuffer was expected",
138  c"Main thread would deadlock",
139  c"External buffers are not allowed",
140  c"Cannot run JavaScript",
141];
142
143pub const NAPI_AUTO_LENGTH: usize = usize::MAX;
144
145thread_local! {
146  pub static MODULE_TO_REGISTER: RefCell<Option<*const NapiModule>> = const { RefCell::new(None) };
147}
148
149type napi_addon_register_func =
150  unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
151type napi_register_module_v1 =
152  unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
153
154#[repr(C)]
155#[derive(Clone)]
156pub struct NapiModule {
157  pub nm_version: i32,
158  pub nm_flags: u32,
159  nm_filename: *const c_char,
160  pub nm_register_func: napi_addon_register_func,
161  nm_modname: *const c_char,
162  nm_priv: *mut c_void,
163  reserved: [*mut c_void; 4],
164}
165
166pub type napi_valuetype = i32;
167
168pub const napi_undefined: napi_valuetype = 0;
169pub const napi_null: napi_valuetype = 1;
170pub const napi_boolean: napi_valuetype = 2;
171pub const napi_number: napi_valuetype = 3;
172pub const napi_string: napi_valuetype = 4;
173pub const napi_symbol: napi_valuetype = 5;
174pub const napi_object: napi_valuetype = 6;
175pub const napi_function: napi_valuetype = 7;
176pub const napi_external: napi_valuetype = 8;
177pub const napi_bigint: napi_valuetype = 9;
178
179pub type napi_threadsafe_function_release_mode = i32;
180
181pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0;
182pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1;
183
184pub type napi_threadsafe_function_call_mode = i32;
185
186pub const napi_tsfn_nonblocking: napi_threadsafe_function_call_mode = 0;
187pub const napi_tsfn_blocking: napi_threadsafe_function_call_mode = 1;
188
189pub type napi_key_collection_mode = i32;
190
191pub const napi_key_include_prototypes: napi_key_collection_mode = 0;
192pub const napi_key_own_only: napi_key_collection_mode = 1;
193
194pub type napi_key_filter = i32;
195
196pub const napi_key_all_properties: napi_key_filter = 0;
197pub const napi_key_writable: napi_key_filter = 1;
198pub const napi_key_enumerable: napi_key_filter = 1 << 1;
199pub const napi_key_configurable: napi_key_filter = 1 << 2;
200pub const napi_key_skip_strings: napi_key_filter = 1 << 3;
201pub const napi_key_skip_symbols: napi_key_filter = 1 << 4;
202
203pub type napi_key_conversion = i32;
204
205pub const napi_key_keep_numbers: napi_key_conversion = 0;
206pub const napi_key_numbers_to_strings: napi_key_conversion = 1;
207
208pub type napi_typedarray_type = i32;
209
210pub const napi_int8_array: napi_typedarray_type = 0;
211pub const napi_uint8_array: napi_typedarray_type = 1;
212pub const napi_uint8_clamped_array: napi_typedarray_type = 2;
213pub const napi_int16_array: napi_typedarray_type = 3;
214pub const napi_uint16_array: napi_typedarray_type = 4;
215pub const napi_int32_array: napi_typedarray_type = 5;
216pub const napi_uint32_array: napi_typedarray_type = 6;
217pub const napi_float32_array: napi_typedarray_type = 7;
218pub const napi_float64_array: napi_typedarray_type = 8;
219pub const napi_bigint64_array: napi_typedarray_type = 9;
220pub const napi_biguint64_array: napi_typedarray_type = 10;
221
222#[repr(C)]
223#[derive(Clone, Copy, PartialEq)]
224pub struct napi_type_tag {
225  pub lower: u64,
226  pub upper: u64,
227}
228
229pub type napi_callback = unsafe extern "C" fn(
230  env: napi_env,
231  info: napi_callback_info,
232) -> napi_value<'static>;
233
234pub type napi_finalize = unsafe extern "C" fn(
235  env: napi_env,
236  data: *mut c_void,
237  finalize_hint: *mut c_void,
238);
239
240pub type napi_async_execute_callback =
241  unsafe extern "C" fn(env: napi_env, data: *mut c_void);
242
243pub type napi_async_complete_callback =
244  unsafe extern "C" fn(env: napi_env, status: napi_status, data: *mut c_void);
245
246pub type napi_threadsafe_function_call_js = unsafe extern "C" fn(
247  env: napi_env,
248  js_callback: napi_value,
249  context: *mut c_void,
250  data: *mut c_void,
251);
252
253pub type napi_async_cleanup_hook = unsafe extern "C" fn(
254  handle: napi_async_cleanup_hook_handle,
255  data: *mut c_void,
256);
257
258pub type napi_cleanup_hook = unsafe extern "C" fn(data: *mut c_void);
259
260pub type napi_property_attributes = i32;
261
262pub const napi_default: napi_property_attributes = 0;
263pub const napi_writable: napi_property_attributes = 1 << 0;
264pub const napi_enumerable: napi_property_attributes = 1 << 1;
265pub const napi_configurable: napi_property_attributes = 1 << 2;
266pub const napi_static: napi_property_attributes = 1 << 10;
267pub const napi_default_method: napi_property_attributes =
268  napi_writable | napi_configurable;
269pub const napi_default_jsproperty: napi_property_attributes =
270  napi_enumerable | napi_configurable | napi_writable;
271
272#[repr(C)]
273#[derive(Copy, Clone, Debug)]
274pub struct napi_property_descriptor<'a> {
275  pub utf8name: *const c_char,
276  pub name: napi_value<'a>,
277  pub method: Option<napi_callback>,
278  pub getter: Option<napi_callback>,
279  pub setter: Option<napi_callback>,
280  pub value: napi_value<'a>,
281  pub attributes: napi_property_attributes,
282  pub data: *mut c_void,
283}
284
285#[repr(C)]
286#[derive(Debug)]
287pub struct napi_extended_error_info {
288  pub error_message: *const c_char,
289  pub engine_reserved: *mut c_void,
290  pub engine_error_code: i32,
291  pub error_code: Cell<napi_status>,
292}
293
294#[repr(C)]
295#[derive(Debug)]
296pub struct napi_node_version {
297  pub major: u32,
298  pub minor: u32,
299  pub patch: u32,
300  pub release: *const c_char,
301}
302
303pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {}
304impl<T> PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {}
305
306pub struct NapiState {
307  // Thread safe functions.
308  pub env_cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
309  pub env_shared_ptrs: Vec<*mut EnvShared>,
310}
311
312// SAFETY: finalizer pointers in env_shared_ptrs are only accessed during Drop
313// on the same thread that created them.
314unsafe impl Send for NapiState {}
315
316impl Drop for NapiState {
317  fn drop(&mut self) {
318    let hooks = {
319      let h = self.env_cleanup_hooks.borrow_mut();
320      h.clone()
321    };
322
323    // Hooks are supposed to be run in LIFO order
324    let hooks_to_run = hooks.into_iter().rev();
325
326    for hook in hooks_to_run {
327      // This hook might have been removed by a previous hook, in such case skip it here.
328      if !self
329        .env_cleanup_hooks
330        .borrow()
331        .iter()
332        .any(|pair| std::ptr::fn_addr_eq(pair.0, hook.0) && pair.1 == hook.1)
333      {
334        continue;
335      }
336
337      unsafe {
338        (hook.0)(hook.1);
339      }
340
341      {
342        self.env_cleanup_hooks.borrow_mut().retain(|pair| {
343          !(std::ptr::fn_addr_eq(pair.0, hook.0) && pair.1 == hook.1)
344        });
345      }
346    }
347
348    // Call instance data finalize callbacks for all registered EnvShared instances.
349    // Each entry should be unique since each op_napi_open creates a fresh EnvShared.
350    debug_assert!(
351      {
352        let mut seen = std::collections::HashSet::new();
353        self.env_shared_ptrs.iter().all(|p| seen.insert(*p))
354      },
355      "env_shared_ptrs contains duplicate entries"
356    );
357    for env_shared_ptr in &self.env_shared_ptrs {
358      // SAFETY: env_shared_ptr was created via Box::into_raw in op_napi_open
359      // and the native module library is kept alive (via std::mem::forget).
360      let env_shared = unsafe { &mut **env_shared_ptr };
361      if let Some(instance_data) = env_shared.instance_data.take()
362        && let Some(cb) = instance_data.finalize_cb
363      {
364        unsafe {
365          cb(
366            std::ptr::null_mut(),
367            instance_data.data,
368            instance_data.finalize_hint,
369          );
370        }
371      }
372    }
373  }
374}
375
376#[repr(C)]
377#[derive(Debug)]
378pub struct InstanceData {
379  pub data: *mut c_void,
380  pub finalize_cb: Option<napi_finalize>,
381  pub finalize_hint: *mut c_void,
382}
383
384#[repr(C)]
385#[derive(Debug)]
386/// Env that is shared between all contexts in same native module.
387pub struct EnvShared {
388  pub instance_data: Option<InstanceData>,
389  pub napi_wrap: v8::Global<v8::Private>,
390  pub type_tag: v8::Global<v8::Private>,
391  pub finalize: Option<napi_finalize>,
392  pub finalize_hint: *mut c_void,
393  pub filename: String,
394}
395
396impl EnvShared {
397  pub fn new(
398    napi_wrap: v8::Global<v8::Private>,
399    type_tag: v8::Global<v8::Private>,
400    filename: String,
401  ) -> Self {
402    Self {
403      instance_data: None,
404      napi_wrap,
405      type_tag,
406      finalize: None,
407      finalize_hint: std::ptr::null_mut(),
408      filename,
409    }
410  }
411}
412
413#[repr(C)]
414pub struct Env {
415  context: NonNull<v8::Context>,
416  pub isolate_ptr: v8::UnsafeRawIsolatePtr,
417  pub open_handle_scopes: usize,
418  pub shared: *mut EnvShared,
419  pub async_work_sender: V8CrossThreadTaskSpawner,
420  cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
421  external_ops_tracker: ExternalOpsTracker,
422  pub last_error: napi_extended_error_info,
423  pub last_exception: Option<v8::Global<v8::Value>>,
424  pub global: v8::Global<v8::Object>,
425  pub create_buffer: v8::Global<v8::Function>,
426  pub report_error: v8::Global<v8::Function>,
427}
428
429unsafe impl Send for Env {}
430unsafe impl Sync for Env {}
431
432impl Env {
433  #[allow(clippy::too_many_arguments)]
434  pub fn new(
435    isolate_ptr: v8::UnsafeRawIsolatePtr,
436    context: v8::Global<v8::Context>,
437    global: v8::Global<v8::Object>,
438    create_buffer: v8::Global<v8::Function>,
439    report_error: v8::Global<v8::Function>,
440    sender: V8CrossThreadTaskSpawner,
441    cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
442    external_ops_tracker: ExternalOpsTracker,
443  ) -> Self {
444    Self {
445      isolate_ptr,
446      context: context.into_raw(),
447      global,
448      create_buffer,
449      report_error,
450      shared: std::ptr::null_mut(),
451      open_handle_scopes: 0,
452      async_work_sender: sender,
453      cleanup_hooks,
454      external_ops_tracker,
455      last_error: napi_extended_error_info {
456        error_message: std::ptr::null(),
457        engine_reserved: std::ptr::null_mut(),
458        engine_error_code: 0,
459        error_code: Cell::new(napi_ok),
460      },
461      last_exception: None,
462    }
463  }
464
465  pub fn shared(&self) -> &EnvShared {
466    // SAFETY: the lifetime of `EnvShared` always exceeds the lifetime of `Env`.
467    unsafe { &*self.shared }
468  }
469
470  pub fn shared_mut(&mut self) -> &mut EnvShared {
471    // SAFETY: the lifetime of `EnvShared` always exceeds the lifetime of `Env`.
472    unsafe { &mut *self.shared }
473  }
474
475  pub fn add_async_work(&mut self, async_work: impl FnOnce() + Send + 'static) {
476    self.async_work_sender.spawn(|_| async_work());
477  }
478
479  #[inline]
480  pub fn isolate(&mut self) -> &mut v8::Isolate {
481    // SAFETY: Lifetime of `Isolate` is longer than `Env`.
482    unsafe {
483      v8::Isolate::ref_from_raw_isolate_ptr_mut_unchecked(&mut self.isolate_ptr)
484    }
485  }
486
487  pub fn context<'s>(&'s self) -> v8::Local<'s, v8::Context> {
488    // SAFETY: `v8::Local` is always non-null pointer; the `PinScope<'_, '_>` is
489    // already on the stack, but we don't have access to it.
490    unsafe {
491      std::mem::transmute::<NonNull<v8::Context>, v8::Local<v8::Context>>(
492        self.context,
493      )
494    }
495  }
496
497  pub fn threadsafe_function_ref(&mut self) {
498    self.external_ops_tracker.ref_op();
499  }
500
501  pub fn threadsafe_function_unref(&mut self) {
502    self.external_ops_tracker.unref_op();
503  }
504
505  pub fn add_cleanup_hook(
506    &mut self,
507    hook: napi_cleanup_hook,
508    data: *mut c_void,
509  ) {
510    let mut hooks = self.cleanup_hooks.borrow_mut();
511    if hooks
512      .iter()
513      .any(|pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
514    {
515      panic!("Cannot register cleanup hook with same data twice");
516    }
517    hooks.push((hook, data));
518  }
519
520  pub fn remove_cleanup_hook(
521    &mut self,
522    hook: napi_cleanup_hook,
523    data: *mut c_void,
524  ) {
525    let mut hooks = self.cleanup_hooks.borrow_mut();
526    match hooks
527      .iter()
528      .rposition(|&pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
529    {
530      Some(index) => {
531        hooks.remove(index);
532      }
533      None => panic!("Cannot remove cleanup hook which was not registered"),
534    }
535  }
536}
537
538deno_core::extension!(deno_napi,
539  ops = [
540    op_napi_open
541  ],
542  options = {
543    deno_rt_native_addon_loader: Option<DenoRtNativeAddonLoaderRc>,
544  },
545  state = |state, options| {
546    state.put(NapiState {
547      env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
548      env_shared_ptrs: vec![],
549    });
550    if let Some(loader) = options.deno_rt_native_addon_loader {
551      state.put(loader);
552    }
553  },
554);
555
556unsafe impl Sync for NapiModuleHandle {}
557unsafe impl Send for NapiModuleHandle {}
558
559#[derive(Clone, Copy)]
560struct NapiModuleHandle(*const NapiModule);
561
562static NAPI_LOADED_MODULES: std::sync::LazyLock<
563  RwLock<HashMap<PathBuf, NapiModuleHandle>>,
564> = std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
565
566#[op2(reentrant, stack_trace)]
567fn op_napi_open<'scope>(
568  scope: &mut v8::PinScope<'scope, '_>,
569  isolate: &mut v8::Isolate,
570  op_state: Rc<RefCell<OpState>>,
571  #[string] path: &str,
572  global: v8::Local<'scope, v8::Object>,
573  create_buffer: v8::Local<'scope, v8::Function>,
574  report_error: v8::Local<'scope, v8::Function>,
575) -> Result<v8::Local<'scope, v8::Value>, NApiError> {
576  // We must limit the OpState borrow because this function can trigger a
577  // re-borrow through the NAPI module.
578  let (
579    async_work_sender,
580    cleanup_hooks,
581    external_ops_tracker,
582    deno_rt_native_addon_loader,
583    path,
584  ) = {
585    let mut op_state = op_state.borrow_mut();
586    let permissions =
587      op_state.borrow_mut::<deno_permissions::PermissionsContainer>();
588    let path = permissions.check_ffi(Cow::Borrowed(Path::new(path)))?;
589    let napi_state = op_state.borrow::<NapiState>();
590    (
591      op_state.borrow::<V8CrossThreadTaskSpawner>().clone(),
592      napi_state.env_cleanup_hooks.clone(),
593      op_state.external_ops_tracker.clone(),
594      op_state.try_borrow::<DenoRtNativeAddonLoaderRc>().cloned(),
595      path,
596    )
597  };
598
599  let napi_wrap_name = v8::String::new(scope, "napi_wrap").unwrap();
600  let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name));
601  let napi_wrap = v8::Global::new(scope, napi_wrap);
602
603  let type_tag_name = v8::String::new(scope, "type_tag").unwrap();
604  let type_tag = v8::Private::new(scope, Some(type_tag_name));
605  let type_tag = v8::Global::new(scope, type_tag);
606
607  let url_filename =
608    Url::from_file_path(&path).map_err(|_| NApiError::InvalidPath)?;
609  let env_shared =
610    EnvShared::new(napi_wrap, type_tag, format!("{url_filename}\0"));
611
612  let ctx = scope.get_current_context();
613  let mut env = Env::new(
614    unsafe { isolate.as_raw_isolate_ptr() },
615    v8::Global::new(scope, ctx),
616    v8::Global::new(scope, global),
617    v8::Global::new(scope, create_buffer),
618    v8::Global::new(scope, report_error),
619    async_work_sender,
620    cleanup_hooks,
621    external_ops_tracker,
622  );
623  env.shared = Box::into_raw(Box::new(env_shared));
624  // Track the EnvShared pointer so we can call instance data finalize
625  // callbacks when the runtime exits. Each op_napi_open call creates a
626  // fresh EnvShared, so entries are always unique.
627  op_state
628    .borrow_mut()
629    .borrow_mut::<NapiState>()
630    .env_shared_ptrs
631    .push(env.shared);
632  let env_ptr = Box::into_raw(Box::new(env)) as _;
633
634  #[cfg(unix)]
635  let flags = RTLD_LAZY;
636  #[cfg(not(unix))]
637  let flags = 0x00000008;
638
639  let real_path = match deno_rt_native_addon_loader {
640    Some(loader) => loader.load_and_resolve_path(&path)?,
641    None => Cow::Borrowed(path.as_ref()),
642  };
643
644  // SAFETY: opening a DLL calls dlopen
645  #[cfg(unix)]
646  let library = unsafe { Library::open(Some(real_path.as_ref()), flags) }?;
647
648  // SAFETY: opening a DLL calls dlopen
649  #[cfg(not(unix))]
650  let library = unsafe { Library::load_with_flags(real_path.as_ref(), flags) }?;
651
652  let maybe_module = MODULE_TO_REGISTER.with(|cell| {
653    let mut slot = cell.borrow_mut();
654    slot.take()
655  });
656
657  // The `module.exports` object.
658  let exports = v8::Object::new(scope);
659
660  let maybe_exports = if let Some(module_to_register) = maybe_module {
661    NAPI_LOADED_MODULES.write().insert(
662      real_path.to_path_buf(),
663      NapiModuleHandle(module_to_register),
664    );
665    // SAFETY: napi_register_module guarantees that `module_to_register` is valid.
666    let nm = unsafe { &*module_to_register };
667    assert_eq!(nm.nm_version, 1);
668    // SAFETY: we are going blind, calling the register function on the other side.
669    unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
670  } else if let Some(module_to_register) =
671    { NAPI_LOADED_MODULES.read().get(real_path.as_ref()).copied() }
672  {
673    // SAFETY: this originated from `napi_register_module`, so the
674    // pointer should still be valid.
675    let nm = unsafe { &*module_to_register.0 };
676    assert_eq!(nm.nm_version, 1);
677    // SAFETY: we are going blind, calling the register function on the other side.
678    unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
679  } else {
680    match unsafe {
681      library.get::<napi_register_module_v1>(b"napi_register_module_v1")
682    } {
683      Ok(init) => {
684        // Initializer callback.
685        // SAFETY: we are going blind, calling the register function on the other side.
686        unsafe { init(env_ptr, exports.into()) }
687      }
688      _ => {
689        return Err(NApiError::ModuleNotFound(path.into_owned()));
690      }
691    }
692  };
693
694  let exports = maybe_exports.unwrap_or(exports.into());
695
696  // NAPI addons can't be unloaded, so we're going to "forget" the library
697  // object so it lives till the program exit.
698  std::mem::forget(library);
699
700  Ok(exports)
701}
702
703#[allow(clippy::print_stdout)]
704pub fn print_linker_flags(name: &str) {
705  let symbols_path =
706    include_str!(concat!(env!("OUT_DIR"), "/napi_symbol_path.txt"));
707
708  #[cfg(target_os = "windows")]
709  println!("cargo:rustc-link-arg-bin={name}=/DEF:{}", symbols_path);
710
711  #[cfg(target_os = "macos")]
712  println!(
713    "cargo:rustc-link-arg-bin={name}=-Wl,-exported_symbols_list,{}",
714    symbols_path,
715  );
716
717  #[cfg(any(
718    target_os = "linux",
719    target_os = "freebsd",
720    target_os = "openbsd"
721  ))]
722  println!(
723    "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
724    symbols_path,
725  );
726
727  #[cfg(target_os = "android")]
728  println!(
729    "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
730    symbols_path,
731  );
732}