diff --git a/lib/repl.js b/lib/repl.js index 3b95bd7f29c85f..b2ab7e1f5b55bb 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -46,6 +46,7 @@ const { ArrayPrototypeConcat, ArrayPrototypeFilter, ArrayPrototypeFindIndex, + ArrayPrototypeForEach, ArrayPrototypeIncludes, ArrayPrototypeJoin, ArrayPrototypeMap, @@ -662,7 +663,7 @@ function REPLServer(prompt, let matched = false; errStack = ''; - for (const line of lines) { + ArrayPrototypeForEach(lines, (line) => { if (!matched && RegExpPrototypeTest(/^\[?([A-Z][a-z0-9_]*)*Error/, line)) { errStack += writer.options.breakLength >= line.length ? @@ -672,7 +673,7 @@ function REPLServer(prompt, } else { errStack += line; } - } + }); if (!matched) { const ln = lines.length === 1 ? ' ' : ':\n'; errStack = `Uncaught${ln}${errStack}`; @@ -753,9 +754,7 @@ function REPLServer(prompt, const prioritizedSigintQueue = new SafeSet(); self.on('SIGINT', function onSigInt() { if (prioritizedSigintQueue.size > 0) { - for (const task of prioritizedSigintQueue) { - task(); - } + ArrayPrototypeForEach(prioritizedSigintQueue, (task) => task()); return; } @@ -1009,13 +1008,13 @@ REPLServer.prototype.createContext = function() { }, () => { context = vm.createContext(); }); - for (const name of ObjectGetOwnPropertyNames(global)) { + ArrayPrototypeForEach(ObjectGetOwnPropertyNames(global), (name) => { // Only set properties that do not already exist as a global builtin. if (!globalBuiltins.has(name)) { ObjectDefineProperty(context, name, ObjectGetOwnPropertyDescriptor(global, name)); } - } + }); context.global = context; const _console = new Console(this.output); ObjectDefineProperty(context, 'console', { @@ -1229,7 +1228,7 @@ function complete(line, callback) { paths = ArrayPrototypeConcat(module.paths, CJSModule.globalPaths); } - for (let dir of paths) { + ArrayPrototypeForEach(paths, (dir) => { dir = path.resolve(dir, subdir); const dirents = gracefulReaddir(dir, { withFileTypes: true }) || []; for (const dirent of dirents) { @@ -1257,7 +1256,7 @@ function complete(line, callback) { } } } - } + }); if (group.length) { ArrayPrototypePush(completionGroups, group); } @@ -1349,11 +1348,11 @@ function complete(line, callback) { if (memberGroups.length) { expr += chaining; - for (const group of memberGroups) { + ArrayPrototypeForEach(memberGroups, (group) => { ArrayPrototypePush(completionGroups, ArrayPrototypeMap(group, (member) => `${expr}${member}`)); - } + }); if (filter) { filter = `${expr}${filter}`; } @@ -1372,7 +1371,7 @@ function complete(line, callback) { // Filter, sort (within each group), uniq and merge the completion groups. if (completionGroups.length && filter) { const newCompletionGroups = []; - for (const group of completionGroups) { + ArrayPrototypeForEach(completionGroups, (group) => { const filteredGroup = ArrayPrototypeFilter( group, (str) => StringPrototypeStartsWith(str, filter) @@ -1380,7 +1379,7 @@ function complete(line, callback) { if (filteredGroup.length) { ArrayPrototypePush(newCompletionGroups, filteredGroup); } - } + }); completionGroups = newCompletionGroups; } @@ -1389,20 +1388,20 @@ function complete(line, callback) { const uniqueSet = new SafeSet(['']); // Completion group 0 is the "closest" (least far up the inheritance // chain) so we put its completions last: to be closest in the REPL. - for (const group of completionGroups) { + ArrayPrototypeForEach(completionGroups, (group) => { ArrayPrototypeSort(group, (a, b) => (b > a ? 1 : -1)); const setSize = uniqueSet.size; - for (const entry of group) { + ArrayPrototypeForEach(group, (entry) => { if (!uniqueSet.has(entry)) { ArrayPrototypeUnshift(completions, entry); uniqueSet.add(entry); } - } + }); // Add a separator between groups. if (uniqueSet.size !== setSize) { ArrayPrototypeUnshift(completions, ''); } - } + }); // Remove obsolete group entry, if present. if (completions[0] === '') { @@ -1566,14 +1565,13 @@ function defineDefaultCommands(repl) { const longestNameLength = MathMax( ...ArrayPrototypeMap(names, (name) => name.length) ); - for (let n = 0; n < names.length; n++) { - const name = names[n]; + ArrayPrototypeForEach(names, (name) => { const cmd = this.commands[name]; const spaces = StringPrototypeRepeat(' ', longestNameLength - name.length + 3); const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`; this.output.write(line); - } + }); this.output.write('\nPress Ctrl+C to abort current expression, ' + 'Ctrl+D to exit the REPL\n'); this.displayPrompt(); diff --git a/test/parallel/test-repl-unsafe-array-iteration.js b/test/parallel/test-repl-unsafe-array-iteration.js new file mode 100644 index 00000000000000..faeb2b159c7ad2 --- /dev/null +++ b/test/parallel/test-repl-unsafe-array-iteration.js @@ -0,0 +1,68 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +const replProcess = spawn(process.argv0, ['--interactive'], { + stdio: ['pipe', 'pipe', 'inherit'], + windowsHide: true, +}); + +replProcess.on('error', common.mustNotCall()); + +const replReadyState = (async function* () { + let ready; + const SPACE = ' '.charCodeAt(); + const BRACKET = '>'.charCodeAt(); + const DOT = '.'.charCodeAt(); + replProcess.stdout.on('data', (data) => { + ready = data[data.length - 1] === SPACE && ( + data[data.length - 2] === BRACKET || ( + data[data.length - 2] === DOT && + data[data.length - 3] === DOT && + data[data.length - 4] === DOT + )); + }); + + const processCrashed = new Promise((resolve, reject) => + replProcess.on('exit', reject) + ); + while (true) { + await Promise.race([new Promise(setImmediate), processCrashed]); + if (ready) { + ready = false; + yield; + } + } +})(); +async function writeLn(data, expectedOutput) { + await replReadyState.next(); + if (expectedOutput) { + replProcess.stdout.once('data', common.mustCall((data) => + assert.match(data.toString('utf8'), expectedOutput) + )); + } + await new Promise((resolve, reject) => replProcess.stdin.write( + `${data}\n`, + (err) => (err ? reject(err) : resolve()) + )); +} + +async function main() { + await writeLn( + 'const ArrayIteratorPrototype =' + + ' Object.getPrototypeOf(Array.prototype[Symbol.iterator]());' + ); + await writeLn('delete ArrayIteratorPrototype.next;'); + await writeLn('delete Array.prototype[Symbol.iterator];'); + + await writeLn( + 'for(const x of [3, 2, 1]);', + /Uncaught TypeError: \[3,2,1\] is not iterable/ + ); + await writeLn('.exit'); + + assert(!replProcess.connected); +} + +main().then(common.mustCall());