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