• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  RegExpPrototypeExec,
5  globalThis,
6} = primordials;
7
8const path = require('path');
9
10const {
11  codes: {
12    ERR_INVALID_ARG_TYPE,
13    ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET,
14    ERR_EVAL_ESM_CANNOT_PRINT,
15  },
16} = require('internal/errors');
17
18const {
19  executionAsyncId,
20  clearDefaultTriggerAsyncId,
21  clearAsyncIdStack,
22  hasAsyncIdStack,
23  afterHooksExist,
24  emitAfter,
25  popAsyncContext,
26} = require('internal/async_hooks');
27
28// shouldAbortOnUncaughtToggle is a typed array for faster
29// communication with JS.
30const { shouldAbortOnUncaughtToggle } = internalBinding('util');
31
32function tryGetCwd() {
33  try {
34    return process.cwd();
35  } catch {
36    // getcwd(3) can fail if the current working directory has been deleted.
37    // Fall back to the directory name of the (absolute) executable path.
38    // It's not really correct but what are the alternatives?
39    return path.dirname(process.execPath);
40  }
41}
42
43function evalModule(source, print) {
44  if (print) {
45    throw new ERR_EVAL_ESM_CANNOT_PRINT();
46  }
47  const { loadESM } = require('internal/process/esm_loader');
48  const { handleMainPromise } = require('internal/modules/run_main');
49  RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
50  return handleMainPromise(loadESM((loader) => loader.eval(source)));
51}
52
53function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
54  const CJSModule = require('internal/modules/cjs/loader').Module;
55  const { kVmBreakFirstLineSymbol } = require('internal/util');
56  const { pathToFileURL } = require('url');
57
58  const cwd = tryGetCwd();
59  const origModule = globalThis.module;  // Set e.g. when called from the REPL.
60
61  const module = new CJSModule(name);
62  module.filename = path.join(cwd, name);
63  module.paths = CJSModule._nodeModulePaths(cwd);
64
65  const { handleMainPromise } = require('internal/modules/run_main');
66  const asyncESM = require('internal/process/esm_loader');
67  const baseUrl = pathToFileURL(module.filename).href;
68  const { loadESM } = asyncESM;
69
70  const runScript = () => {
71    // Create wrapper for cache entry
72    const script = `
73      globalThis.module = module;
74      globalThis.exports = exports;
75      globalThis.__dirname = __dirname;
76      globalThis.require = require;
77      return (main) => main();
78    `;
79    globalThis.__filename = name;
80    RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
81    const result = module._compile(script, `${name}-wrapper`)(() =>
82      require('vm').runInThisContext(body, {
83        filename: name,
84        displayErrors: true,
85        [kVmBreakFirstLineSymbol]: !!breakFirstLine,
86        importModuleDynamically(specifier, _, importAssertions) {
87          const loader = asyncESM.esmLoader;
88          return loader.import(specifier, baseUrl, importAssertions);
89        },
90      }));
91    if (print) {
92      const { log } = require('internal/console/global');
93      log(result);
94    }
95
96    if (origModule !== undefined)
97      globalThis.module = origModule;
98  };
99
100  if (shouldLoadESM) {
101    return handleMainPromise(loadESM(runScript));
102  }
103  return runScript();
104}
105
106const exceptionHandlerState = {
107  captureFn: null,
108  reportFlag: false,
109};
110
111function setUncaughtExceptionCaptureCallback(fn) {
112  if (fn === null) {
113    exceptionHandlerState.captureFn = fn;
114    shouldAbortOnUncaughtToggle[0] = 1;
115    process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag;
116    return;
117  }
118  if (typeof fn !== 'function') {
119    throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn);
120  }
121  if (exceptionHandlerState.captureFn !== null) {
122    throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET();
123  }
124  exceptionHandlerState.captureFn = fn;
125  shouldAbortOnUncaughtToggle[0] = 0;
126  exceptionHandlerState.reportFlag =
127    process.report.reportOnUncaughtException === true;
128  process.report.reportOnUncaughtException = false;
129}
130
131function hasUncaughtExceptionCaptureCallback() {
132  return exceptionHandlerState.captureFn !== null;
133}
134
135function noop() {}
136
137// XXX(joyeecheung): for some reason this cannot be defined at the top-level
138// and exported to be written to process._fatalException, it has to be
139// returned as an *anonymous function* wrapped inside a factory function,
140// otherwise it breaks the test-timers.setInterval async hooks test -
141// this may indicate that node::errors::TriggerUncaughtException() should
142// fix up the callback scope before calling into process._fatalException,
143// or this function should take extra care of the async hooks before it
144// schedules a setImmediate.
145function createOnGlobalUncaughtException() {
146  // The C++ land node::errors::TriggerUncaughtException() will
147  // exit the process if it returns false, and continue execution if it
148  // returns true (which indicates that the exception is handled by the user).
149  return (er, fromPromise) => {
150    // It's possible that defaultTriggerAsyncId was set for a constructor
151    // call that threw and was never cleared. So clear it now.
152    clearDefaultTriggerAsyncId();
153
154    const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
155    process.emit('uncaughtExceptionMonitor', er, type);
156    if (exceptionHandlerState.captureFn !== null) {
157      exceptionHandlerState.captureFn(er);
158    } else if (!process.emit('uncaughtException', er, type)) {
159      // If someone handled it, then great. Otherwise, die in C++ land
160      // since that means that we'll exit the process, emit the 'exit' event.
161      try {
162        if (!process._exiting) {
163          process._exiting = true;
164          process.exitCode = 1;
165          process.emit('exit', 1);
166        }
167      } catch {
168        // Nothing to be done about it at this point.
169      }
170      return false;
171    }
172
173    // If we handled an error, then make sure any ticks get processed
174    // by ensuring that the next Immediate cycle isn't empty.
175    require('timers').setImmediate(noop);
176
177    // Emit the after() hooks now that the exception has been handled.
178    if (afterHooksExist()) {
179      do {
180        const asyncId = executionAsyncId();
181        if (asyncId === 0)
182          popAsyncContext(0);
183        else
184          emitAfter(asyncId);
185      } while (hasAsyncIdStack());
186    }
187    // And completely empty the id stack, including anything that may be
188    // cached on the native side.
189    clearAsyncIdStack();
190
191    return true;
192  };
193}
194
195function readStdin(callback) {
196  process.stdin.setEncoding('utf8');
197
198  let code = '';
199  process.stdin.on('data', (d) => {
200    code += d;
201  });
202
203  process.stdin.on('end', () => {
204    callback(code);
205  });
206}
207
208module.exports = {
209  readStdin,
210  tryGetCwd,
211  evalModule,
212  evalScript,
213  onGlobalUncaughtException: createOnGlobalUncaughtException(),
214  setUncaughtExceptionCaptureCallback,
215  hasUncaughtExceptionCaptureCallback,
216};
217