Skip to content

Commit a16910e

Browse files
authored
fix(ext/node): throw ERR_UNKNOWN_BUILTIN_MODULE for unknown node: builtins (#34766)
Node's CommonJS loader throws `ERR_UNKNOWN_BUILTIN_MODULE` (with message `No such built-in module: <id>`) when `require()` is given a `node:`-prefixed specifier that is not a public builtin. Two cases were handled incorrectly: 1. `require("node:unknown")` threw a generic `MODULE_NOT_FOUND` `Error` instead of `ERR_UNKNOWN_BUILTIN_MODULE`. 2. `require("node:internal/...")` resolved to Deno's internal polyfill modules. Node only exposes `internal/*` builtins under `--expose-internals`, so from userland these should be treated as unknown. This matches the existing `isBuiltin()` check and the public `builtinModules` list, both of which already exclude `internal/*`. This changes the `node:`-scheme branch of `Module._resolveFilename` to skip `internal/*` ids and to throw `internalErrors.ERR_UNKNOWN_BUILTIN_MODULE`. Bare (non-`node:`-prefixed) `require("internal/...")` is intentionally left unchanged. Enables the upstream test `parallel/test-require-node-prefix.js`.
1 parent 5b21175 commit a16910e

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

ext/node/polyfills/01_require.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,25 @@ Module._resolveLookupPaths = function (request, parent) {
13751375
};
13761376

13771377
Module._load = function (request, parent, isMain) {
1378+
// A `node:`-prefixed `require()` of a specifier that isn't a user-requirable
1379+
// builtin throws ERR_UNKNOWN_BUILTIN_MODULE. This covers unknown ids and
1380+
// `internal/*` modules (Node only exposes those under `--expose-internals`).
1381+
// `require.resolve()` goes through `_resolveFilename` directly and instead
1382+
// reports MODULE_NOT_FOUND, so this check lives here rather than there.
1383+
if (
1384+
typeof request === "string" &&
1385+
StringPrototypeStartsWith(request, "node:") &&
1386+
!(hookEntries.length > 0 && !insideResolveHook)
1387+
) {
1388+
const id = StringPrototypeSlice(request, 5);
1389+
if (
1390+
!(id in nativeModuleExports) ||
1391+
StringPrototypeStartsWith(id, "internal/")
1392+
) {
1393+
throw new internalErrors.ERR_UNKNOWN_BUILTIN_MODULE(request);
1394+
}
1395+
}
1396+
13781397
let relResolveCacheIdentifier;
13791398
if (parent) {
13801399
// Fast path for (lazy loaded) modules in the same directory. The indirect
@@ -1675,6 +1694,9 @@ Module._resolveFilename = function (
16751694
if (hookEntries.length > 0 && !insideResolveHook) {
16761695
return request;
16771696
}
1697+
// `require.resolve("node:unknown")` reports MODULE_NOT_FOUND, unlike
1698+
// `require("node:unknown")` which throws ERR_UNKNOWN_BUILTIN_MODULE from
1699+
// `Module._load`.
16781700
const err = new Error(`Cannot find module '${request}'`);
16791701
err.code = "MODULE_NOT_FOUND";
16801702
throw err;

tests/node_compat/config.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3111,6 +3111,7 @@
31113111
"parallel/test-require-invalid-package.js": {},
31123112
"parallel/test-require-json.js": {},
31133113
"parallel/test-require-long-path.js": {},
3114+
"parallel/test-require-node-prefix.js": {},
31143115
"parallel/test-require-nul.js": {},
31153116
"parallel/test-require-process.js": {},
31163117
"parallel/test-require-resolve.js": {},

0 commit comments

Comments
 (0)