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