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