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