lts/lib/internal/process/execution.js
'use strict';
const {
JSONStringify,
PromiseResolve,
} = primordials;
const path = require('path');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET
}
} = require('internal/errors');
const {
executionAsyncId,
clearDefaultTriggerAsyncId,
clearAsyncIdStack,
hasAsyncIdStack,
afterHooksExist,
emitAfter
} = require('internal/async_hooks');
// shouldAbortOnUncaughtToggle is a typed array for faster
// communication with JS.
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
function tryGetCwd() {
try {
return process.cwd();
} catch {
// getcwd(3) can fail if the current working directory has been deleted.
// Fall back to the directory name of the (absolute) executable path.
// It's not really correct but what are the alternatives?
return path.dirname(process.execPath);
}
}
function evalModule(source, print) {
const { log, error } = require('internal/console/global');
const { decorateErrorStack } = require('internal/util');
const asyncESM = require('internal/process/esm_loader');
PromiseResolve(asyncESM.ESMLoader).then(async (loader) => {
const { result } = await loader.eval(source);
if (print) {
log(result);
}
})
.catch((e) => {
decorateErrorStack(e);
error(e);
process.exit(1);
});
}
function evalScript(name, body, breakFirstLine, print) {
const CJSModule = require('internal/modules/cjs/loader').Module;
const { kVmBreakFirstLineSymbol } = require('internal/util');
const { pathToFileURL } = require('url');
const cwd = tryGetCwd();
const origModule = global.module; // Set e.g. when called from the REPL.
const module = new CJSModule(name);
module.filename = path.join(cwd, name);
module.paths = CJSModule._nodeModulePaths(cwd);
global.kVmBreakFirstLineSymbol = kVmBreakFirstLineSymbol;
global.asyncESM = require('internal/process/esm_loader');
const baseUrl = pathToFileURL(module.filename).href;
const script = `
global.__filename = ${JSONStringify(name)};
global.exports = exports;
global.module = module;
global.__dirname = __dirname;
global.require = require;
const { kVmBreakFirstLineSymbol, asyncESM } = global;
delete global.kVmBreakFirstLineSymbol;
delete global.asyncESM;
return require("vm").runInThisContext(
${JSONStringify(body)}, {
filename: ${JSONStringify(name)},
displayErrors: true,
[kVmBreakFirstLineSymbol]: ${!!breakFirstLine},
async importModuleDynamically (specifier) {
const loader = await asyncESM.ESMLoader;
return loader.import(specifier, ${JSONStringify(baseUrl)});
}
});\n`;
const result = module._compile(script, `${name}-wrapper`);
if (print) {
const { log } = require('internal/console/global');
log(result);
}
if (origModule !== undefined)
global.module = origModule;
}
const exceptionHandlerState = { captureFn: null };
function setUncaughtExceptionCaptureCallback(fn) {
if (fn === null) {
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 1;
return;
}
if (typeof fn !== 'function') {
throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
}
if (exceptionHandlerState.captureFn !== null) {
throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
}
exceptionHandlerState.captureFn = fn;
shouldAbortOnUncaughtToggle[0] = 0;
}
function hasUncaughtExceptionCaptureCallback() {
return exceptionHandlerState.captureFn !== null;
}
function noop() {}
// XXX(joyeecheung): for some reason this cannot be defined at the top-level
// and exported to be written to process._fatalException, it has to be
// returned as an *anonymous function* wrapped inside a factory function,
// otherwise it breaks the test-timers.setInterval async hooks test -
// this may indicate that node::errors::TriggerUncaughtException() should
// fix up the callback scope before calling into process._fatalException,
// or this function should take extra care of the async hooks before it
// schedules a setImmediate.
function createOnGlobalUncaughtException() {
// The C++ land node::errors::TriggerUncaughtException() will
// exit the process if it returns false, and continue execution if it
// returns true (which indicates that the exception is handled by the user).
return (er, fromPromise) => {
// It's possible that defaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();
// If diagnostic reporting is enabled, call into its handler to see
// whether it is interested in handling the situation.
// Ignore if the error is scoped inside a domain.
// use == in the checks as we want to allow for null and undefined
if (er == null || er.domain == null) {
try {
const report = internalBinding('report');
if (report != null && report.shouldReportOnUncaughtException()) {
report.writeReport(er ? er.message : 'Exception',
'Exception',
null,
er ? er : {});
}
} catch {} // Ignore the exception. Diagnostic reporting is unavailable.
}
const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
process.emit('uncaughtExceptionMonitor', er, type);
if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
} else if (!process.emit('uncaughtException', er, type)) {
// If someone handled it, then great. Otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event.
try {
if (!process._exiting) {
process._exiting = true;
process.exitCode = 1;
process.emit('exit', 1);
}
} catch {
// Nothing to be done about it at this point.
}
return false;
}
// If we handled an error, then make sure any ticks get processed
// by ensuring that the next Immediate cycle isn't empty.
require('timers').setImmediate(noop);
// Emit the after() hooks now that the exception has been handled.
if (afterHooksExist()) {
do {
emitAfter(executionAsyncId());
} while (hasAsyncIdStack());
// Or completely empty the id stack.
} else {
clearAsyncIdStack();
}
return true;
};
}
function readStdin(callback) {
process.stdin.setEncoding('utf8');
let code = '';
process.stdin.on('data', (d) => {
code += d;
});
process.stdin.on('end', () => {
callback(code);
});
}
module.exports = {
readStdin,
tryGetCwd,
evalModule,
evalScript,
onGlobalUncaughtException: createOnGlobalUncaughtException(),
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
};