fix(napi): report a clear error for legacy V8/nan native addons#34683
Merged
Conversation
Legacy `NODE_MODULE` (V8/nan) native addons such as `node-rdkafka` register themselves through the `node_module_register` symbol from a static constructor at `dlopen` time. Deno only exports the Node-API (`napi_*`) symbols, so `node_module_register` was undefined. On macOS, addons link with `-undefined dynamic_lookup`, which turns the missing symbol into a dyld lazy-bind stub that aborts the whole process with a cryptic `dyld[...]: missing symbol called` before Deno can report anything. Deno does not support the legacy V8 native addon ABI (only Node-API is supported), and full support would require exporting all of V8's C++ API plus reimplementing the `node::` helpers. Instead, export `node_module_register` so the addon's constructor resolves and `dlopen` succeeds, then have `op_napi_open` inspect `nm_version`: anything other than the Node-API version (1) is a legacy addon and is rejected with a clear, catchable `TypeError` explaining the situation. Adds a `module_legacy.c` test fixture and an `init_test.js` case asserting the legacy module is rejected with the new error. Closes #26656 Co-Authored-By: Divy Srivastava <[email protected]>
littledivy
approved these changes
Jun 2, 2026
This was referenced Jun 2, 2026
littledivy
added a commit
that referenced
this pull request
Jun 2, 2026
…4695) Native addons built against the legacy Node.js `NODE_MODULE` / `nan` ABI (such as `better-sqlite3`) link against V8 C++ internals that Deno does not expose. Since #34683, Deno rejects them with a clear `UnsupportedLegacyAddon` error instead of crashing — but the error offered no guidance on what to do next. This adds an actionable fix-suggestion in `runtime/fmt_errors.rs` (`get_suggestions_for_terminal_errors`) that fires on the legacy-addon error and points users at Node-API alternatives: - **`better-sqlite3`** is special-cased (the error message includes the `.node` path) to recommend the built-in `node:sqlite` module and the `npm:libsql` / `npm:@libsql/client` packages (the latter expose a `better-sqlite3`-compatible API). - Any other legacy addon gets a generic hint to switch to / migrate the addon to Node-API. ### Why not actually run `better-sqlite3`? Running these addons would require Deno to expose V8's C++ API and node:: helper symbols and to be ABI-compatible with the V8 the addon was built against. Deno's `rusty_v8` is several major V8 versions ahead of the V8 that prebuilt `better-sqlite3` binaries target (Node 20, V8 11.x), so exported symbols would not be ABI-compatible. That is a large, separate effort; this PR improves the failure UX in the meantime, directly addressing the most-reported package in the issue. ### Test `tests/specs/run/node_addon_legacy_suggestion/` exercises both the `better-sqlite3`-specific and the generic suggestion by throwing the error string (no native build needed, mirroring the existing `node_addon_bindings_suggestion` spec test). ``` cargo test --test specs -- run::node_addon_legacy_suggestion ``` Refs #26034 Closes denoland/divybot#408 Co-authored-by: divybot <[email protected]> Co-authored-by: Divy Srivastava <[email protected]>
littledivy
added a commit
that referenced
this pull request
Jun 2, 2026
…exe (#34696) ## Problem Loading uWebSockets.js (and similar prebuilt native addons) on **Windows** fails with a cryptic error: ``` error: Uncaught (in promise) Error: This version of uWS.js supports only Node.js LTS versions 16, 18 and 20 ... TypeError: LoadLibraryExW failed ``` The "supports only Node.js LTS" text is uWS's own fallback message; the real error is `LoadLibraryExW failed`, which tells the user nothing actionable. ## Root cause uWS's prebuilt Windows `.node` (`uws_win32_x64_{108,115,127}.node`) is **not a Node-API addon**. Inspecting its PE import table (`objdump -p`) shows a **regular, non delay-load import of `node.exe`** that pulls in: - raw **V8 C++ ABI** symbols (`v8::Object::New`, `HandleScope`, `FunctionTemplate`, …) - **Node.js internals** (`node::MakeCallback`, `node::AddEnvironmentCleanupHook`, `node::GetCurrentEventLoop`) - **libuv** (`uv_run`, `uv_async_init`, …) and **zlib** The Windows loader resolves an import of `node.exe` by module name at `LoadLibraryExW` time, so it only succeeds when the host process is literally named `node.exe`. Loaded into `deno.exe` (or any other host — this is also why it breaks in Node SEA, Bun and Electron), the import can't be satisfied and the load fails. Deno cannot make such an addon work: it doesn't (and can't) export V8's C++ ABI, Node's internal C++ API, or a real libuv event loop. The only correct outcome is a clear diagnostic. ## Fix Mirrors the legacy V8/nan addon diagnostic added in #34683. - New dependency-free, bounds-checked PE import-table reader in `ext/napi/pe.rs`. `imports_node_executable()` parses the **regular** import directory and checks for a `node.exe` import (case-insensitive). It is unit-tested with a synthetic PE builder and was validated against the real uWS binaries (output matches `objdump` exactly). - New `NApiError::UnsupportedNodeBinaryAddon`. On Windows, when `Library::load_with_flags` fails, the addon is inspected; if it imports `node.exe` directly, Deno now reports: > Cannot load native addon at <path>: it links directly against the Node.js binary (node.exe) and relies on the V8 C++ ABI, Node.js internals and/or libuv exported by that executable, none of which Deno provides. Only Node-API (N-API) addons are supported. ... **Delay-loaded** `node.exe` imports (the node-gyp default, redirected to the host at runtime via `win_delay_load_hook`) are intentionally ignored — those resolve against the host's exported N-API symbols and work fine in Deno. ## Testing - `cargo test -p deno_napi --lib pe::` — 4 unit tests (real-binary-shaped fixtures + malformed input). - `cargo clippy -p deno_napi --all-targets` and `cargo clippy -p deno_napi --target x86_64-pc-windows-msvc` both clean (the new load-failure arm is Windows-only). Refs #25956 Closes denoland/divybot#409 --------- Co-authored-by: divybot <[email protected]> Co-authored-by: Divy Srivastava <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Importing a legacy V8/nan native addon such as
node-rdkafkacrashes the whole process with a cryptic, uncatchable message:Steps to reproduce (from #26656):
pnpm install node-rdkafkamain.ts:import 'node-rdkafka'deno run --allow-all main.tsRoot cause
node-rdkafkais a legacyNODE_MODULE(V8/nan) addon, not a Node-API (N-API) addon. Its.noderegisters itself through thenode_module_registersymbol from a static constructor that runs atdlopentime. Deno only exports the Node-API (napi_*) symbols (plus a libuv shim), sonode_module_registerwas undefined.On macOS native addons link with
-undefined dynamic_lookup, so the missingnode_module_registerbecomes a dyld lazy-bind stub that aborts the entire process withmissing symbol calledthe moment the addon's constructor calls it — before Deno ever gets a chance to report a useful error.Fix
Deno does not support the legacy V8 native addon ABI (only Node-API is supported), and full support would require exporting all of V8's public C++ API plus reimplementing the
node::runtime helpers (node::MakeCallback,node::Buffer::New, …). That is out of scope.Instead, this turns the hard crash into a clear, catchable error:
Export
node_module_register(mirrorsnapi_module_register, storing the module descriptor) so the addon's constructor resolves anddlopensucceeds instead of aborting.In
op_napi_open, inspectnm_version: the Node-API version is1; legacy addons useNODE_MODULE_VERSION(e.g.127). Anything other than1is rejected with a descriptiveTypeError:This replaces the two
assert_eq!(nm.nm_version, 1)panics with the graceful error.Testing
With this change, the repro now produces a catchable
TypeErrorwith the message above instead of the dyld abort.Adds a
tests/napi/module_legacy.cfixture (a minimal legacy addon registering vianode_module_registerwithnm_version != 1) and aninit_test.jscase asserting it is rejected with the new error. The fullnapi_testsintegration suite passes.Closes #26656
Closes denoland/divybot#403