Skip to content

Commit b0b8253

Browse files
committed
lib: reset RegExp statics before running user code
Fixes: #43740
1 parent 98ade2d commit b0b8253

File tree

5 files changed

+109
-4
lines changed

5 files changed

+109
-4
lines changed

lib/internal/main/run_main_module.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const { RegExpPrototypeExec } = primordials;
4+
35
const {
46
prepareMainThreadExecution
57
} = require('internal/bootstrap/pre_execution');
@@ -8,6 +10,9 @@ prepareMainThreadExecution(true);
810

911
markBootstrapComplete();
1012

13+
// Necessary to reset RegExp statics before user code runs.
14+
RegExpPrototypeExec(/^/, '');
15+
1116
// Note: this loads the module through the ESM loader if the module is
1217
// determined to be an ES module. This hangs from the CJS module loader
1318
// because we currently allow monkey-patching of the module loaders

lib/internal/modules/esm/translators.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {
1111
SafeArrayIterator,
1212
SafeMap,
1313
SafeSet,
14-
StringPrototypeReplace,
14+
StringPrototypeReplaceAll,
1515
StringPrototypeSlice,
1616
StringPrototypeStartsWith,
1717
SyntaxErrorPrototype,
@@ -144,14 +144,13 @@ function enrichCJSError(err, content, filename) {
144144

145145
// Strategy for loading a node-style CommonJS module
146146
const isWindows = process.platform === 'win32';
147-
const winSepRegEx = /\//g;
148147
translators.set('commonjs', async function commonjsStrategy(url, source,
149148
isMain) {
150149
debug(`Translating CJSModule ${url}`);
151150

152151
let filename = internalURLModule.fileURLToPath(new URL(url));
153152
if (isWindows)
154-
filename = StringPrototypeReplace(filename, winSepRegEx, '\\');
153+
filename = StringPrototypeReplaceAll(filename, '/', '\\');
155154

156155
if (!cjsParse) await initCJSParse();
157156
const { module, exportNames } = cjsPreparseModuleExports(filename);
@@ -274,7 +273,7 @@ translators.set('json', async function jsonStrategy(url, source) {
274273
let module;
275274
if (pathname) {
276275
modulePath = isWindows ?
277-
StringPrototypeReplace(pathname, winSepRegEx, '\\') : pathname;
276+
StringPrototypeReplaceAll(pathname, '/', '\\') : pathname;
278277
module = CJSModule._cache[modulePath];
279278
if (module && module.loaded) {
280279
const exports = module.exports;

lib/internal/process/execution.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
RegExpPrototypeExec,
45
globalThis,
56
} = primordials;
67

@@ -45,6 +46,7 @@ function evalModule(source, print) {
4546
}
4647
const { loadESM } = require('internal/process/esm_loader');
4748
const { handleMainPromise } = require('internal/modules/run_main');
49+
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
4850
return handleMainPromise(loadESM((loader) => loader.eval(source)));
4951
}
5052

@@ -72,6 +74,7 @@ function evalScript(name, body, breakFirstLine, print) {
7274
return (main) => main();
7375
`;
7476
globalThis.__filename = name;
77+
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
7578
const result = module._compile(script, `${name}-wrapper`)(() =>
7679
require('vm').runInThisContext(body, {
7780
filename: name,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('node:assert');
5+
const { spawnSync, spawn } = require('node:child_process');
6+
7+
assert.strictEqual(RegExp.$_, '');
8+
assert.strictEqual(RegExp.$0, undefined);
9+
assert.strictEqual(RegExp.$1, '');
10+
assert.strictEqual(RegExp.$2, '');
11+
assert.strictEqual(RegExp.$3, '');
12+
assert.strictEqual(RegExp.$4, '');
13+
assert.strictEqual(RegExp.$5, '');
14+
assert.strictEqual(RegExp.$6, '');
15+
assert.strictEqual(RegExp.$7, '');
16+
assert.strictEqual(RegExp.$8, '');
17+
assert.strictEqual(RegExp.$9, '');
18+
assert.strictEqual(RegExp.input, '');
19+
assert.strictEqual(RegExp.lastMatch, '');
20+
assert.strictEqual(RegExp.lastParen, '');
21+
assert.strictEqual(RegExp.leftContext, '');
22+
assert.strictEqual(RegExp.rightContext, '');
23+
assert.strictEqual(RegExp['$&'], '');
24+
assert.strictEqual(RegExp['$`'], '');
25+
assert.strictEqual(RegExp['$+'], '');
26+
assert.strictEqual(RegExp["$'"], '');
27+
28+
const allRegExpStatics =
29+
'RegExp.$_ + RegExp["$&"] + RegExp["$`"] + RegExp["$+"] + RegExp["$\'"] + ' +
30+
'RegExp.input + RegExp.lastMatch + RegExp.lastParen + ' +
31+
'RegExp.leftContext + RegExp.rightContext + ' +
32+
Array.from({ length: 10 }, (_, i) => `RegExp.$${i}`).join(' + ');
33+
34+
{
35+
const child = spawnSync(process.execPath,
36+
[ '-p', allRegExpStatics ],
37+
{ stdio: ['inherit', 'pipe', 'inherit'] });
38+
assert.match(child.stdout.toString(), /^undefined\r?\n$/);
39+
assert.strictEqual(child.status, 0);
40+
assert.strictEqual(child.signal, null);
41+
}
42+
43+
{
44+
const child = spawnSync(process.execPath,
45+
[ '-e', `console.log(${allRegExpStatics})`, '--input-type=module' ],
46+
{ stdio: ['inherit', 'pipe', 'inherit'] });
47+
assert.match(child.stdout.toString(), /^undefined\r?\n$/);
48+
assert.strictEqual(child.status, 0);
49+
assert.strictEqual(child.signal, null);
50+
}
51+
52+
{
53+
const child = spawn(process.execPath, [], { stdio: ['pipe', 'pipe', 'inherit'], encoding: 'utf8' });
54+
55+
let stdout = '';
56+
child.stdout.on('data', (chunk) => {
57+
stdout += chunk;
58+
});
59+
60+
child.on('exit', common.mustCall((status, signal) => {
61+
assert.match(stdout, /^undefined\r?\n$/);
62+
assert.strictEqual(status, 0);
63+
assert.strictEqual(signal, null);
64+
}));
65+
child.on('error', common.mustNotCall());
66+
67+
child.stdin.end(`console.log(${allRegExpStatics});\n`);
68+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// We must load the CJS version here because the ESM wrapper call `hasIPv6`
2+
// which compiles a RegEx.
3+
// eslint-disable-next-line node-core/require-common-first
4+
import common from '../common/index.js';
5+
6+
import assert from 'node:assert';
7+
8+
// TODO(aduh95): make this test pass on Windows.
9+
if (common.isWindows) common.skip('Test fails on Windows');
10+
11+
assert.strictEqual(RegExp.$_, '');
12+
assert.strictEqual(RegExp.$0, undefined);
13+
assert.strictEqual(RegExp.$1, '');
14+
assert.strictEqual(RegExp.$2, '');
15+
assert.strictEqual(RegExp.$3, '');
16+
assert.strictEqual(RegExp.$4, '');
17+
assert.strictEqual(RegExp.$5, '');
18+
assert.strictEqual(RegExp.$6, '');
19+
assert.strictEqual(RegExp.$7, '');
20+
assert.strictEqual(RegExp.$8, '');
21+
assert.strictEqual(RegExp.$9, '');
22+
assert.strictEqual(RegExp.input, '');
23+
assert.strictEqual(RegExp.lastMatch, '');
24+
assert.strictEqual(RegExp.lastParen, '');
25+
assert.strictEqual(RegExp.leftContext, '');
26+
assert.strictEqual(RegExp.rightContext, '');
27+
assert.strictEqual(RegExp['$&'], '');
28+
assert.strictEqual(RegExp['$`'], '');
29+
assert.strictEqual(RegExp['$+'], '');
30+
assert.strictEqual(RegExp["$'"], '');

0 commit comments

Comments
 (0)