Skip to content

Commit a2d55ae

Browse files
aduh95guangwong
authored andcommitted
lib: reset RegExp statics before running user code
Fixes: nodejs/node#43740 PR-URL: nodejs/node#43741 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 67cfeef commit a2d55ae

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,
@@ -89,6 +88,7 @@ const {
8988
promisify: {
9089
custom: kCustomPromisifiedSymbol,
9190
},
91+
SideEffectFreeRegExpPrototypeExec,
9292
} = require('internal/util');
9393
const {
9494
constants: {
@@ -2410,7 +2410,7 @@ if (isWindows) {
24102410
// slash.
24112411
const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
24122412
splitRoot = function splitRoot(str) {
2413-
return RegExpPrototypeExec(splitRootRe, str)[0];
2413+
return SideEffectFreeRegExpPrototypeExec(splitRootRe, str)[0];
24142414
};
24152415
} else {
24162416
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
} = 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/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

@@ -266,4 +267,7 @@ process._fatalException = workerOnGlobalUncaughtException;
266267

267268
markBootstrapComplete();
268269

270+
// Necessary to reset RegExp statics before user code runs.
271+
RegExpPrototypeExec(/^/, '');
272+
269273
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,
@@ -1372,8 +1373,6 @@ function urlToHttpOptions(url) {
13721373
return options;
13731374
}
13741375

1375-
const forwardSlashRegEx = /\//g;
1376-
13771376
function getPathFromURLWin32(url) {
13781377
const hostname = url.hostname;
13791378
let pathname = url.pathname;
@@ -1388,7 +1387,7 @@ function getPathFromURLWin32(url) {
13881387
}
13891388
}
13901389
}
1391-
pathname = pathname.replace(forwardSlashRegEx, '\\');
1390+
pathname = StringPrototypeReplaceAll(pathname, '/', '\\');
13921391
pathname = decodeURIComponent(pathname);
13931392
if (hostname !== '') {
13941393
// 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,
@@ -554,6 +555,21 @@ function setOwnProperty(obj, key, value) {
554555
});
555556
}
556557

558+
let internalGlobal;
559+
function getInternalGlobal() {
560+
if (internalGlobal == null) {
561+
// Lazy-load to avoid a circular dependency.
562+
const { runInNewContext } = require('vm');
563+
internalGlobal = runInNewContext('this', undefined, { contextName: 'internal' });
564+
}
565+
return internalGlobal;
566+
}
567+
568+
function SideEffectFreeRegExpPrototypeExec(regex, string) {
569+
const { RegExp: RegExpFromAnotherRealm } = getInternalGlobal();
570+
return FunctionPrototypeCall(RegExpFromAnotherRealm.prototype.exec, regex, string);
571+
}
572+
557573
module.exports = {
558574
assertCrypto,
559575
cachedResult,
@@ -568,6 +584,7 @@ module.exports = {
568584
filterDuplicateStrings,
569585
filterOwnProperties,
570586
getConstructorOf,
587+
getInternalGlobal,
571588
getSystemErrorMap,
572589
getSystemErrorName,
573590
isError,
@@ -577,6 +594,7 @@ module.exports = {
577594
normalizeEncoding,
578595
once,
579596
promisify,
597+
SideEffectFreeRegExpPrototypeExec,
580598
sleep,
581599
spliceOne,
582600
structuredClone,
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)