Skip to content

fix(napi): report a clear error for legacy V8/nan native addons#34683

Merged
littledivy merged 1 commit into
mainfrom
orch/divybot-403
Jun 2, 2026
Merged

fix(napi): report a clear error for legacy V8/nan native addons#34683
littledivy merged 1 commit into
mainfrom
orch/divybot-403

Conversation

@divybot

@divybot divybot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Problem

Importing a legacy V8/nan native addon such as node-rdkafka crashes the whole process with a cryptic, uncatchable message:

dyld[...]: missing symbol called

Steps to reproduce (from #26656):

  1. pnpm install node-rdkafka
  2. main.ts: import 'node-rdkafka'
  3. deno run --allow-all main.ts

Root cause

node-rdkafka is a legacy NODE_MODULE (V8/nan) addon, not a Node-API (N-API) addon. Its .node registers itself through the node_module_register symbol from a static constructor that runs at dlopen time. Deno only exports the Node-API (napi_*) symbols (plus a libuv shim), so node_module_register was undefined.

On macOS native addons link with -undefined dynamic_lookup, so the missing node_module_register becomes a dyld lazy-bind stub that aborts the entire process with missing symbol called the 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 (mirrors napi_module_register, storing the module descriptor) so the addon's constructor resolves and dlopen succeeds instead of aborting.

  • In op_napi_open, inspect nm_version: the Node-API version is 1; legacy addons use NODE_MODULE_VERSION (e.g. 127). Anything other than 1 is rejected with a descriptive TypeError:

    Cannot load native addon at <path>: it was built against the legacy Node.js
    native addon API (NODE_MODULE / nan), which Deno does not support. Only
    Node-API (N-API) addons are supported.
    

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 TypeError with the message above instead of the dyld abort.

Adds a tests/napi/module_legacy.c fixture (a minimal legacy addon registering via node_module_register with nm_version != 1) and an init_test.js case asserting it is rejected with the new error. The full napi_tests integration suite passes.

Closes #26656

Closes denoland/divybot#403

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
littledivy merged commit a7e40b0 into main Jun 2, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-403 branch June 2, 2026 04:50
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 &lt;path&gt;: 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to load node-rdkafka

2 participants