Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: fix .load infinite loop caused by shared use of lineEnding RegExp #46742

Merged
merged 12 commits into from
Mar 1, 2023
Merged
22 changes: 13 additions & 9 deletions lib/internal/readline/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -1322,18 +1322,22 @@ class Interface extends InterfaceConstructor {
// falls through
default:
if (typeof s === 'string' && s) {
// Erase state of previous searches.
lineEnding.lastIndex = 0;
let nextMatch = RegExpPrototypeExec(lineEnding, s);
if (nextMatch !== null) {
this[kInsertString](StringPrototypeSlice(s, 0, nextMatch.index));
let { lastIndex } = lineEnding;
while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) {
this[kLine]();
// If no line endings are found, just insert the string as is.
if (nextMatch === null) {
this[kInsertString](s);
} else {
// Keep track of the end of the last match.
let lastIndex = 0;
do {
this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index));
({ lastIndex } = lineEnding);
}
if (lastIndex === s.length) this[kLine]();
} else {
this[kInsertString](s);
this[kLine]();
// Restore lastIndex as the call to kLine could have mutated it.
lineEnding.lastIndex = lastIndex;
} while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null);
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions test/parallel/test-readline-interface-recursive-writes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';
const common = require('../common');
const ArrayStream = require('../common/arraystream');
const assert = require('assert');

common.skipIfDumbTerminal();

const readline = require('readline');
const rli = new readline.Interface({
terminal: true,
input: new ArrayStream(),
});

let recursionDepth = 0;

// Minimal reproduction for #46731
const testInput = ' \n}\n';
const numberOfExpectedLines = testInput.match(/\n/g).length;

rli.on('line', () => {
// Abort in case of infinite loop
if (recursionDepth > numberOfExpectedLines) {
return;
}
recursionDepth++;
// Write something recursively to readline
rli.write('foo');
});


rli.write(testInput);

assert.strictEqual(recursionDepth, numberOfExpectedLines);