1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayPrototypeForEach, 26 Symbol, 27 PromiseReject, 28 ReflectApply, 29} = primordials; 30 31const { 32 ContextifyScript, 33 MicrotaskQueue, 34 makeContext, 35 constants, 36 measureMemory: _measureMemory, 37} = internalBinding('contextify'); 38const { 39 ERR_CONTEXT_NOT_INITIALIZED, 40 ERR_INVALID_ARG_TYPE, 41} = require('internal/errors').codes; 42const { 43 validateBoolean, 44 validateBuffer, 45 validateFunction, 46 validateInt32, 47 validateObject, 48 validateOneOf, 49 validateString, 50 validateUint32, 51} = require('internal/validators'); 52const { 53 emitExperimentalWarning, 54 kEmptyObject, 55 kVmBreakFirstLineSymbol, 56} = require('internal/util'); 57const { 58 internalCompileFunction, 59 isContext, 60} = require('internal/vm'); 61const kParsingContext = Symbol('script parsing context'); 62 63class Script extends ContextifyScript { 64 constructor(code, options = kEmptyObject) { 65 code = `${code}`; 66 if (typeof options === 'string') { 67 options = { filename: options }; 68 } else { 69 validateObject(options, 'options'); 70 } 71 72 const { 73 filename = 'evalmachine.<anonymous>', 74 lineOffset = 0, 75 columnOffset = 0, 76 cachedData, 77 produceCachedData = false, 78 importModuleDynamically, 79 [kParsingContext]: parsingContext, 80 } = options; 81 82 validateString(filename, 'options.filename'); 83 validateInt32(lineOffset, 'options.lineOffset'); 84 validateInt32(columnOffset, 'options.columnOffset'); 85 if (cachedData !== undefined) { 86 validateBuffer(cachedData, 'options.cachedData'); 87 } 88 validateBoolean(produceCachedData, 'options.produceCachedData'); 89 90 // Calling `ReThrow()` on a native TryCatch does not generate a new 91 // abort-on-uncaught-exception check. A dummy try/catch in JS land 92 // protects against that. 93 try { // eslint-disable-line no-useless-catch 94 super(code, 95 filename, 96 lineOffset, 97 columnOffset, 98 cachedData, 99 produceCachedData, 100 parsingContext); 101 } catch (e) { 102 throw e; /* node-do-not-add-exception-line */ 103 } 104 105 if (importModuleDynamically !== undefined) { 106 validateFunction(importModuleDynamically, 107 'options.importModuleDynamically'); 108 const { importModuleDynamicallyWrap } = 109 require('internal/vm/module'); 110 const { callbackMap } = internalBinding('module_wrap'); 111 callbackMap.set(this, { 112 importModuleDynamically: 113 importModuleDynamicallyWrap(importModuleDynamically), 114 }); 115 } 116 } 117 118 runInThisContext(options) { 119 const { breakOnSigint, args } = getRunInContextArgs(null, options); 120 if (breakOnSigint && process.listenerCount('SIGINT') > 0) { 121 return sigintHandlersWrap(super.runInContext, this, args); 122 } 123 return ReflectApply(super.runInContext, this, args); 124 } 125 126 runInContext(contextifiedObject, options) { 127 validateContext(contextifiedObject); 128 const { breakOnSigint, args } = getRunInContextArgs( 129 contextifiedObject, 130 options, 131 ); 132 if (breakOnSigint && process.listenerCount('SIGINT') > 0) { 133 return sigintHandlersWrap(super.runInContext, this, args); 134 } 135 return ReflectApply(super.runInContext, this, args); 136 } 137 138 runInNewContext(contextObject, options) { 139 const context = createContext(contextObject, getContextOptions(options)); 140 return this.runInContext(context, options); 141 } 142} 143 144function validateContext(contextifiedObject) { 145 if (!isContext(contextifiedObject)) { 146 throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'vm.Context', 147 contextifiedObject); 148 } 149} 150 151function getRunInContextArgs(contextifiedObject, options = kEmptyObject) { 152 validateObject(options, 'options'); 153 154 let timeout = options.timeout; 155 if (timeout === undefined) { 156 timeout = -1; 157 } else { 158 validateUint32(timeout, 'options.timeout', true); 159 } 160 161 const { 162 displayErrors = true, 163 breakOnSigint = false, 164 [kVmBreakFirstLineSymbol]: breakFirstLine = false, 165 } = options; 166 167 validateBoolean(displayErrors, 'options.displayErrors'); 168 validateBoolean(breakOnSigint, 'options.breakOnSigint'); 169 170 return { 171 breakOnSigint, 172 args: [ 173 contextifiedObject, 174 timeout, 175 displayErrors, 176 breakOnSigint, 177 breakFirstLine, 178 ], 179 }; 180} 181 182function getContextOptions(options) { 183 if (!options) 184 return {}; 185 const contextOptions = { 186 name: options.contextName, 187 origin: options.contextOrigin, 188 codeGeneration: undefined, 189 microtaskMode: options.microtaskMode, 190 }; 191 if (contextOptions.name !== undefined) 192 validateString(contextOptions.name, 'options.contextName'); 193 if (contextOptions.origin !== undefined) 194 validateString(contextOptions.origin, 'options.contextOrigin'); 195 if (options.contextCodeGeneration !== undefined) { 196 validateObject(options.contextCodeGeneration, 197 'options.contextCodeGeneration'); 198 const { strings, wasm } = options.contextCodeGeneration; 199 if (strings !== undefined) 200 validateBoolean(strings, 'options.contextCodeGeneration.strings'); 201 if (wasm !== undefined) 202 validateBoolean(wasm, 'options.contextCodeGeneration.wasm'); 203 contextOptions.codeGeneration = { strings, wasm }; 204 } 205 if (options.microtaskMode !== undefined) 206 validateString(options.microtaskMode, 'options.microtaskMode'); 207 return contextOptions; 208} 209 210let defaultContextNameIndex = 1; 211function createContext(contextObject = {}, options = kEmptyObject) { 212 if (isContext(contextObject)) { 213 return contextObject; 214 } 215 216 validateObject(options, 'options'); 217 218 const { 219 name = `VM Context ${defaultContextNameIndex++}`, 220 origin, 221 codeGeneration, 222 microtaskMode, 223 } = options; 224 225 validateString(name, 'options.name'); 226 if (origin !== undefined) 227 validateString(origin, 'options.origin'); 228 if (codeGeneration !== undefined) 229 validateObject(codeGeneration, 'options.codeGeneration'); 230 231 let strings = true; 232 let wasm = true; 233 if (codeGeneration !== undefined) { 234 ({ strings = true, wasm = true } = codeGeneration); 235 validateBoolean(strings, 'options.codeGeneration.strings'); 236 validateBoolean(wasm, 'options.codeGeneration.wasm'); 237 } 238 239 validateOneOf(microtaskMode, 240 'options.microtaskMode', 241 ['afterEvaluate', undefined]); 242 const microtaskQueue = microtaskMode === 'afterEvaluate' ? 243 new MicrotaskQueue() : 244 null; 245 246 makeContext(contextObject, name, origin, strings, wasm, microtaskQueue); 247 return contextObject; 248} 249 250function createScript(code, options) { 251 return new Script(code, options); 252} 253 254// Remove all SIGINT listeners and re-attach them after the wrapped function 255// has executed, so that caught SIGINT are handled by the listeners again. 256function sigintHandlersWrap(fn, thisArg, argsArray) { 257 const sigintListeners = process.rawListeners('SIGINT'); 258 259 process.removeAllListeners('SIGINT'); 260 261 try { 262 return ReflectApply(fn, thisArg, argsArray); 263 } finally { 264 // Add using the public methods so that the `newListener` handler of 265 // process can re-attach the listeners. 266 ArrayPrototypeForEach(sigintListeners, (listener) => { 267 process.addListener('SIGINT', listener); 268 }); 269 } 270} 271 272function runInContext(code, contextifiedObject, options) { 273 validateContext(contextifiedObject); 274 if (typeof options === 'string') { 275 options = { 276 filename: options, 277 [kParsingContext]: contextifiedObject, 278 }; 279 } else { 280 options = { ...options, [kParsingContext]: contextifiedObject }; 281 } 282 return createScript(code, options) 283 .runInContext(contextifiedObject, options); 284} 285 286function runInNewContext(code, contextObject, options) { 287 if (typeof options === 'string') { 288 options = { filename: options }; 289 } 290 contextObject = createContext(contextObject, getContextOptions(options)); 291 options = { ...options, [kParsingContext]: contextObject }; 292 return createScript(code, options).runInNewContext(contextObject, options); 293} 294 295function runInThisContext(code, options) { 296 if (typeof options === 'string') { 297 options = { filename: options }; 298 } 299 return createScript(code, options).runInThisContext(options); 300} 301 302function compileFunction(code, params, options = kEmptyObject) { 303 return internalCompileFunction(code, params, options).function; 304} 305 306const measureMemoryModes = { 307 summary: constants.measureMemory.mode.SUMMARY, 308 detailed: constants.measureMemory.mode.DETAILED, 309}; 310 311const measureMemoryExecutions = { 312 default: constants.measureMemory.execution.DEFAULT, 313 eager: constants.measureMemory.execution.EAGER, 314}; 315 316function measureMemory(options = kEmptyObject) { 317 emitExperimentalWarning('vm.measureMemory'); 318 validateObject(options, 'options'); 319 const { mode = 'summary', execution = 'default' } = options; 320 validateOneOf(mode, 'options.mode', ['summary', 'detailed']); 321 validateOneOf(execution, 'options.execution', ['default', 'eager']); 322 const result = _measureMemory(measureMemoryModes[mode], 323 measureMemoryExecutions[execution]); 324 if (result === undefined) { 325 return PromiseReject(new ERR_CONTEXT_NOT_INITIALIZED()); 326 } 327 return result; 328} 329 330module.exports = { 331 Script, 332 createContext, 333 createScript, 334 runInContext, 335 runInNewContext, 336 runInThisContext, 337 isContext, 338 compileFunction, 339 measureMemory, 340}; 341 342// The vm module is patched to include vm.Module, vm.SourceTextModule 343// and vm.SyntheticModule in the pre-execution phase when 344// --experimental-vm-modules is on. 345