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