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