diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 9b8ff459fa3573..09354cdc158420 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -1330,18 +1330,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); } } } diff --git a/test/parallel/test-readline-interface-recursive-writes.js b/test/parallel/test-readline-interface-recursive-writes.js new file mode 100644 index 00000000000000..3a0aee5be9d619 --- /dev/null +++ b/test/parallel/test-readline-interface-recursive-writes.js @@ -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);