mirror of https://github.com/nodejs/node.git
419 lines
13 KiB
JavaScript
419 lines
13 KiB
JavaScript
// 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.<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) {
|
|
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.
|