diff --git a/lib/internal/main/run_main_module.js b/lib/internal/main/run_main_module.js index ca5d1122c59d94..97313cdeb38f68 100644 --- a/lib/internal/main/run_main_module.js +++ b/lib/internal/main/run_main_module.js @@ -1,5 +1,7 @@ 'use strict'; +const { RegExpPrototypeExec } = primordials; + const { prepareMainThreadExecution } = require('internal/bootstrap/pre_execution'); @@ -8,6 +10,9 @@ prepareMainThreadExecution(true); markBootstrapComplete(); +// Necessary to reset RegExp statics before user code runs. +RegExpPrototypeExec(/^/, ''); + // Note: this loads the module through the ESM loader if the module is // determined to be an ES module. This hangs from the CJS module loader // because we currently allow monkey-patching of the module loaders diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 8fb3c96f8dc4c5..1211974ba9cf9d 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -11,7 +11,7 @@ const { SafeArrayIterator, SafeMap, SafeSet, - StringPrototypeReplace, + StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeStartsWith, SyntaxErrorPrototype, @@ -144,14 +144,13 @@ function enrichCJSError(err, content, filename) { // Strategy for loading a node-style CommonJS module const isWindows = process.platform === 'win32'; -const winSepRegEx = /\//g; translators.set('commonjs', async function commonjsStrategy(url, source, isMain) { debug(`Translating CJSModule ${url}`); let filename = internalURLModule.fileURLToPath(new URL(url)); if (isWindows) - filename = StringPrototypeReplace(filename, winSepRegEx, '\\'); + filename = StringPrototypeReplaceAll(filename, '/', '\\'); if (!cjsParse) await initCJSParse(); const { module, exportNames } = cjsPreparseModuleExports(filename); @@ -274,7 +273,7 @@ translators.set('json', async function jsonStrategy(url, source) { let module; if (pathname) { modulePath = isWindows ? - StringPrototypeReplace(pathname, winSepRegEx, '\\') : pathname; + StringPrototypeReplaceAll(pathname, '/', '\\') : pathname; module = CJSModule._cache[modulePath]; if (module && module.loaded) { const exports = module.exports; diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 4a8e51f694f7e6..844d18d291c6a4 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -1,6 +1,7 @@ 'use strict'; const { + RegExpPrototypeExec, globalThis, } = primordials; @@ -45,6 +46,7 @@ function evalModule(source, print) { } const { loadESM } = require('internal/process/esm_loader'); const { handleMainPromise } = require('internal/modules/run_main'); + RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs. return handleMainPromise(loadESM((loader) => loader.eval(source))); } @@ -72,6 +74,7 @@ function evalScript(name, body, breakFirstLine, print) { return (main) => main(); `; globalThis.__filename = name; + RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs. const result = module._compile(script, `${name}-wrapper`)(() => require('vm').runInThisContext(body, { filename: name, diff --git a/test/parallel/test-startup-empty-regexp-statics.js b/test/parallel/test-startup-empty-regexp-statics.js new file mode 100644 index 00000000000000..80f6bef5a5a08e --- /dev/null +++ b/test/parallel/test-startup-empty-regexp-statics.js @@ -0,0 +1,68 @@ +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const { spawnSync, spawn } = require('node:child_process'); + +assert.strictEqual(RegExp.$_, ''); +assert.strictEqual(RegExp.$0, undefined); +assert.strictEqual(RegExp.$1, ''); +assert.strictEqual(RegExp.$2, ''); +assert.strictEqual(RegExp.$3, ''); +assert.strictEqual(RegExp.$4, ''); +assert.strictEqual(RegExp.$5, ''); +assert.strictEqual(RegExp.$6, ''); +assert.strictEqual(RegExp.$7, ''); +assert.strictEqual(RegExp.$8, ''); +assert.strictEqual(RegExp.$9, ''); +assert.strictEqual(RegExp.input, ''); +assert.strictEqual(RegExp.lastMatch, ''); +assert.strictEqual(RegExp.lastParen, ''); +assert.strictEqual(RegExp.leftContext, ''); +assert.strictEqual(RegExp.rightContext, ''); +assert.strictEqual(RegExp['$&'], ''); +assert.strictEqual(RegExp['$`'], ''); +assert.strictEqual(RegExp['$+'], ''); +assert.strictEqual(RegExp["$'"], ''); + +const allRegExpStatics = + 'RegExp.$_ + RegExp["$&"] + RegExp["$`"] + RegExp["$+"] + RegExp["$\'"] + ' + + 'RegExp.input + RegExp.lastMatch + RegExp.lastParen + ' + + 'RegExp.leftContext + RegExp.rightContext + ' + + Array.from({ length: 10 }, (_, i) => `RegExp.$${i}`).join(' + '); + +{ + const child = spawnSync(process.execPath, + [ '-p', allRegExpStatics ], + { stdio: ['inherit', 'pipe', 'inherit'] }); + assert.match(child.stdout.toString(), /^undefined\r?\n$/); + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); +} + +{ + const child = spawnSync(process.execPath, + [ '-e', `console.log(${allRegExpStatics})`, '--input-type=module' ], + { stdio: ['inherit', 'pipe', 'inherit'] }); + assert.match(child.stdout.toString(), /^undefined\r?\n$/); + assert.strictEqual(child.status, 0); + assert.strictEqual(child.signal, null); +} + +{ + const child = spawn(process.execPath, [], { stdio: ['pipe', 'pipe', 'inherit'], encoding: 'utf8' }); + + let stdout = ''; + child.stdout.on('data', (chunk) => { + stdout += chunk; + }); + + child.on('exit', common.mustCall((status, signal) => { + assert.match(stdout, /^undefined\r?\n$/); + assert.strictEqual(status, 0); + assert.strictEqual(signal, null); + })); + child.on('error', common.mustNotCall()); + + child.stdin.end(`console.log(${allRegExpStatics});\n`); +} diff --git a/test/parallel/test-startup-empty-regexp-statics.mjs b/test/parallel/test-startup-empty-regexp-statics.mjs new file mode 100644 index 00000000000000..005fa86c6579f0 --- /dev/null +++ b/test/parallel/test-startup-empty-regexp-statics.mjs @@ -0,0 +1,30 @@ +// We must load the CJS version here because the ESM wrapper call `hasIPv6` +// which compiles a RegEx. +// eslint-disable-next-line node-core/require-common-first +import common from '../common/index.js'; + +import assert from 'node:assert'; + +// TODO(aduh95): make this test pass on Windows. +if (common.isWindows) common.skip('Test fails on Windows'); + +assert.strictEqual(RegExp.$_, ''); +assert.strictEqual(RegExp.$0, undefined); +assert.strictEqual(RegExp.$1, ''); +assert.strictEqual(RegExp.$2, ''); +assert.strictEqual(RegExp.$3, ''); +assert.strictEqual(RegExp.$4, ''); +assert.strictEqual(RegExp.$5, ''); +assert.strictEqual(RegExp.$6, ''); +assert.strictEqual(RegExp.$7, ''); +assert.strictEqual(RegExp.$8, ''); +assert.strictEqual(RegExp.$9, ''); +assert.strictEqual(RegExp.input, ''); +assert.strictEqual(RegExp.lastMatch, ''); +assert.strictEqual(RegExp.lastParen, ''); +assert.strictEqual(RegExp.leftContext, ''); +assert.strictEqual(RegExp.rightContext, ''); +assert.strictEqual(RegExp['$&'], ''); +assert.strictEqual(RegExp['$`'], ''); +assert.strictEqual(RegExp['$+'], ''); +assert.strictEqual(RegExp["$'"], '');