Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c7d4ff3

Browse filesBrowse files
Theo-Steinertargos
authored andcommitted
repl: fix .load infinite loop caused by shared use of lineEnding RegExp
Since the lineEnding Regular Expression is declared on the module scope, recursive invocations of its `[kTtyWrite]` method share one instance of this Regular Expression. Since the state of a RegExp is managed by instance, alternately calling RegExpPrototypeExec with the same RegExp on different strings can lead to the state changing unexpectedly. This is the root cause of this infinite loop bug when calling .load on javascript files of certain shapes. PR-URL: #46742 Fixes: #46731 Reviewed-By: Kohei Ueno <kohei.ueno119@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 132fc45 commit c7d4ff3
Copy full SHA for c7d4ff3

File tree

Expand file treeCollapse file tree

2 files changed

+46
-9
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+46
-9
lines changed
Open diff view settings
Collapse file

‎lib/internal/readline/interface.js‎

Copy file name to clipboardExpand all lines: lib/internal/readline/interface.js
+13-9Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,18 +1330,22 @@ class Interface extends InterfaceConstructor {
13301330
// falls through
13311331
default:
13321332
if (typeof s === 'string' && s) {
1333+
// Erase state of previous searches.
1334+
lineEnding.lastIndex = 0;
13331335
let nextMatch = RegExpPrototypeExec(lineEnding, s);
1334-
if (nextMatch !== null) {
1335-
this[kInsertString](StringPrototypeSlice(s, 0, nextMatch.index));
1336-
let { lastIndex } = lineEnding;
1337-
while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) {
1338-
this[kLine]();
1336+
// If no line endings are found, just insert the string as is.
1337+
if (nextMatch === null) {
1338+
this[kInsertString](s);
1339+
} else {
1340+
// Keep track of the end of the last match.
1341+
let lastIndex = 0;
1342+
do {
13391343
this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index));
13401344
({ lastIndex } = lineEnding);
1341-
}
1342-
if (lastIndex === s.length) this[kLine]();
1343-
} else {
1344-
this[kInsertString](s);
1345+
this[kLine]();
1346+
// Restore lastIndex as the call to kLine could have mutated it.
1347+
lineEnding.lastIndex = lastIndex;
1348+
} while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null);
13451349
}
13461350
}
13471351
}
Collapse file
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
const common = require('../common');
3+
const ArrayStream = require('../common/arraystream');
4+
const assert = require('assert');
5+
6+
common.skipIfDumbTerminal();
7+
8+
const readline = require('readline');
9+
const rli = new readline.Interface({
10+
terminal: true,
11+
input: new ArrayStream(),
12+
});
13+
14+
let recursionDepth = 0;
15+
16+
// Minimal reproduction for #46731
17+
const testInput = ' \n}\n';
18+
const numberOfExpectedLines = testInput.match(/\n/g).length;
19+
20+
rli.on('line', () => {
21+
// Abort in case of infinite loop
22+
if (recursionDepth > numberOfExpectedLines) {
23+
return;
24+
}
25+
recursionDepth++;
26+
// Write something recursively to readline
27+
rli.write('foo');
28+
});
29+
30+
31+
rli.write(testInput);
32+
33+
assert.strictEqual(recursionDepth, numberOfExpectedLines);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.