Skip to content

Commit 687ffcc

Browse files
aduh95RafaelGSS
authored andcommitted
lib: reset RegExp statics before running user code
Fixes: #43740 Backport-PR-URL: #43741 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Matteo Collina <[email protected]> PR-URL: #44247 Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent 4700ee5 commit 687ffcc

11 files changed

+136
-12
lines changed

lib/fs.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const {
3737
ObjectDefineProperty,
3838
Promise,
3939
ReflectApply,
40-
RegExpPrototypeExec,
4140
SafeMap,
4241
SafeSet,
4342
String,
@@ -90,6 +89,7 @@ const {
9089
promisify: {
9190
custom: kCustomPromisifiedSymbol,
9291
},
92+
SideEffectFreeRegExpPrototypeExec,
9393
} = require('internal/util');
9494
const {
9595
constants: {
@@ -2424,7 +2424,7 @@ if (isWindows) {
24242424
// slash.
24252425
const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
24262426
splitRoot = function splitRoot(str) {
2427-
return RegExpPrototypeExec(splitRootRe, str)[0];
2427+
return SideEffectFreeRegExpPrototypeExec(splitRootRe, str)[0];
24282428
};
24292429
} else {
24302430
splitRoot = function splitRoot(str) {

lib/internal/main/run_main_module.js

+5
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
markBootstrapComplete
@@ -9,6 +11,9 @@ prepareMainThreadExecution(true);
911

1012
markBootstrapComplete();
1113

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

lib/internal/main/worker_thread.js

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
ArrayPrototypeSplice,
1010
ObjectDefineProperty,
1111
PromisePrototypeThen,
12+
RegExpPrototypeExec,
1213
globalThis: { Atomics },
1314
} = primordials;
1415

@@ -267,4 +268,7 @@ process._fatalException = workerOnGlobalUncaughtException;
267268

268269
markBootstrapComplete();
269270

271+
// Necessary to reset RegExp statics before user code runs.
272+
RegExpPrototypeExec(/^/, '');
273+
270274
port.start();

lib/internal/modules/esm/translators.js

+3-4
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

+3
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,

lib/internal/url.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
StringPrototypeCharCodeAt,
2424
StringPrototypeIncludes,
2525
StringPrototypeReplace,
26+
StringPrototypeReplaceAll,
2627
StringPrototypeSlice,
2728
StringPrototypeSplit,
2829
StringPrototypeStartsWith,
@@ -1424,8 +1425,6 @@ function urlToHttpOptions(url) {
14241425
return options;
14251426
}
14261427

1427-
const forwardSlashRegEx = /\//g;
1428-
14291428
function getPathFromURLWin32(url) {
14301429
const hostname = url.hostname;
14311430
let pathname = url.pathname;
@@ -1440,7 +1439,7 @@ function getPathFromURLWin32(url) {
14401439
}
14411440
}
14421441
}
1443-
pathname = pathname.replace(forwardSlashRegEx, '\\');
1442+
pathname = StringPrototypeReplaceAll(pathname, '/', '\\');
14441443
pathname = decodeURIComponent(pathname);
14451444
if (hostname !== '') {
14461445
// If hostname is set, then we have a UNC path

lib/internal/util.js

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
ArrayPrototypeSlice,
88
ArrayPrototypeSort,
99
Error,
10+
FunctionPrototypeCall,
1011
ObjectCreate,
1112
ObjectDefineProperties,
1213
ObjectDefineProperty,
@@ -543,6 +544,21 @@ function setOwnProperty(obj, key, value) {
543544
});
544545
}
545546

547+
let internalGlobal;
548+
function getInternalGlobal() {
549+
if (internalGlobal == null) {
550+
// Lazy-load to avoid a circular dependency.
551+
const { runInNewContext } = require('vm');
552+
internalGlobal = runInNewContext('this', undefined, { contextName: 'internal' });
553+
}
554+
return internalGlobal;
555+
}
556+
557+
function SideEffectFreeRegExpPrototypeExec(regex, string) {
558+
const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
559+
return FunctionPrototypeCall(RegExpFromAnotherRealm.prototype.exec, regex, string);
560+
}
561+
546562
module.exports = {
547563
assertCrypto,
548564
cachedResult,
@@ -557,6 +573,7 @@ module.exports = {
557573
filterDuplicateStrings,
558574
filterOwnProperties,
559575
getConstructorOf,
576+
getInternalGlobal,
560577
getSystemErrorMap,
561578
getSystemErrorName,
562579
isError,
@@ -567,6 +584,7 @@ module.exports = {
567584
normalizeEncoding,
568585
once,
569586
promisify,
587+
SideEffectFreeRegExpPrototypeExec,
570588
sleep,
571589
spliceOne,
572590
toUSVString,
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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/index.js';
5+
import assert from 'node:assert';
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["$'"], '');

test/parallel/test-vm-measure-memory-multi-context.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ expectExperimentalWarning();
2323
// We must hold on to the contexts here so that they
2424
// don't get GC'ed until the measurement is complete
2525
assert.strictEqual(arr.length, count);
26-
assertDetailedShape(result, count);
26+
assertDetailedShape(result, count + common.isWindows);
2727
}));
2828
}

test/parallel/test-vm-measure-memory.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ expectExperimentalWarning();
1515
vm.measureMemory({ execution: 'eager' })
1616
.then(common.mustCall(assertSummaryShape));
1717

18-
vm.measureMemory({ mode: 'detailed', execution: 'eager' })
19-
.then(common.mustCall(assertSingleDetailedShape));
18+
if (!common.isWindows) {
19+
vm.measureMemory({ mode: 'detailed', execution: 'eager' })
20+
.then(common.mustCall(assertSingleDetailedShape));
21+
}
2022

2123
vm.measureMemory({ mode: 'summary', execution: 'eager' })
2224
.then(common.mustCall(assertSummaryShape));

0 commit comments

Comments
 (0)