diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 45ae95614a88b5..efbd8ccaa68f79 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -3,10 +3,10 @@ const { ArrayIsArray, ArrayPrototypePush, - ArrayPrototypeReduce, ArrayPrototypeSlice, FunctionPrototype, FunctionPrototypeCall, + ObjectDefineProperty, ObjectSetPrototypeOf, ReflectApply, StringPrototypeSlice, @@ -975,40 +975,51 @@ function getValidStdio(stdio, sync) { throw new ERR_INVALID_ARG_VALUE('stdio', stdio); } - // At least 3 stdio will be created - // Don't concat() a new Array() because it would be sparse, and - // stdio.reduce() would skip the sparse elements of stdio. - // See https://stackoverflow.com/a/5501711/3561 + // At least 3 stdio will be created. while (stdio.length < 3) ArrayPrototypePush(stdio, undefined); // Translate stdio into C++-readable form // (i.e. PipeWraps or fds) - stdio = ArrayPrototypeReduce(stdio, (acc, stdio, i) => { - function cleanup() { - for (let i = 0; i < acc.length; i++) { - if ((acc[i].type === 'pipe' || acc[i].type === 'ipc') && acc[i].handle) - acc[i].handle.close(); - } + const acc = []; + + function cleanup() { + for (let i = 0; i < acc.length; i++) { + if ((acc[i].type === 'pipe' || acc[i].type === 'ipc') && acc[i].handle) + acc[i].handle.close(); } + } + + function pushAcc(entry) { + ObjectDefineProperty(acc, acc.length, { + __proto__: null, + value: entry, + configurable: true, + enumerable: true, + writable: true, + }); + } + + for (let i = 0; i < stdio.length; i++) { + let stdioValue = stdio[i]; // Defaults - stdio ??= i < 3 ? 'pipe' : 'ignore'; - - if (stdio === 'ignore') { - ArrayPrototypePush(acc, { type: 'ignore' }); - } else if (stdio === 'pipe' || stdio === 'overlapped' || - (typeof stdio === 'number' && stdio < 0)) { - const a = { - type: stdio === 'overlapped' ? 'overlapped' : 'pipe', + stdioValue ??= i < 3 ? 'pipe' : 'ignore'; + + if (stdioValue === 'ignore') { + pushAcc({ type: 'ignore' }); + } else if (stdioValue === 'pipe' || stdioValue === 'overlapped' || + (typeof stdioValue === 'number' && stdioValue < 0)) { + const entry = { + type: stdioValue === 'overlapped' ? 'overlapped' : 'pipe', readable: i === 0, writable: i !== 0, }; if (!sync) - a.handle = new Pipe(PipeConstants.SOCKET); + entry.handle = new Pipe(PipeConstants.SOCKET); - ArrayPrototypePush(acc, a); - } else if (stdio === 'ipc') { + pushAcc(entry); + } else if (stdioValue === 'ipc') { if (sync || ipc !== undefined) { // Cleanup previously created pipes cleanup(); @@ -1021,46 +1032,50 @@ function getValidStdio(stdio, sync) { ipc = new Pipe(PipeConstants.IPC); ipcFd = i; - ArrayPrototypePush(acc, { + pushAcc({ type: 'pipe', handle: ipc, ipc: true, }); - } else if (stdio === 'inherit') { - ArrayPrototypePush(acc, { + } else if (stdioValue === 'inherit') { + pushAcc({ type: 'inherit', fd: i, }); - } else if (typeof stdio === 'number' || typeof stdio.fd === 'number') { - ArrayPrototypePush(acc, { + } else if (typeof stdioValue === 'number' || + typeof stdioValue.fd === 'number') { + pushAcc({ type: 'fd', - fd: typeof stdio === 'number' ? stdio : stdio.fd, + fd: typeof stdioValue === 'number' ? stdioValue : stdioValue.fd, }); - } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) || - getHandleWrapType(stdio._handle)) { - const handle = getHandleWrapType(stdio) ? - stdio : - getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle; - - ArrayPrototypePush(acc, { + } else if (getHandleWrapType(stdioValue) || + getHandleWrapType(stdioValue.handle) || + getHandleWrapType(stdioValue._handle)) { + const handle = getHandleWrapType(stdioValue) ? + stdioValue : + getHandleWrapType(stdioValue.handle) ? + stdioValue.handle : + stdioValue._handle; + + pushAcc({ type: 'wrap', wrapType: getHandleWrapType(handle), handle: handle, - _stdio: stdio, + _stdio: stdioValue, }); - } else if (isArrayBufferView(stdio) || typeof stdio === 'string') { + } else if (isArrayBufferView(stdioValue) || typeof stdioValue === 'string') { if (!sync) { cleanup(); - throw new ERR_INVALID_SYNC_FORK_INPUT(inspect(stdio)); + throw new ERR_INVALID_SYNC_FORK_INPUT(inspect(stdioValue)); } } else { // Cleanup cleanup(); - throw new ERR_INVALID_ARG_VALUE('stdio', stdio); + throw new ERR_INVALID_ARG_VALUE('stdio', stdioValue); } + } - return acc; - }, []); + stdio = acc; return { stdio, ipc, ipcFd }; } diff --git a/src/process_wrap.cc b/src/process_wrap.cc index d27ca7da7b587b..e11d53f47ce4e3 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -140,6 +140,11 @@ class ProcessWrap : public HandleWrap { if (!stdios->Get(context, i).ToLocal(&val)) { return Nothing(); } + if (!val->IsObject()) { + THROW_ERR_INVALID_ARG_TYPE( + env, "options.stdio[%u] must be an object", i); + return Nothing(); + } Local stdio = val.As(); Local type; if (!stdio->Get(context, env->type_string()).ToLocal(&type)) { diff --git a/test/parallel/test-child-process-array-prototype-setter.js b/test/parallel/test-child-process-array-prototype-setter.js new file mode 100644 index 00000000000000..0f752731e96d87 --- /dev/null +++ b/test/parallel/test-child-process-array-prototype-setter.js @@ -0,0 +1,29 @@ +'use strict'; +require('../common'); + +const assert = require('assert'); +const { spawnSyncAndAssert } = require('../common/child_process'); + +const script = ` +Object.defineProperty(Array.prototype, '2', { + __proto__: null, + set() {}, + configurable: true, +}); + +try { + require('child_process').spawn(process.execPath, ['-e', '0']); + console.log('NO_ERROR'); +} catch (error) { + console.log(error.code); +} +`; + +spawnSyncAndAssert(process.execPath, ['-e', script], { + stdout: (output) => { + assert.match(output, /^NO_ERROR\r?\n$/); + }, + stderr: (output) => { + assert.doesNotMatch(output, /FATAL ERROR: v8::ToLocalChecked Empty MaybeLocal/); + }, +});