child_process: simplify spawn argument parsing

This commit simplifies the object returned by
normalizeSpawnArguments(). This does impact monkey patching,
as illustrated by the changes in tests.

PR-URL: https://github.com/nodejs/node/pull/27854
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
cjihrig 2019-05-24 11:00:45 -04:00
parent 476c3ec750
commit 4f9cd2770a
No known key found for this signature in database
GPG Key ID: 7434390BDBE9B9C5
5 changed files with 38 additions and 57 deletions

View File

@ -460,16 +460,14 @@ function normalizeSpawnArguments(file, args, options) {
} }
// Validate windowsVerbatimArguments, if present. // Validate windowsVerbatimArguments, if present.
if (options.windowsVerbatimArguments != null && let { windowsVerbatimArguments } = options;
typeof options.windowsVerbatimArguments !== 'boolean') { if (windowsVerbatimArguments != null &&
typeof windowsVerbatimArguments !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('options.windowsVerbatimArguments', throw new ERR_INVALID_ARG_TYPE('options.windowsVerbatimArguments',
'boolean', 'boolean',
options.windowsVerbatimArguments); windowsVerbatimArguments);
} }
// Make a shallow copy so we don't clobber the user's options object.
options = { ...options };
if (options.shell) { if (options.shell) {
const command = [file].concat(args).join(' '); const command = [file].concat(args).join(' ');
// Set the shell, switches, and commands. // Set the shell, switches, and commands.
@ -481,7 +479,7 @@ function normalizeSpawnArguments(file, args, options) {
// '/d /s /c' is used only for cmd.exe. // '/d /s /c' is used only for cmd.exe.
if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) { if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) {
args = ['/d', '/s', '/c', `"${command}"`]; args = ['/d', '/s', '/c', `"${command}"`];
options.windowsVerbatimArguments = true; windowsVerbatimArguments = true;
} else { } else {
args = ['-c', command]; args = ['-c', command];
} }
@ -521,47 +519,35 @@ function normalizeSpawnArguments(file, args, options) {
} }
return { return {
file: file, // Make a shallow copy so we don't clobber the user's options object.
args: args, ...options,
options: options, args,
envPairs: envPairs detached: !!options.detached,
envPairs,
file,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!windowsVerbatimArguments
}; };
} }
var spawn = exports.spawn = function spawn(file, args, options) { var spawn = exports.spawn = function spawn(file, args, options) {
const opts = normalizeSpawnArguments(file, args, options);
const child = new ChildProcess(); const child = new ChildProcess();
options = opts.options; options = normalizeSpawnArguments(file, args, options);
debug('spawn', opts.args, options); debug('spawn', options);
child.spawn(options);
child.spawn({
file: opts.file,
args: opts.args,
cwd: options.cwd,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
detached: !!options.detached,
envPairs: opts.envPairs,
stdio: options.stdio,
uid: options.uid,
gid: options.gid
});
return child; return child;
}; };
function spawnSync(file, args, options) { function spawnSync(file, args, options) {
const opts = normalizeSpawnArguments(file, args, options); options = {
const defaults = {
maxBuffer: MAX_BUFFER, maxBuffer: MAX_BUFFER,
...opts.options ...normalizeSpawnArguments(file, args, options)
}; };
options = opts.options = defaults;
debug('spawnSync', opts.args, options); debug('spawnSync', options);
// Validate the timeout, if present. // Validate the timeout, if present.
validateTimeout(options.timeout); validateTimeout(options.timeout);
@ -569,10 +555,6 @@ function spawnSync(file, args, options) {
// Validate maxBuffer, if present. // Validate maxBuffer, if present.
validateMaxBuffer(options.maxBuffer); validateMaxBuffer(options.maxBuffer);
options.file = opts.file;
options.args = opts.args;
options.envPairs = opts.envPairs;
// Validate and translate the kill signal, if present. // Validate and translate the kill signal, if present.
options.killSignal = sanitizeKillSignal(options.killSignal); options.killSignal = sanitizeKillSignal(options.killSignal);
@ -603,7 +585,7 @@ function spawnSync(file, args, options) {
} }
} }
return child_process.spawnSync(opts); return child_process.spawnSync(options);
} }
exports.spawnSync = spawnSync; exports.spawnSync = spawnSync;
@ -628,15 +610,15 @@ function checkExecSyncError(ret, args, cmd) {
function execFileSync(command, args, options) { function execFileSync(command, args, options) {
const opts = normalizeSpawnArguments(command, args, options); options = normalizeSpawnArguments(command, args, options);
const inheritStderr = !opts.options.stdio;
const ret = spawnSync(opts.file, opts.args.slice(1), opts.options); const inheritStderr = !options.stdio;
const ret = spawnSync(options.file, options.args.slice(1), options);
if (inheritStderr && ret.stderr) if (inheritStderr && ret.stderr)
process.stderr.write(ret.stderr); process.stderr.write(ret.stderr);
const err = checkExecSyncError(ret, opts.args, undefined); const err = checkExecSyncError(ret, options.args, undefined);
if (err) if (err)
throw err; throw err;

View File

@ -1022,8 +1022,7 @@ function maybeClose(subprocess) {
} }
} }
function spawnSync(opts) { function spawnSync(options) {
const options = opts.options;
const result = spawn_sync.spawn(options); const result = spawn_sync.spawn(options);
if (result.output && options.encoding && options.encoding !== 'buffer') { if (result.output && options.encoding && options.encoding !== 'buffer') {
@ -1038,9 +1037,9 @@ function spawnSync(opts) {
result.stderr = result.output && result.output[2]; result.stderr = result.output && result.output[2];
if (result.error) { if (result.error) {
result.error = errnoException(result.error, 'spawnSync ' + opts.file); result.error = errnoException(result.error, 'spawnSync ' + options.file);
result.error.path = opts.file; result.error.path = options.file;
result.error.spawnargs = opts.args.slice(1); result.error.spawnargs = options.args.slice(1);
} }
return result; return result;

View File

@ -36,7 +36,7 @@ if (process.argv[2] === 'child') {
// Verify that the default kill signal is SIGTERM. // Verify that the default kill signal is SIGTERM.
{ {
const child = spawn(undefined, (opts) => { const child = spawn(undefined, (opts) => {
assert.strictEqual(opts.options.killSignal, undefined); assert.strictEqual(opts.killSignal, undefined);
}); });
assert.strictEqual(child.signal, 'SIGTERM'); assert.strictEqual(child.signal, 'SIGTERM');
@ -45,7 +45,7 @@ if (process.argv[2] === 'child') {
// Verify that a string signal name is handled properly. // Verify that a string signal name is handled properly.
{ {
const child = spawn('SIGKILL', (opts) => { const child = spawn('SIGKILL', (opts) => {
assert.strictEqual(opts.options.killSignal, SIGKILL); assert.strictEqual(opts.killSignal, SIGKILL);
}); });
assert.strictEqual(child.signal, 'SIGKILL'); assert.strictEqual(child.signal, 'SIGKILL');
@ -56,7 +56,7 @@ if (process.argv[2] === 'child') {
assert.strictEqual(typeof SIGKILL, 'number'); assert.strictEqual(typeof SIGKILL, 'number');
const child = spawn(SIGKILL, (opts) => { const child = spawn(SIGKILL, (opts) => {
assert.strictEqual(opts.options.killSignal, SIGKILL); assert.strictEqual(opts.killSignal, SIGKILL);
}); });
assert.strictEqual(child.signal, 'SIGKILL'); assert.strictEqual(child.signal, 'SIGKILL');

View File

@ -64,12 +64,12 @@ assert.strictEqual(env.stdout.toString().trim(), 'buzz');
assert.strictEqual(opts.file, shellOutput); assert.strictEqual(opts.file, shellOutput);
assert.deepStrictEqual(opts.args, assert.deepStrictEqual(opts.args,
[shellOutput, ...shellFlags, outputCmd]); [shellOutput, ...shellFlags, outputCmd]);
assert.strictEqual(opts.options.shell, shell); assert.strictEqual(opts.shell, shell);
assert.strictEqual(opts.options.file, opts.file); assert.strictEqual(opts.file, opts.file);
assert.deepStrictEqual(opts.options.args, opts.args); assert.deepStrictEqual(opts.args, opts.args);
assert.strictEqual(opts.options.windowsHide, undefined); assert.strictEqual(opts.windowsHide, false);
assert.strictEqual(opts.options.windowsVerbatimArguments, assert.strictEqual(opts.windowsVerbatimArguments,
windowsVerbatim); !!windowsVerbatim);
}); });
cp.spawnSync(cmd, { shell }); cp.spawnSync(cmd, { shell });
internalCp.spawnSync = oldSpawnSync; internalCp.spawnSync = oldSpawnSync;

View File

@ -19,7 +19,7 @@ internalCp.ChildProcess.prototype.spawn = common.mustCall(function(options) {
}, 2); }, 2);
internalCp.spawnSync = common.mustCall(function(options) { internalCp.spawnSync = common.mustCall(function(options) {
assert.strictEqual(options.options.windowsHide, true); assert.strictEqual(options.windowsHide, true);
return originalSpawnSync.apply(this, arguments); return originalSpawnSync.apply(this, arguments);
}); });