summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-05-26 06:40:17 +0200
committerGitHub <noreply@github.com>2023-05-26 10:10:17 +0530
commit512d5337c480a2a2704881d3fe1c40b6e0445cf0 (patch)
tree99b03358883223a69f48b2d0e5b9387f3abae517
parent7ae55e75d812edc93251357465f8d49fc2fb5d26 (diff)
fix(napi): clear currently registering module slot (#19249)
This commit fixes problem with loading N-API modules that use the "old" way of registration (using "napi_module_register" API). The slot was not cleared after loading modules, causing subsequent calls that use the new way of registration (using "napi_register_module_v1" API) to try and load the previous module. Ref https://github.com/denoland/deno/issues/16460 --------- Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
-rwxr-xr-x.github/workflows/ci.generate.ts4
-rw-r--r--.github/workflows/ci.yml6
-rw-r--r--cli/napi/env.rs5
-rw-r--r--ext/napi/lib.rs140
-rw-r--r--test_napi/.gitignore5
-rw-r--r--test_napi/common.js6
-rw-r--r--test_napi/init_test.js14
-rw-r--r--test_napi/module.c68
-rw-r--r--test_napi/tests/napi_tests.rs29
9 files changed, 186 insertions, 91 deletions
diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts
index c1f1eef6b..44fd3c47c 100755
--- a/.github/workflows/ci.generate.ts
+++ b/.github/workflows/ci.generate.ts
@@ -17,7 +17,7 @@ const Runners = (() => {
})();
// bump the number at the start when you want to purge the cache
const prCacheKeyPrefix =
- "29-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-";
+ "31-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-";
const installPkgsCommand =
"sudo apt-get install --no-install-recommends debootstrap clang-15 lld-15";
@@ -480,7 +480,7 @@ const ci = {
"~/.cargo/git/db",
].join("\n"),
key:
- "29-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}",
+ "31-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}",
},
},
{
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c78791ee5..97219b4d0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -293,7 +293,7 @@ jobs:
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
- key: '29-cargo-home-${{ matrix.os }}-${{ hashFiles(''Cargo.lock'') }}'
+ key: '31-cargo-home-${{ matrix.os }}-${{ hashFiles(''Cargo.lock'') }}'
if: '!(github.event_name == ''pull_request'' && matrix.skip_pr)'
- name: Restore cache build output (PR)
uses: actions/cache/restore@v3
@@ -305,7 +305,7 @@ jobs:
!./target/*/*.zip
!./target/*/*.tar.gz
key: never_saved
- restore-keys: '29-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-'
+ restore-keys: '31-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-'
- name: Apply and update mtime cache
if: '!(github.event_name == ''pull_request'' && matrix.skip_pr) && (!startsWith(github.ref, ''refs/tags/''))'
uses: ./.github/mtime_cache
@@ -589,7 +589,7 @@ jobs:
!./target/*/gn_out
!./target/*/*.zip
!./target/*/*.tar.gz
- key: '29-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
+ key: '31-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
publish-canary:
name: publish canary
runs-on: ubuntu-22.04
diff --git a/cli/napi/env.rs b/cli/napi/env.rs
index 114c43026..decdd59d6 100644
--- a/cli/napi/env.rs
+++ b/cli/napi/env.rs
@@ -136,9 +136,10 @@ fn node_api_get_module_file_name(
#[napi_sym::napi_sym]
fn napi_module_register(module: *const NapiModule) -> napi_status {
- MODULE.with(|cell| {
+ MODULE_TO_REGISTER.with(|cell| {
let mut slot = cell.borrow_mut();
- slot.replace(module);
+ let prev = slot.replace(module);
+ assert!(prev.is_none());
});
napi_ok
}
diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs
index 32f8c52fc..22d86e4a9 100644
--- a/ext/napi/lib.rs
+++ b/ext/napi/lib.rs
@@ -81,9 +81,7 @@ pub const napi_would_deadlock: napi_status = 21;
pub const NAPI_AUTO_LENGTH: usize = usize::MAX;
thread_local! {
- pub static MODULE: RefCell<Option<*const NapiModule>> = RefCell::new(None);
- pub static ASYNC_WORK_SENDER: RefCell<Option<mpsc::UnboundedSender<PendingNapiAsyncWork>>> = RefCell::new(None);
- pub static THREAD_SAFE_FN_SENDER: RefCell<Option<mpsc::UnboundedSender<ThreadSafeFunctionStatus>>> = RefCell::new(None);
+ pub static MODULE_TO_REGISTER: RefCell<Option<*const NapiModule>> = RefCell::new(None);
}
type napi_addon_register_func =
@@ -346,15 +344,6 @@ impl Env {
>,
tsfn_ref_counters: Arc<Mutex<ThreadsafeFunctionRefCounters>>,
) -> Self {
- let sc = sender.clone();
- ASYNC_WORK_SENDER.with(|s| {
- s.replace(Some(sc));
- });
- let ts = threadsafe_function_sender.clone();
- THREAD_SAFE_FN_SENDER.with(|s| {
- s.replace(Some(ts));
- });
-
Self {
isolate_ptr,
context: context.into_raw(),
@@ -559,7 +548,6 @@ where
{
let permissions = op_state.borrow_mut::<NP>();
permissions.check(Some(&PathBuf::from(&path)))?;
-
let (
async_work_sender,
tsfn_sender,
@@ -622,77 +610,67 @@ where
Err(e) => return Err(type_error(e.to_string())),
};
- MODULE.with(|cell| {
- let slot = *cell.borrow();
- let obj = match slot {
- Some(nm) => {
- // SAFETY: napi_register_module guarantees that `nm` is valid.
- let nm = unsafe { &*nm };
- assert_eq!(nm.nm_version, 1);
- // SAFETY: we are going blind, calling the register function on the other side.
- let maybe_exports = unsafe {
- (nm.nm_register_func)(
- env_ptr,
- std::mem::transmute::<v8::Local<v8::Value>, napi_value>(
- exports.into(),
- ),
- )
- };
-
- let exports = maybe_exports
- .as_ref()
- .map(|_| unsafe {
- // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
- // to a value, they have the same layout
- std::mem::transmute::<napi_value, v8::Local<v8::Value>>(
- maybe_exports,
- )
- })
- .unwrap_or_else(|| {
- // If the module didn't return anything, we use the exports object.
- exports.into()
- });
-
- Ok(serde_v8::Value { v8_value: exports })
- }
- None => {
- // Initializer callback.
- // SAFETY: we are going blind, calling the register function on the other side.
- unsafe {
- let init = library
- .get::<unsafe extern "C" fn(
- env: napi_env,
- exports: napi_value,
- ) -> napi_value>(b"napi_register_module_v1")
- .expect("napi_register_module_v1 not found");
- let maybe_exports = init(
- env_ptr,
- std::mem::transmute::<v8::Local<v8::Value>, napi_value>(
- exports.into(),
- ),
- );
-
- let exports = maybe_exports
- .as_ref()
- .map(|_| {
- // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
- // to a value, they have the same layout
- std::mem::transmute::<napi_value, v8::Local<v8::Value>>(
- maybe_exports,
- )
- })
- .unwrap_or_else(|| {
- // If the module didn't return anything, we use the exports object.
- exports.into()
- });
-
- Ok(serde_v8::Value { v8_value: exports })
- }
+ let maybe_module = MODULE_TO_REGISTER.with(|cell| {
+ let mut slot = cell.borrow_mut();
+ slot.take()
+ });
+
+ if let Some(module_to_register) = maybe_module {
+ // SAFETY: napi_register_module guarantees that `module_to_register` is valid.
+ let nm = unsafe { &*module_to_register };
+ assert_eq!(nm.nm_version, 1);
+ // SAFETY: we are going blind, calling the register function on the other side.
+ let maybe_exports = unsafe {
+ (nm.nm_register_func)(
+ env_ptr,
+ std::mem::transmute::<v8::Local<v8::Value>, napi_value>(exports.into()),
+ )
+ };
+
+ let exports = if maybe_exports.is_some() {
+ // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
+ // to a value, they have the same layout
+ unsafe {
+ std::mem::transmute::<napi_value, v8::Local<v8::Value>>(maybe_exports)
}
+ } else {
+ exports.into()
};
+
// NAPI addons can't be unloaded, so we're going to "forget" the library
// object so it lives till the program exit.
std::mem::forget(library);
- obj
- })
+ return Ok(serde_v8::Value { v8_value: exports });
+ }
+
+ // Initializer callback.
+ // SAFETY: we are going blind, calling the register function on the other side.
+
+ let maybe_exports = unsafe {
+ let init = library
+ .get::<unsafe extern "C" fn(
+ env: napi_env,
+ exports: napi_value,
+ ) -> napi_value>(b"napi_register_module_v1")
+ .expect("napi_register_module_v1 not found");
+ init(
+ env_ptr,
+ std::mem::transmute::<v8::Local<v8::Value>, napi_value>(exports.into()),
+ )
+ };
+
+ let exports = if maybe_exports.is_some() {
+ // SAFETY: v8::Local is a pointer to a value and napi_value is also a pointer
+ // to a value, they have the same layout
+ unsafe {
+ std::mem::transmute::<napi_value, v8::Local<v8::Value>>(maybe_exports)
+ }
+ } else {
+ exports.into()
+ };
+
+ // NAPI addons can't be unloaded, so we're going to "forget" the library
+ // object so it lives till the program exit.
+ std::mem::forget(library);
+ Ok(serde_v8::Value { v8_value: exports })
}
diff --git a/test_napi/.gitignore b/test_napi/.gitignore
index 6fdcc4a66..54de1ef34 100644
--- a/test_napi/.gitignore
+++ b/test_napi/.gitignore
@@ -1,4 +1,7 @@
package-lock.json
# Test generated artifacts
-.swc \ No newline at end of file
+.swc
+*.dylib
+*.so
+*.dll
diff --git a/test_napi/common.js b/test_napi/common.js
index 09378918f..ce9b2544b 100644
--- a/test_napi/common.js
+++ b/test_napi/common.js
@@ -9,17 +9,19 @@ export {
export { fromFileUrl } from "../test_util/std/path/mod.ts";
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
-const [libPrefix, libSuffix] = {
+export const [libPrefix, libSuffix] = {
darwin: ["lib", "dylib"],
linux: ["lib", "so"],
windows: ["", "dll"],
}[Deno.build.os];
+const ops = Deno[Deno.internal].core.ops;
+
export function loadTestLibrary() {
const specifier = `${targetDir}/${libPrefix}test_napi.${libSuffix}`;
// Internal, used in ext/node
- return Deno[Deno.internal].core.ops.op_napi_open(specifier, {
+ return ops.op_napi_open(specifier, {
Buffer: {},
});
}
diff --git a/test_napi/init_test.js b/test_napi/init_test.js
new file mode 100644
index 000000000..633fdbb61
--- /dev/null
+++ b/test_napi/init_test.js
@@ -0,0 +1,14 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import { assert, libSuffix } from "./common.js";
+
+const ops = Deno[Deno.internal].core.ops;
+
+Deno.test("ctr initialization (napi_module_register)", {
+ ignore: Deno.build.os == "windows",
+}, function () {
+ const path = new URL(`./module.${libSuffix}`, import.meta.url).pathname;
+ const obj = ops.op_napi_open(path, {});
+ assert(obj != null);
+ assert(typeof obj === "object");
+});
diff --git a/test_napi/module.c b/test_napi/module.c
new file mode 100644
index 000000000..4548aa37f
--- /dev/null
+++ b/test_napi/module.c
@@ -0,0 +1,68 @@
+typedef struct napi_module {
+ int nm_version;
+ unsigned int nm_flags;
+ const char* nm_filename;
+ void* nm_register_func;
+ const char* nm_modname;
+ void* nm_priv;
+ void* reserved[4];
+} napi_module;
+
+#ifdef _WIN32
+#define NAPI_EXTERN __declspec(dllexport)
+#define NAPI_CDECL __cdecl
+#else
+#define NAPI_EXTERN __attribute__((visibility("default")))
+#define NAPI_CDECL
+#endif
+
+NAPI_EXTERN void NAPI_CDECL
+napi_module_register(napi_module* mod);
+
+#if defined(_MSC_VER)
+#if defined(__cplusplus)
+#define NAPI_C_CTOR(fn) \
+ static void NAPI_CDECL fn(void); \
+ namespace { \
+ struct fn##_ { \
+ fn##_() { fn(); } \
+ } fn##_v_; \
+ } \
+ static void NAPI_CDECL fn(void)
+#else // !defined(__cplusplus)
+#pragma section(".CRT$XCU", read)
+// The NAPI_C_CTOR macro defines a function fn that is called during CRT
+// initialization.
+// C does not support dynamic initialization of static variables and this code
+// simulates C++ behavior. Exporting the function pointer prevents it from being
+// optimized. See for details:
+// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
+#define NAPI_C_CTOR(fn) \
+ static void NAPI_CDECL fn(void); \
+ __declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \
+ fn; \
+ static void NAPI_CDECL fn(void)
+#endif // defined(__cplusplus)
+#else
+#define NAPI_C_CTOR(fn) \
+ static void fn(void) __attribute__((constructor)); \
+ static void fn(void)
+#endif
+
+#define NAPI_MODULE_TEST(modname, regfunc) \
+ static napi_module _module = { \
+ 1, \
+ 0, \
+ __FILE__, \
+ regfunc, \
+ #modname, \
+ 0, \
+ {0}, \
+ }; \
+ NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \
+
+void* init(void* env __attribute__((unused)), void* exports) {
+ return exports;
+}
+
+NAPI_MODULE_TEST(TEST_NAPI_MODULE_NAME, init)
diff --git a/test_napi/tests/napi_tests.rs b/test_napi/tests/napi_tests.rs
index 3e3989436..c3ce285e0 100644
--- a/test_napi/tests/napi_tests.rs
+++ b/test_napi/tests/napi_tests.rs
@@ -18,6 +18,35 @@ fn build() {
}
let build_plugin_output = build_plugin.output().unwrap();
assert!(build_plugin_output.status.success());
+
+ // cc module.c -undefined dynamic_lookup -shared -Wl,-no_fixup_chains -dynamic -o module.dylib
+ #[cfg(not(target_os = "windows"))]
+ {
+ let out = if cfg!(target_os = "macos") {
+ "module.dylib"
+ } else {
+ "module.so"
+ };
+
+ let mut cc = Command::new("cc");
+
+ #[cfg(not(target_os = "macos"))]
+ let c_module = cc.arg("module.c").arg("-shared").arg("-o").arg(out);
+
+ #[cfg(target_os = "macos")]
+ let c_module = {
+ cc.arg("module.c")
+ .arg("-undefined")
+ .arg("dynamic_lookup")
+ .arg("-shared")
+ .arg("-Wl,-no_fixup_chains")
+ .arg("-dynamic")
+ .arg("-o")
+ .arg(out)
+ };
+ let c_module_output = c_module.output().unwrap();
+ assert!(c_module_output.status.success());
+ }
}
#[test]