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 validateArray, 44 validateBoolean, 45 validateBuffer, 46 validateInt32, 47 validateOneOf, 48 validateObject, 49 validateString, 50 validateStringArray, 51 validateUint32, 52} = require('internal/validators'); 53const { 54 emitExperimentalWarning, 55 kEmptyObject, 56 kVmBreakFirstLineSymbol, 57} = require('internal/util'); 58const { 59 getHostDefinedOptionId, 60 internalCompileFunction, 61 isContext, 62 registerImportModuleDynamically, 63} = require('internal/vm'); 64const kParsingContext = Symbol('script parsing context'); 65 66class Script extends ContextifyScript { 67 constructor(code, options = kEmptyObject) { 68 code = `${code}`; 69 if (typeof options === 'string') { 70 options = { filename: options }; 71 } else { 72 validateObject(options, 'options'); 73 } 74 75 const { 76 filename = 'evalmachine.<anonymous>', 77 lineOffset = 0, 78 columnOffset = 0, 79 cachedData, 80 produceCachedData = false, 81 importModuleDynamically, 82 [kParsingContext]: parsingContext, 83 } = options; 84 85 validateString(filename, 'options.filename'); 86 validateInt32(lineOffset, 'options.lineOffset'); 87 validateInt32(columnOffset, 'options.columnOffset'); 88 if (cachedData !== undefined) { 89 validateBuffer(cachedData, 'options.cachedData'); 90 } 91 validateBoolean(produceCachedData, 'options.produceCachedData'); 92 93 const hostDefinedOptionId = 94 getHostDefinedOptionId(importModuleDynamically, filename); 95 // Calling `ReThrow()` on a native TryCatch does not generate a new 96 // abort-on-uncaught-exception check. A dummy try/catch in JS land 97 // protects against that. 98 try { // eslint-disable-line no-useless-catch 99 super(code, 100 filename, 101 lineOffset, 102 columnOffset, 103 cachedData, 104 produceCachedData, 105 parsingContext, 106 hostDefinedOptionId); 107 } catch (e) { 108 throw e; /* node-do-not-add-exception-line */ 109 } 110 111 if (importModuleDynamically !== undefined) { 112 registerImportModuleDynamically(this, importModuleDynamically); 113 } 114 } 115 116 runInThisContext(options) { 117 const { breakOnSigint, args } = getRunInContextArgs(null, options); 118 if (breakOnSigint && process.listenerCount('SIGINT') > 0) { 119 return sigintHandlersWrap(super.runInContext, this, args); 120 } 121 return ReflectApply(super.runInContext, this, args); 122 } 123 124 runInContext(contextifiedObject, options) { 125 validateContext(contextifiedObject); 126 const { breakOnSigint, args } = getRunInContextArgs( 127 contextifiedObject, 128 options, 129 ); 130 if (breakOnSigint && process.listenerCount('SIGINT') > 0) { 131 return sigintHandlersWrap(super.runInContext, this, args); 132 } 133 return ReflectApply(super.runInContext, this, args); 134 } 135 136 runInNewContext(contextObject, options) { 137 const context = createContext(contextObject, getContextOptions(options)); 138 return this.runInContext(context, options); 139 } 140} 141 142function validateContext(contextifiedObject) { 143 if (!isContext(contextifiedObject)) { 144 throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'vm.Context', 145 contextifiedObject); 146 } 147} 148 149function getRunInContextArgs(contextifiedObject, options = kEmptyObject) { 150 validateObject(options, 'options'); 151 152 let timeout = options.timeout; 153 if (timeout === undefined) { 154 timeout = -1; 155 } else { 156 validateUint32(timeout, 'options.timeout', true); 157 } 158 159 const { 160 displayErrors = true, 161 breakOnSigint = false, 162 [kVmBreakFirstLineSymbol]: breakFirstLine = false, 163 } = options; 164 165 validateBoolean(displayErrors, 'options.displayErrors'); 166 validateBoolean(breakOnSigint, 'options.breakOnSigint'); 167 168 return { 169 breakOnSigint, 170 args: [ 171 contextifiedObject, 172 timeout, 173 displayErrors, 174 breakOnSigint, 175 breakFirstLine, 176 ], 177 }; 178} 179 180function getContextOptions(options) { 181 if (!options) 182 return {}; 183 const contextOptions = { 184 name: options.contextName, 185 origin: options.contextOrigin, 186 codeGeneration: undefined, 187 microtaskMode: options.microtaskMode, 188 }; 189 if (contextOptions.name !== undefined) 190 validateString(contextOptions.name, 'options.contextName'); 191 if (contextOptions.origin !== undefined) 192 validateString(contextOptions.origin, 'options.contextOrigin'); 193 if (options.contextCodeGeneration !== undefined) { 194 validateObject(options.contextCodeGeneration, 195 'options.contextCodeGeneration'); 196 const { strings, wasm } = options.contextCodeGeneration; 197 if (strings !== undefined) 198 validateBoolean(strings, 'options.contextCodeGeneration.strings'); 199 if (wasm !== undefined) 200 validateBoolean(wasm, 'options.contextCodeGeneration.wasm'); 201 contextOptions.codeGeneration = { strings, wasm }; 202 } 203 if (options.microtaskMode !== undefined) 204 validateString(options.microtaskMode, 'options.microtaskMode'); 205 return contextOptions; 206} 207 208let defaultContextNameIndex = 1; 209function createContext(contextObject = {}, options = kEmptyObject) { 210 if (isContext(contextObject)) { 211 return contextObject; 212 } 213 214 validateObject(options, 'options'); 215 216 const { 217 name = `VM Context ${defaultContextNameIndex++}`, 218 origin, 219 codeGeneration, 220 microtaskMode, 221 } = options; 222 223 validateString(name, 'options.name'); 224 if (origin !== undefined) 225 validateString(origin, 'options.origin'); 226 if (codeGeneration !== undefined) 227 validateObject(codeGeneration, 'options.codeGeneration'); 228 229 let strings = true; 230 let wasm = true; 231 if (codeGeneration !== undefined) { 232 ({ strings = true, wasm = true } = codeGeneration); 233 validateBoolean(strings, 'options.codeGeneration.strings'); 234 validateBoolean(wasm, 'options.codeGeneration.wasm'); 235 } 236 237 validateOneOf(microtaskMode, 238 'options.microtaskMode', 239 ['afterEvaluate', undefined]); 240 const microtaskQueue = microtaskMode === 'afterEvaluate' ? 241 new MicrotaskQueue() : 242 null; 243 244 makeContext(contextObject, name, origin, strings, wasm, microtaskQueue); 245 return contextObject; 246} 247 248function createScript(code, options) { 249 return new Script(code, options); 250} 251 252// Remove all SIGINT listeners and re-attach them after the wrapped function 253// has executed, so that caught SIGINT are handled by the listeners again. 254function sigintHandlersWrap(fn, thisArg, argsArray) { 255 const sigintListeners = process.rawListeners('SIGINT'); 256 257 process.removeAllListeners('SIGINT'); 258 259 try { 260 return ReflectApply(fn, thisArg, argsArray); 261 } finally { 262 // Add using the public methods so that the `newListener` handler of 263 // process can re-attach the listeners. 264 ArrayPrototypeForEach(sigintListeners, (listener) => { 265 process.addListener('SIGINT', listener); 266 }); 267 } 268} 269 270function runInContext(code, contextifiedObject, options) { 271 validateContext(contextifiedObject); 272 if (typeof options === 'string') { 273 options = { 274 filename: options, 275 [kParsingContext]: contextifiedObject, 276 }; 277 } else { 278 options = { ...options, [kParsingContext]: contextifiedObject }; 279 } 280 return createScript(code, options) 281 .runInContext(contextifiedObject, options); 282} 283 284function runInNewContext(code, contextObject, options) { 285 if (typeof options === 'string') { 286 options = { filename: options }; 287 } 288 contextObject = createContext(contextObject, getContextOptions(options)); 289 options = { ...options, [kParsingContext]: contextObject }; 290 return createScript(code, options).runInNewContext(contextObject, options); 291} 292 293function runInThisContext(code, options) { 294 if (typeof options === 'string') { 295 options = { filename: options }; 296 } 297 return createScript(code, options).runInThisContext(options); 298} 299 300function compileFunction(code, params, options = kEmptyObject) { 301 validateString(code, 'code'); 302 if (params !== undefined) { 303 validateStringArray(params, 'params'); 304 } 305 const { 306 filename = '', 307 columnOffset = 0, 308 lineOffset = 0, 309 cachedData = undefined, 310 produceCachedData = false, 311 parsingContext = undefined, 312 contextExtensions = [], 313 importModuleDynamically, 314 } = options; 315 316 validateString(filename, 'options.filename'); 317 validateInt32(columnOffset, 'options.columnOffset'); 318 validateInt32(lineOffset, 'options.lineOffset'); 319 if (cachedData !== undefined) 320 validateBuffer(cachedData, 'options.cachedData'); 321 validateBoolean(produceCachedData, 'options.produceCachedData'); 322 if (parsingContext !== undefined) { 323 if ( 324 typeof parsingContext !== 'object' || 325 parsingContext === null || 326 !isContext(parsingContext) 327 ) { 328 throw new ERR_INVALID_ARG_TYPE( 329 'options.parsingContext', 330 'Context', 331 parsingContext, 332 ); 333 } 334 } 335 validateArray(contextExtensions, 'options.contextExtensions'); 336 ArrayPrototypeForEach(contextExtensions, (extension, i) => { 337 const name = `options.contextExtensions[${i}]`; 338 validateObject(extension, name, { __proto__: null, nullable: true }); 339 }); 340 341 const hostDefinedOptionId = 342 getHostDefinedOptionId(importModuleDynamically, filename); 343 344 return internalCompileFunction( 345 code, filename, lineOffset, columnOffset, 346 cachedData, produceCachedData, parsingContext, contextExtensions, 347 params, hostDefinedOptionId, importModuleDynamically, 348 ).function; 349} 350 351const measureMemoryModes = { 352 summary: constants.measureMemory.mode.SUMMARY, 353 detailed: constants.measureMemory.mode.DETAILED, 354}; 355 356const measureMemoryExecutions = { 357 default: constants.measureMemory.execution.DEFAULT, 358 eager: constants.measureMemory.execution.EAGER, 359}; 360 361function measureMemory(options = kEmptyObject) { 362 emitExperimentalWarning('vm.measureMemory'); 363 validateObject(options, 'options'); 364 const { mode = 'summary', execution = 'default' } = options; 365 validateOneOf(mode, 'options.mode', ['summary', 'detailed']); 366 validateOneOf(execution, 'options.execution', ['default', 'eager']); 367 const result = _measureMemory(measureMemoryModes[mode], 368 measureMemoryExecutions[execution]); 369 if (result === undefined) { 370 return PromiseReject(new ERR_CONTEXT_NOT_INITIALIZED()); 371 } 372 return result; 373} 374 375module.exports = { 376 Script, 377 createContext, 378 createScript, 379 runInContext, 380 runInNewContext, 381 runInThisContext, 382 isContext, 383 compileFunction, 384 measureMemory, 385}; 386 387// The vm module is patched to include vm.Module, vm.SourceTextModule 388// and vm.SyntheticModule in the pre-execution phase when 389// --experimental-vm-modules is on. 390