// 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 { ArrayPrototypeForEach, ObjectFreeze, PromiseReject, ReflectApply, Symbol, } = primordials; const { ContextifyScript, makeContext, constants, measureMemory: _measureMemory, } = internalBinding('contextify'); const { ERR_CONTEXT_NOT_INITIALIZED, ERR_INVALID_ARG_TYPE, } = require('internal/errors').codes; const { validateArray, validateBoolean, validateBuffer, validateInt32, validateOneOf, validateObject, validateString, validateStringArray, validateUint32, kValidateObjectAllowArray, kValidateObjectAllowNullable, } = require('internal/validators'); const { emitExperimentalWarning, kEmptyObject, kVmBreakFirstLineSymbol, } = require('internal/util'); const { getHostDefinedOptionId, internalCompileFunction, isContext: _isContext, registerImportModuleDynamically, } = require('internal/vm'); const { vm_dynamic_import_main_context_default, vm_context_no_contextify, } = internalBinding('symbols'); const kParsingContext = Symbol('script parsing context'); /** * Check if object is a context object created by vm.createContext(). * @throws {TypeError} If object is not an object in the first place, throws TypeError. * @param {object} object Object to check. * @returns {boolean} */ function isContext(object) { validateObject(object, 'object', kValidateObjectAllowArray); return _isContext(object); } class Script extends ContextifyScript { constructor(code, options = kEmptyObject) { code = `${code}`; if (typeof options === 'string') { options = { filename: options }; } else { validateObject(options, 'options'); } const { filename = 'evalmachine.', 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) { validateBuffer(cachedData, 'options.cachedData'); } validateBoolean(produceCachedData, 'options.produceCachedData'); const hostDefinedOptionId = getHostDefinedOptionId(importModuleDynamically, filename); // 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, hostDefinedOptionId); } catch (e) { throw e; /* node-do-not-add-exception-line */ } registerImportModuleDynamically(this, importModuleDynamically); } runInThisContext(options) { const { breakOnSigint, args } = getRunInContextArgs(null, options); if (breakOnSigint && process.listenerCount('SIGINT') > 0) { return sigintHandlersWrap(super.runInContext, this, args); } return ReflectApply(super.runInContext, this, args); } runInContext(contextifiedObject, options) { validateContext(contextifiedObject); const { breakOnSigint, args } = getRunInContextArgs( contextifiedObject, options, ); if (breakOnSigint && process.listenerCount('SIGINT') > 0) { return sigintHandlersWrap(super.runInContext, this, args); } return ReflectApply(super.runInContext, this, args); } runInNewContext(contextObject, options) { const context = createContext(contextObject, getContextOptions(options)); return this.runInContext(context, options); } } function validateContext(contextifiedObject) { if (!isContext(contextifiedObject)) { throw new ERR_INVALID_ARG_TYPE('contextifiedObject', 'vm.Context', contextifiedObject); } } function getRunInContextArgs(contextifiedObject, options = kEmptyObject) { validateObject(options, '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; validateBoolean(displayErrors, 'options.displayErrors'); validateBoolean(breakOnSigint, 'options.breakOnSigint'); return { breakOnSigint, args: [ contextifiedObject, timeout, displayErrors, breakOnSigint, breakFirstLine, ], }; } function getContextOptions(options) { if (!options) return {}; const contextOptions = { name: options.contextName, origin: options.contextOrigin, codeGeneration: undefined, microtaskMode: options.microtaskMode, }; if (contextOptions.name !== undefined) validateString(contextOptions.name, 'options.contextName'); if (contextOptions.origin !== undefined) validateString(contextOptions.origin, 'options.contextOrigin'); if (options.contextCodeGeneration !== undefined) { validateObject(options.contextCodeGeneration, 'options.contextCodeGeneration'); const { strings, wasm } = options.contextCodeGeneration; if (strings !== undefined) validateBoolean(strings, 'options.contextCodeGeneration.strings'); if (wasm !== undefined) validateBoolean(wasm, 'options.contextCodeGeneration.wasm'); contextOptions.codeGeneration = { strings, wasm }; } if (options.microtaskMode !== undefined) validateString(options.microtaskMode, 'options.microtaskMode'); return contextOptions; } let defaultContextNameIndex = 1; function createContext(contextObject = {}, options = kEmptyObject) { if (contextObject !== vm_context_no_contextify && isContext(contextObject)) { return contextObject; } validateObject(options, 'options'); const { name = `VM Context ${defaultContextNameIndex++}`, origin, codeGeneration, microtaskMode, importModuleDynamically, } = options; validateString(name, 'options.name'); if (origin !== undefined) validateString(origin, 'options.origin'); if (codeGeneration !== undefined) validateObject(codeGeneration, 'options.codeGeneration'); let strings = true; let wasm = true; if (codeGeneration !== undefined) { ({ strings = true, wasm = true } = codeGeneration); validateBoolean(strings, 'options.codeGeneration.strings'); validateBoolean(wasm, 'options.codeGeneration.wasm'); } validateOneOf(microtaskMode, 'options.microtaskMode', ['afterEvaluate', undefined]); const microtaskQueue = (microtaskMode === 'afterEvaluate'); const hostDefinedOptionId = getHostDefinedOptionId(importModuleDynamically, name); const result = makeContext(contextObject, name, origin, strings, wasm, microtaskQueue, hostDefinedOptionId); // Register the context scope callback after the context was initialized. registerImportModuleDynamically(result, importModuleDynamically); return result; } 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 ReflectApply(fn, thisArg, argsArray); } finally { // Add using the public methods so that the `newListener` handler of // process can re-attach the listeners. ArrayPrototypeForEach(sigintListeners, (listener) => { process.addListener('SIGINT', listener); }); } } function runInContext(code, contextifiedObject, options) { validateContext(contextifiedObject); if (typeof options === 'string') { options = { filename: options, [kParsingContext]: contextifiedObject, }; } else { options = { ...options, [kParsingContext]: contextifiedObject }; } return createScript(code, options) .runInContext(contextifiedObject, options); } function runInNewContext(code, contextObject, options) { if (typeof options === 'string') { options = { filename: options }; } contextObject = createContext(contextObject, getContextOptions(options)); options = { ...options, [kParsingContext]: contextObject }; return createScript(code, options).runInNewContext(contextObject, options); } function runInThisContext(code, options) { if (typeof options === 'string') { options = { filename: options }; } return createScript(code, options).runInThisContext(options); } function compileFunction(code, params, options = kEmptyObject) { validateString(code, 'code'); if (params !== undefined) { validateStringArray(params, 'params'); } const { filename = '', columnOffset = 0, lineOffset = 0, cachedData = undefined, produceCachedData = false, parsingContext = undefined, contextExtensions = [], importModuleDynamically, } = options; validateString(filename, 'options.filename'); validateInt32(columnOffset, 'options.columnOffset'); validateInt32(lineOffset, 'options.lineOffset'); if (cachedData !== undefined) validateBuffer(cachedData, 'options.cachedData'); validateBoolean(produceCachedData, 'options.produceCachedData'); if (parsingContext !== undefined) { if ( typeof parsingContext !== 'object' || parsingContext === null || !isContext(parsingContext) ) { throw new ERR_INVALID_ARG_TYPE( 'options.parsingContext', 'Context', parsingContext, ); } } validateArray(contextExtensions, 'options.contextExtensions'); ArrayPrototypeForEach(contextExtensions, (extension, i) => { const name = `options.contextExtensions[${i}]`; validateObject(extension, name, kValidateObjectAllowNullable); }); const hostDefinedOptionId = getHostDefinedOptionId(importModuleDynamically, filename); return internalCompileFunction( code, filename, lineOffset, columnOffset, cachedData, produceCachedData, parsingContext, contextExtensions, params, hostDefinedOptionId, importModuleDynamically, ).function; } const measureMemoryModes = { summary: constants.measureMemory.mode.SUMMARY, detailed: constants.measureMemory.mode.DETAILED, }; const measureMemoryExecutions = { default: constants.measureMemory.execution.DEFAULT, eager: constants.measureMemory.execution.EAGER, }; function measureMemory(options = kEmptyObject) { emitExperimentalWarning('vm.measureMemory'); validateObject(options, 'options'); const { mode = 'summary', execution = 'default' } = options; validateOneOf(mode, 'options.mode', ['summary', 'detailed']); validateOneOf(execution, 'options.execution', ['default', 'eager']); const result = _measureMemory(measureMemoryModes[mode], measureMemoryExecutions[execution]); if (result === undefined) { return PromiseReject(new ERR_CONTEXT_NOT_INITIALIZED()); } return result; } const vmConstants = { __proto__: null, USE_MAIN_CONTEXT_DEFAULT_LOADER: vm_dynamic_import_main_context_default, DONT_CONTEXTIFY: vm_context_no_contextify, }; ObjectFreeze(vmConstants); module.exports = { Script, createContext, createScript, runInContext, runInNewContext, runInThisContext, isContext, compileFunction, measureMemory, constants: vmConstants, }; // The vm module is patched to include vm.Module, vm.SourceTextModule // and vm.SyntheticModule in the pre-execution phase when // --experimental-vm-modules is on.