| '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.stack : undefined); |
| } |
| } 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 |
| }; |