refactor: lazily hook into child_process in asar_init (#18576)

Previously we loaded both fs and child_process and then hooked into
the returned value, relying on the module cache to keep our modifications
and give them to everyone.

Loading child_process took in excess of 20ms though so instead of loading
it and then hooking in.  We intercept all Module load requests, and when
the first one for `child_process` comes in, we wrap the appropriate methods
and then never touch it again.
This commit is contained in:
Samuel Attard 2019-06-03 13:19:52 -07:00 committed by GitHub
parent cb4579fe28
commit cec61d010b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,9 +2,9 @@
(function () {
const asar = process._linkedBinding('atom_common_asar')
const assert = require('assert')
const v8Util = process._linkedBinding('atom_common_v8_util')
const { Buffer } = require('buffer')
const childProcess = require('child_process')
const Module = require('module')
const path = require('path')
const util = require('util')
@ -141,7 +141,7 @@
error = new Error(`Invalid package ${asarPath}`)
break
default:
assert.fail(`Invalid error type "${errorType}" passed to createError.`)
throw new Error(`Invalid error type "${errorType}" passed to createError.`)
}
return error
}
@ -703,16 +703,6 @@
}
}
// Executing a command string containing a path to an asar
// archive confuses `childProcess.execFile`, which is internally
// called by `childProcess.{exec,execSync}`, causing
// Electron to consider the full command as a single path
// to an archive.
const { exec, execSync } = childProcess
childProcess.exec = invokeWithNoAsar(exec)
childProcess.exec[util.promisify.custom] = invokeWithNoAsar(exec[util.promisify.custom])
childProcess.execSync = invokeWithNoAsar(execSync)
function invokeWithNoAsar (func) {
return function () {
const processNoAsarOriginalValue = process.noAsar
@ -732,10 +722,35 @@
overrideAPISync(fs, 'copyFileSync')
overrideAPI(fs, 'open')
overrideAPI(childProcess, 'execFile')
overrideAPISync(process, 'dlopen', 1)
overrideAPISync(require('module')._extensions, '.node', 1)
overrideAPISync(Module._extensions, '.node', 1)
overrideAPISync(fs, 'openSync')
overrideAPISync(childProcess, 'execFileSync')
// Lazily override the child_process APIs only when child_process is fetched the first time
const originalModuleLoad = Module._load
Module._load = (request, ...args) => {
const loadResult = originalModuleLoad(request, ...args)
if (request === 'child_process') {
if (!v8Util.getHiddenValue(loadResult, 'asar-ready')) {
v8Util.setHiddenValue(loadResult, 'asar-ready', true)
// Just to make it obvious what we are dealing with here
const childProcess = loadResult
// Executing a command string containing a path to an asar
// archive confuses `childProcess.execFile`, which is internally
// called by `childProcess.{exec,execSync}`, causing
// Electron to consider the full command as a single path
// to an archive.
const { exec, execSync } = childProcess
childProcess.exec = invokeWithNoAsar(exec)
childProcess.exec[util.promisify.custom] = invokeWithNoAsar(exec[util.promisify.custom])
childProcess.execSync = invokeWithNoAsar(execSync)
overrideAPI(childProcess, 'execFile')
overrideAPISync(childProcess, 'execFileSync')
}
}
return loadResult
}
}
})()