| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| 'use strict'; |
| |
| const { Array, ArrayPrototype } = primordials; |
| |
| const { |
| ContextifyScript, |
| makeContext, |
| isContext: _isContext, |
| compileFunction: _compileFunction |
| } = internalBinding('contextify'); |
| const { |
| ERR_INVALID_ARG_TYPE, |
| } = require('internal/errors').codes; |
| const { |
| isArrayBufferView, |
| } = require('internal/util/types'); |
| const { |
| validateInt32, |
| validateUint32, |
| validateString |
| } = require('internal/validators'); |
| const { kVmBreakFirstLineSymbol } = require('internal/util'); |
| const kParsingContext = Symbol('script parsing context'); |
| |
| class Script extends ContextifyScript { |
| constructor(code, options = {}) { |
| code = `${code}`; |
| if (typeof options === 'string') { |
| options = { filename: options }; |
| } else if (typeof options !== 'object' || options === null) { |
| throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); |
| } |
| |
| const { |
| filename = 'evalmachine.<anonymous>', |
| lineOffset = 0, |
| columnOffset = 0, |
| cachedData, |
| produceCachedData = false, |
| importModuleDynamically, |
| [kParsingContext]: parsingContext, |
| } = options; |
| |
| validateString(filename, 'options.filename'); |
| validateInt32(lineOffset, 'options.lineOffset'); |
| validateInt32(columnOffset, 'options.columnOffset'); |
| if (cachedData !== undefined && !isArrayBufferView(cachedData)) { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'options.cachedData', |
| ['Buffer', 'TypedArray', 'DataView'], |
| cachedData |
| ); |
| } |
| if (typeof produceCachedData !== 'boolean') { |
| throw new ERR_INVALID_ARG_TYPE('options.produceCachedData', 'boolean', |
| produceCachedData); |
| } |
| |
| // Calling `ReThrow()` on a native TryCatch does not generate a new |
| // abort-on-uncaught-exception check. A dummy try/catch in JS land |
| // protects against that. |
| try { // eslint-disable-line no-useless-catch |
| super(code, |
| filename, |
| lineOffset, |
| columnOffset, |
| cachedData, |
| produceCachedData, |
| parsingContext); |
| } catch (e) { |
| throw e; /* node-do-not-add-exception-line */ |
| } |
| |
| if (importModuleDynamically !== undefined) { |
| if (typeof importModuleDynamically !== 'function') { |
| throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically', |
| 'function', |
| importModuleDynamically); |
| } |
| const { importModuleDynamicallyWrap } = |
| require('internal/vm/module'); |
| const { callbackMap } = internalBinding('module_wrap'); |
| callbackMap.set(this, { |
| importModuleDynamically: |
| importModuleDynamicallyWrap(importModuleDynamically), |
| }); |
| } |
| } |
| |
| runInThisContext(options) { |
| const { breakOnSigint, args } = getRunInContextArgs(options); |
| if (breakOnSigint && process.listenerCount('SIGINT') > 0) { |
| return sigintHandlersWrap(super.runInThisContext, this, args); |
| } else { |
| return super.runInThisContext(...args); |
| } |
| } |
| |
| runInContext(contextifiedSandbox, options) { |
| validateContext(contextifiedSandbox); |
| const { breakOnSigint, args } = getRunInContextArgs(options); |
| if (breakOnSigint && process.listenerCount('SIGINT') > 0) { |
| return sigintHandlersWrap(super.runInContext, this, |
| [contextifiedSandbox, ...args]); |
| } else { |
| return super.runInContext(contextifiedSandbox, ...args); |
| } |
| } |
| |
| runInNewContext(sandbox, options) { |
| const context = createContext(sandbox, getContextOptions(options)); |
| return this.runInContext(context, options); |
| } |
| } |
| |
| function validateContext(sandbox) { |
| if (typeof sandbox !== 'object' || sandbox === null) { |
| throw new ERR_INVALID_ARG_TYPE('contextifiedSandbox', 'Object', sandbox); |
| } |
| if (!_isContext(sandbox)) { |
| throw new ERR_INVALID_ARG_TYPE('contextifiedSandbox', 'vm.Context', |
| sandbox); |
| } |
| } |
| |
| function validateBool(prop, propName) { |
| if (prop !== undefined && typeof prop !== 'boolean') |
| throw new ERR_INVALID_ARG_TYPE(propName, 'boolean', prop); |
| } |
| |
| function validateObject(prop, propName) { |
| if (prop !== undefined && (typeof prop !== 'object' || prop === null)) |
| throw new ERR_INVALID_ARG_TYPE(propName, 'Object', prop); |
| } |
| |
| function getRunInContextArgs(options = {}) { |
| if (typeof options !== 'object' || options === null) { |
| throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); |
| } |
| |
| let timeout = options.timeout; |
| if (timeout === undefined) { |
| timeout = -1; |
| } else { |
| validateUint32(timeout, 'options.timeout', true); |
| } |
| |
| const { |
| displayErrors = true, |
| breakOnSigint = false, |
| [kVmBreakFirstLineSymbol]: breakFirstLine = false, |
| } = options; |
| |
| if (typeof displayErrors !== 'boolean') { |
| throw new ERR_INVALID_ARG_TYPE('options.displayErrors', 'boolean', |
| displayErrors); |
| } |
| if (typeof breakOnSigint !== 'boolean') { |
| throw new ERR_INVALID_ARG_TYPE('options.breakOnSigint', 'boolean', |
| breakOnSigint); |
| } |
| |
| return { |
| breakOnSigint, |
| args: [timeout, displayErrors, breakOnSigint, breakFirstLine] |
| }; |
| } |
| |
| function getContextOptions(options) { |
| if (options) { |
| validateObject(options.contextCodeGeneration, |
| 'options.contextCodeGeneration'); |
| const contextOptions = { |
| name: options.contextName, |
| origin: options.contextOrigin, |
| codeGeneration: typeof options.contextCodeGeneration === 'object' ? { |
| strings: options.contextCodeGeneration.strings, |
| wasm: options.contextCodeGeneration.wasm, |
| } : undefined, |
| }; |
| if (contextOptions.name !== undefined) |
| validateString(contextOptions.name, 'options.contextName'); |
| if (contextOptions.origin !== undefined) |
| validateString(contextOptions.origin, 'options.contextOrigin'); |
| if (contextOptions.codeGeneration) { |
| validateBool(contextOptions.codeGeneration.strings, |
| 'options.contextCodeGeneration.strings'); |
| validateBool(contextOptions.codeGeneration.wasm, |
| 'options.contextCodeGeneration.wasm'); |
| } |
| return contextOptions; |
| } |
| return {}; |
| } |
| |
| function isContext(sandbox) { |
| if (typeof sandbox !== 'object' || sandbox === null) { |
| throw new ERR_INVALID_ARG_TYPE('sandbox', 'Object', sandbox); |
| } |
| return _isContext(sandbox); |
| } |
| |
| let defaultContextNameIndex = 1; |
| function createContext(sandbox = {}, options = {}) { |
| if (isContext(sandbox)) { |
| return sandbox; |
| } |
| |
| if (typeof options !== 'object' || options === null) { |
| throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); |
| } |
| |
| const { |
| name = `VM Context ${defaultContextNameIndex++}`, |
| origin, |
| codeGeneration |
| } = options; |
| |
| validateString(name, 'options.name'); |
| if (origin !== undefined) |
| validateString(origin, 'options.origin'); |
| validateObject(codeGeneration, 'options.codeGeneration'); |
| |
| let strings = true; |
| let wasm = true; |
| if (codeGeneration !== undefined) { |
| ({ strings = true, wasm = true } = codeGeneration); |
| validateBool(strings, 'options.codeGeneration.strings'); |
| validateBool(wasm, 'options.codeGeneration.wasm'); |
| } |
| |
| makeContext(sandbox, name, origin, strings, wasm); |
| return sandbox; |
| } |
| |
| function createScript(code, options) { |
| return new Script(code, options); |
| } |
| |
| // Remove all SIGINT listeners and re-attach them after the wrapped function |
| // has executed, so that caught SIGINT are handled by the listeners again. |
| function sigintHandlersWrap(fn, thisArg, argsArray) { |
| const sigintListeners = process.rawListeners('SIGINT'); |
| |
| process.removeAllListeners('SIGINT'); |
| |
| try { |
| return fn.apply(thisArg, argsArray); |
| } finally { |
| // Add using the public methods so that the `newListener` handler of |
| // process can re-attach the listeners. |
| for (const listener of sigintListeners) { |
| process.addListener('SIGINT', listener); |
| } |
| } |
| } |
| |
| function runInContext(code, contextifiedSandbox, options) { |
| validateContext(contextifiedSandbox); |
| if (typeof options === 'string') { |
| options = { |
| filename: options, |
| [kParsingContext]: contextifiedSandbox |
| }; |
| } else { |
| options = { ...options, [kParsingContext]: contextifiedSandbox }; |
| } |
| return createScript(code, options) |
| .runInContext(contextifiedSandbox, options); |
| } |
| |
| function runInNewContext(code, sandbox, options) { |
| if (typeof options === 'string') { |
| options = { filename: options }; |
| } |
| sandbox = createContext(sandbox, getContextOptions(options)); |
| options = { ...options, [kParsingContext]: sandbox }; |
| return createScript(code, options).runInNewContext(sandbox, options); |
| } |
| |
| function runInThisContext(code, options) { |
| if (typeof options === 'string') { |
| options = { filename: options }; |
| } |
| return createScript(code, options).runInThisContext(options); |
| } |
| |
| function compileFunction(code, params, options = {}) { |
| validateString(code, 'code'); |
| if (params !== undefined) { |
| if (!Array.isArray(params)) { |
| throw new ERR_INVALID_ARG_TYPE('params', 'Array', params); |
| } |
| ArrayPrototype.forEach(params, |
| (param, i) => validateString(param, `params[${i}]`)); |
| } |
| |
| const { |
| filename = '', |
| columnOffset = 0, |
| lineOffset = 0, |
| cachedData = undefined, |
| produceCachedData = false, |
| parsingContext = undefined, |
| contextExtensions = [], |
| } = options; |
| |
| validateString(filename, 'options.filename'); |
| validateUint32(columnOffset, 'options.columnOffset'); |
| validateUint32(lineOffset, 'options.lineOffset'); |
| if (cachedData !== undefined && !isArrayBufferView(cachedData)) { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'options.cachedData', |
| ['Buffer', 'TypedArray', 'DataView'], |
| cachedData |
| ); |
| } |
| if (typeof produceCachedData !== 'boolean') { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'options.produceCachedData', |
| 'boolean', |
| produceCachedData |
| ); |
| } |
| if (parsingContext !== undefined) { |
| if ( |
| typeof parsingContext !== 'object' || |
| parsingContext === null || |
| !isContext(parsingContext) |
| ) { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'options.parsingContext', |
| 'Context', |
| parsingContext |
| ); |
| } |
| } |
| if (!Array.isArray(contextExtensions)) { |
| throw new ERR_INVALID_ARG_TYPE( |
| 'options.contextExtensions', |
| 'Array', |
| contextExtensions |
| ); |
| } |
| ArrayPrototype.forEach(contextExtensions, (extension, i) => { |
| if (typeof extension !== 'object') { |
| throw new ERR_INVALID_ARG_TYPE( |
| `options.contextExtensions[${i}]`, |
| 'object', |
| extension |
| ); |
| } |
| }); |
| |
| const result = _compileFunction( |
| code, |
| filename, |
| lineOffset, |
| columnOffset, |
| cachedData, |
| produceCachedData, |
| parsingContext, |
| contextExtensions, |
| params |
| ); |
| |
| if (produceCachedData) { |
| result.function.cachedDataProduced = result.cachedDataProduced; |
| } |
| |
| if (result.cachedData) { |
| result.function.cachedData = result.cachedData; |
| } |
| |
| return result.function; |
| } |
| |
| |
| module.exports = { |
| Script, |
| createContext, |
| createScript, |
| runInContext, |
| runInNewContext, |
| runInThisContext, |
| isContext, |
| compileFunction, |
| }; |
| |
| if (require('internal/options').getOptionValue('--experimental-vm-modules')) { |
| const { |
| Module, SourceTextModule, SyntheticModule, |
| } = require('internal/vm/module'); |
| module.exports.Module = Module; |
| module.exports.SourceTextModule = SourceTextModule; |
| module.exports.SyntheticModule = SyntheticModule; |
| } |