From 6b1e5659e1a8a6716263365c262c51a20e4c7203 Mon Sep 17 00:00:00 2001 From: Boris Staal Date: Sun, 9 Apr 2023 15:20:49 -0500 Subject: [PATCH] Proxy support --- dist/restore-only/bridge.js | 1010 +++++++++++++++++++++++ dist/restore-only/events.js | 977 ++++++++++++++++++++++ dist/restore-only/setup-node-sandbox.js | 469 +++++++++++ dist/restore-only/setup-sandbox.js | 474 +++++++++++ dist/restore/bridge.js | 1010 +++++++++++++++++++++++ dist/restore/events.js | 977 ++++++++++++++++++++++ dist/restore/setup-node-sandbox.js | 469 +++++++++++ dist/restore/setup-sandbox.js | 474 +++++++++++ dist/save-only/bridge.js | 1010 +++++++++++++++++++++++ dist/save-only/events.js | 977 ++++++++++++++++++++++ dist/save-only/setup-node-sandbox.js | 469 +++++++++++ dist/save-only/setup-sandbox.js | 474 +++++++++++ dist/save/bridge.js | 1010 +++++++++++++++++++++++ dist/save/events.js | 977 ++++++++++++++++++++++ dist/save/setup-node-sandbox.js | 469 +++++++++++ dist/save/setup-sandbox.js | 474 +++++++++++ 16 files changed, 11720 insertions(+) create mode 100644 dist/restore-only/bridge.js create mode 100644 dist/restore-only/events.js create mode 100644 dist/restore-only/setup-node-sandbox.js create mode 100644 dist/restore-only/setup-sandbox.js create mode 100644 dist/restore/bridge.js create mode 100644 dist/restore/events.js create mode 100644 dist/restore/setup-node-sandbox.js create mode 100644 dist/restore/setup-sandbox.js create mode 100644 dist/save-only/bridge.js create mode 100644 dist/save-only/events.js create mode 100644 dist/save-only/setup-node-sandbox.js create mode 100644 dist/save-only/setup-sandbox.js create mode 100644 dist/save/bridge.js create mode 100644 dist/save/events.js create mode 100644 dist/save/setup-node-sandbox.js create mode 100644 dist/save/setup-sandbox.js diff --git a/dist/restore-only/bridge.js b/dist/restore-only/bridge.js new file mode 100644 index 0000000..8690b51 --- /dev/null +++ b/dist/restore-only/bridge.js @@ -0,0 +1,1010 @@ +'use strict'; + +/** + * __ ___ ____ _ _ ___ _ _ ____ + * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| + * \ \ /\ / / _ \ | |_) | \| || || \| | | _ + * \ V V / ___ \| _ <| |\ || || |\ | |_| | + * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| + * + * This file is critical for vm2. It implements the bridge between the host and the sandbox. + * If you do not know exactly what you are doing, you should NOT edit this file. + * + * The file is loaded in the host and sandbox to handle objects in both directions. + * This is done to ensure that RangeErrors are from the correct context. + * The boundary between the sandbox and host might throw RangeErrors from both contexts. + * Therefore, thisFromOther and friends can handle objects from both domains. + * + * Method parameters have comments to tell from which context they came. + * + */ + +const globalsList = [ + 'Number', + 'String', + 'Boolean', + 'Date', + 'RegExp', + 'Map', + 'WeakMap', + 'Set', + 'WeakSet', + 'Promise', + 'Function' +]; + +const errorsList = [ + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'EvalError', + 'URIError', + 'Error' +]; + +const OPNA = 'Operation not allowed on contextified object.'; + +const thisGlobalPrototypes = { + __proto__: null, + Object: Object.prototype, + Array: Array.prototype +}; + +for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +const { + getPrototypeOf: thisReflectGetPrototypeOf, + setPrototypeOf: thisReflectSetPrototypeOf, + defineProperty: thisReflectDefineProperty, + deleteProperty: thisReflectDeleteProperty, + getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, + isExtensible: thisReflectIsExtensible, + preventExtensions: thisReflectPreventExtensions, + apply: thisReflectApply, + construct: thisReflectConstruct, + set: thisReflectSet, + get: thisReflectGet, + has: thisReflectHas, + ownKeys: thisReflectOwnKeys, + enumerate: thisReflectEnumerate, +} = Reflect; + +const thisObject = Object; +const { + freeze: thisObjectFreeze, + prototype: thisObjectPrototype +} = thisObject; +const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; +const ThisProxy = Proxy; +const ThisWeakMap = WeakMap; +const { + get: thisWeakMapGet, + set: thisWeakMapSet +} = ThisWeakMap.prototype; +const ThisMap = Map; +const thisMapGet = ThisMap.prototype.get; +const thisMapSet = ThisMap.prototype.set; +const thisFunction = Function; +const thisFunctionBind = thisFunction.prototype.bind; +const thisArrayIsArray = Array.isArray; +const thisErrorCaptureStackTrace = Error.captureStackTrace; + +const thisSymbolToString = Symbol.prototype.toString; +const thisSymbolToStringTag = Symbol.toStringTag; +const thisSymbolIterator = Symbol.iterator; +const thisSymbolNodeJSUtilInspectCustom = Symbol.for('nodejs.util.inspect.custom'); + +/** + * VMError. + * + * @public + * @extends {Error} + */ +class VMError extends Error { + + /** + * Create VMError instance. + * + * @public + * @param {string} message - Error message. + * @param {string} code - Error code. + */ + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + thisErrorCaptureStackTrace(this, this.constructor); + } +} + +thisGlobalPrototypes['VMError'] = VMError.prototype; + +function thisUnexpected() { + return new VMError('Unexpected'); +} + +if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); + +function thisSafeGetOwnPropertyDescriptor(obj, key) { + const desc = thisReflectGetOwnPropertyDescriptor(obj, key); + if (!desc) return desc; + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + return desc; +} + +function thisThrowCallerCalleeArgumentsAccess(key) { + 'use strict'; + thisThrowCallerCalleeArgumentsAccess[key]; + return thisUnexpected(); +} + +function thisIdMapping(factory, other) { + return other; +} + +const thisThrowOnKeyAccessHandler = thisObjectFreeze({ + __proto__: null, + get(target, key, receiver) { + if (typeof key === 'symbol') { + key = thisReflectApply(thisSymbolToString, key, []); + } + throw new VMError(`Unexpected access to key '${key}'`); + } +}); + +const emptyForzenObject = thisObjectFreeze({ + __proto__: null +}); + +const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); + +function SafeBase() {} + +if (!thisReflectDefineProperty(SafeBase, 'prototype', { + __proto__: null, + value: thisThrowOnKeyAccess +})) throw thisUnexpected(); + +function SHARED_FUNCTION() {} + +const TEST_PROXY_HANDLER = thisObjectFreeze({ + __proto__: thisThrowOnKeyAccess, + construct() { + return this; + } +}); + +function thisIsConstructor(obj) { + // Note: obj@any(unsafe) + const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); + try { + // eslint-disable-next-line no-new + new Func(); + return true; + } catch (e) { + return false; + } +} + +function thisCreateTargetObject(obj, proto) { + // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) + let base; + if (typeof obj === 'function') { + if (thisIsConstructor(obj)) { + // Bind the function since bound functions do not have a prototype property. + base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); + } else { + base = () => {}; + } + } else if (thisArrayIsArray(obj)) { + base = []; + } else { + return {__proto__: proto}; + } + if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); + return base; +} + +function createBridge(otherInit, registerProxy) { + + const mappingOtherToThis = new ThisWeakMap(); + const protoMappings = new ThisMap(); + const protoName = new ThisMap(); + + function thisAddProtoMapping(proto, other, name) { + // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => thisProxyOther(factory, object, proto)]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + + function thisAddProtoMappingFactory(protoFactory, other, name) { + // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + let proto; + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => { + if (!proto) { + proto = protoFactory(); + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + return thisProxyOther(factory, object, proto); + }]); + } + + const result = { + __proto__: null, + globalPrototypes: thisGlobalPrototypes, + safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, + fromArguments: thisFromOtherArguments, + from: thisFromOther, + fromWithFactory: thisFromOtherWithFactory, + ensureThis: thisEnsureThis, + mapping: mappingOtherToThis, + connect: thisConnect, + reflectSet: thisReflectSet, + reflectGet: thisReflectGet, + reflectDefineProperty: thisReflectDefineProperty, + reflectDeleteProperty: thisReflectDeleteProperty, + reflectApply: thisReflectApply, + reflectConstruct: thisReflectConstruct, + reflectHas: thisReflectHas, + reflectOwnKeys: thisReflectOwnKeys, + reflectEnumerate: thisReflectEnumerate, + reflectGetPrototypeOf: thisReflectGetPrototypeOf, + reflectIsExtensible: thisReflectIsExtensible, + reflectPreventExtensions: thisReflectPreventExtensions, + objectHasOwnProperty: thisObjectHasOwnProperty, + weakMapSet: thisWeakMapSet, + addProtoMapping: thisAddProtoMapping, + addProtoMappingFactory: thisAddProtoMappingFactory, + defaultFactory, + protectedFactory, + readonlyFactory, + VMError + }; + + const isHost = typeof otherInit !== 'object'; + + if (isHost) { + otherInit = otherInit(result, registerProxy); + } + + result.other = otherInit; + + const { + globalPrototypes: otherGlobalPrototypes, + safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, + fromArguments: otherFromThisArguments, + from: otherFromThis, + mapping: mappingThisToOther, + reflectSet: otherReflectSet, + reflectGet: otherReflectGet, + reflectDefineProperty: otherReflectDefineProperty, + reflectDeleteProperty: otherReflectDeleteProperty, + reflectApply: otherReflectApply, + reflectConstruct: otherReflectConstruct, + reflectHas: otherReflectHas, + reflectOwnKeys: otherReflectOwnKeys, + reflectEnumerate: otherReflectEnumerate, + reflectGetPrototypeOf: otherReflectGetPrototypeOf, + reflectIsExtensible: otherReflectIsExtensible, + reflectPreventExtensions: otherReflectPreventExtensions, + objectHasOwnProperty: otherObjectHasOwnProperty, + weakMapSet: otherWeakMapSet + } = otherInit; + + function thisOtherHasOwnProperty(object, key) { + // Note: object@other(safe) key@prim throws@this(unsafe) + try { + return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + function thisDefaultGet(handler, object, key, desc) { + // Note: object@other(unsafe) key@prim desc@other(safe) + let ret; // @other(unsafe) + if (desc.get || desc.set) { + const getter = desc.get; + if (!getter) return undefined; + try { + ret = otherReflectApply(getter, object, [key]); + } catch (e) { + throw thisFromOtherForThrow(e); + } + } else { + ret = desc.value; + } + return handler.fromOtherWithContext(ret); + } + + function otherFromThisIfAvailable(to, from, key) { + // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) + if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; + try { + to[key] = otherFromThis(from[key]); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return true; + } + + class BaseHandler extends SafeBase { + + constructor(object) { + // Note: object@other(unsafe) throws@this(unsafe) + super(); + this.objectWrapper = () => object; + } + + getObject() { + return this.objectWrapper(); + } + + getFactory() { + return defaultFactory; + } + + fromOtherWithContext(other) { + // Note: other@other(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(this.getFactory(), other); + } + + doPreventExtensions(target, object, factory) { + // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) + let keys; // @other(safe-array-of-prim) + try { + keys = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; // @prim + let desc; + try { + desc = otherSafeGetOwnPropertyDescriptor(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!desc) continue; + if (!desc.configurable) { + const current = thisSafeGetOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = this.fromOtherWithContext(desc.get); + desc.set = this.fromOtherWithContext(desc.set); + } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { + desc.value = null; + } else { + desc.value = this.fromOtherWithContext(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); + } + if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + switch (key) { + case 'constructor': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + const proto = thisReflectGetPrototypeOf(target); + return proto === null ? undefined : proto.constructor; + } + case '__proto__': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + return thisReflectGetPrototypeOf(target); + } + case thisSymbolToStringTag: + if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { + const proto = thisReflectGetPrototypeOf(target); + const name = thisReflectApply(thisMapGet, protoName, [proto]); + if (name) return name; + } + break; + case 'arguments': + case 'caller': + case 'callee': + if (typeof object === 'function' && thisOtherHasOwnProperty(object, key)) { + throw thisThrowCallerCalleeArgumentsAccess(key); + } + break; + } + let ret; // @other(unsafe) + try { + ret = otherReflectGet(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(ret); + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { + return this.setPrototypeOf(target, value); + } + try { + value = otherFromThis(value); + return otherReflectSet(object, key, value) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + getPrototypeOf(target) { + // Note: target@this(unsafe) + return thisReflectGetPrototypeOf(target); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + throw new VMError(OPNA); + } + + apply(target, context, args) { + // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + context = otherFromThis(context); + args = otherFromThisArguments(args); + ret = otherReflectApply(object, context, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(ret); + } + + construct(target, args, newTarget) { + // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + args = otherFromThisArguments(args); + ret = otherReflectConstruct(object, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); + } + + getOwnPropertyDescriptorDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; + return desc; + } + + getOwnPropertyDescriptor(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let desc; // @other(safe) + try { + desc = otherSafeGetOwnPropertyDescriptor(object, prop); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); + + if (!desc) return undefined; + + let thisDesc; + if (desc.get || desc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(desc.get), + set: this.fromOtherWithContext(desc.set), + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(desc.value), + writable: desc.writable === true, + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } + if (!thisDesc.configurable) { + const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); + if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + } + return thisDesc; + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + return desc; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + + desc = this.definePropertyDesc(target, prop, desc); + + if (!desc) return false; + + let otherDesc = {__proto__: null}; + let hasFunc = true; + let hasValue = true; + let hasBasic = true; + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); + + try { + if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; + if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { + otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); + } + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + if (!otherDesc.configurable) { + let thisDesc; + if (otherDesc.get || otherDesc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(otherDesc.get), + set: this.fromOtherWithContext(otherDesc.set), + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(otherDesc.value), + writable: otherDesc.writable, + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + return true; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectDeleteProperty(object, prop) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + has(target, key) { + // Note: target@this(unsafe) key@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectHas(object, key) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (otherReflectIsExtensible(object)) return true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return false; + } + + ownKeys(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(res); + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (!otherReflectPreventExtensions(object)) return false; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return true; + } + + enumerate(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectEnumerate(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(res); + } + + } + + BaseHandler.prototype[thisSymbolNodeJSUtilInspectCustom] = undefined; + BaseHandler.prototype[thisSymbolToStringTag] = 'VM2 Wrapper'; + BaseHandler.prototype[thisSymbolIterator] = undefined; + + function defaultFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new BaseHandler(object); + } + + class ProtectedHandler extends BaseHandler { + + getFactory() { + return protectedFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + if (typeof value === 'function') { + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }) === true; + } + return super.set(target, key, value, receiver); + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; + return desc; + } + + } + + function protectedFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ProtectedHandler(object); + } + + class ReadOnlyHandler extends BaseHandler { + + getFactory() { + return readonlyFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + return false; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + return false; + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + } + + function readonlyFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ReadOnlyHandler(object); + } + + class ReadOnlyMockHandler extends ReadOnlyHandler { + + constructor(object, mock) { + // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) + super(object); + this.mock = mock; + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + const mock = this.mock; + if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { + return mock[key]; + } + return super.get(target, key, receiver); + } + + } + + function thisFromOther(other) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(defaultFactory, other); + } + + function thisProxyOther(factory, other, proto) { + const target = thisCreateTargetObject(other, proto); + const handler = factory(other); + const proxy = new ThisProxy(target, handler); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); + registerProxy(proxy, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + if (!isHost) { + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); + return proxy; + } + const proxy2 = new ThisProxy(proxy, emptyForzenObject); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); + registerProxy(proxy2, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); + return proxy2; + } + + function thisEnsureThis(other) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { + return other; + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherForThrow(other) { + for (let loop = 0; loop < 10; loop++) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + let proto; + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(defaultFactory, other, null); + } + for (;;) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(defaultFactory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); + } + break; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + throw new VMError('Exception recursion depth'); + } + + function thisFromOtherWithFactory(factory, other, proto) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + do { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } while (proto); + return thisProxyOther(factory, other, thisObjectPrototype); + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherArguments(args) { + // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) + const arr = []; + for (let i = 0; i < args.length; i++) { + const value = thisFromOther(args[i]); + thisReflectDefineProperty(arr, i, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + return arr; + } + + function thisConnect(obj, other) { + // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); + } + + thisAddProtoMapping(thisGlobalPrototypes.Object, otherGlobalPrototypes.Object); + thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); + + for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, key); + } + + for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, 'Error'); + } + + thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); + + result.BaseHandler = BaseHandler; + result.ProtectedHandler = ProtectedHandler; + result.ReadOnlyHandler = ReadOnlyHandler; + result.ReadOnlyMockHandler = ReadOnlyMockHandler; + + return result; +} + +exports.createBridge = createBridge; +exports.VMError = VMError; diff --git a/dist/restore-only/events.js b/dist/restore-only/events.js new file mode 100644 index 0000000..624e827 --- /dev/null +++ b/dist/restore-only/events.js @@ -0,0 +1,977 @@ +// 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. + +// Modified by the vm2 team to make this a standalone module to be loaded into the sandbox. + +'use strict'; + +const host = fromhost; + +const { + Boolean, + Error, + String, + Symbol +} = globalThis; + +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; + +const ErrorCaptureStackTrace = Error.captureStackTrace; + +const NumberIsNaN = Number.isNaN; + +const ObjectCreate = Object.create; +const ObjectDefineProperty = Object.defineProperty; +const ObjectDefineProperties = Object.defineProperties; +const ObjectGetPrototypeOf = Object.getPrototypeOf; + +const SymbolFor = Symbol.for; + +function uncurryThis(func) { + return (thiz, ...args) => ReflectApply(func, thiz, args); +} + +const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); +const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); + +const kRejection = SymbolFor('nodejs.rejection'); + +function inspect(obj) { + return typeof obj === 'symbol' ? obj.toString() : `${obj}`; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function assert(what, message) { + if (!what) throw new Error(message); +} + +function E(key, msg, Base) { + return function NodeError(...args) { + const error = new Base(); + const message = ReflectApply(msg, error, args); + ObjectDefineProperties(error, { + message: { + value: message, + enumerable: false, + writable: true, + configurable: true, + }, + toString: { + value() { + return `${this.name} [${key}]: ${this.message}`; + }, + enumerable: false, + writable: true, + configurable: true, + }, + }); + error.code = key; + return error; + }; +} + + +const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', + (name, expected, actual) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (StringPrototypeEndsWith(name, ' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (ArrayPrototypeIncludes(kTypes, value)) { + ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); + } else if (RegExpPrototypeTest(classRegExp, value)) { + ArrayPrototypePush(instances, value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + ArrayPrototypePush(other, value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = ArrayPrototypeIndexOf(types, 'object'); + if (pos !== -1) { + ArrayPrototypeSplice(types, pos, 1); + ArrayPrototypePush(instances, 'Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = ArrayPrototypePop(types); + msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = ArrayPrototypePop(instances); + msg += + `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = ArrayPrototypePop(other); + msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (StringPrototypeToLowerCase(other[0]) !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; + }, TypeError); + +const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); + +const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', + (str, range, input, replaceDefaultBoolean = false) => { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean ? str : + `The value of "${str}" is out of range.`; + const received = inspect(input); + msg += ` It must be ${range}. Received ${received}`; + return msg; + }, RangeError); + +const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', + err => { + const msg = 'Unhandled error.'; + if (err === undefined) return msg; + return `${msg} (${err})`; + }, Error); + +function validateBoolean(value, name) { + if (typeof value !== 'boolean') + throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); +} + +function validateFunction(value, name) { + if (typeof value !== 'function') + throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); +} + +function validateString(value, name) { + if (typeof value !== 'string') + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); +} + +function nc(cond, e) { + return cond === undefined || cond === null ? e : cond; +} + +function oc(base, key) { + return base === undefined || base === null ? undefined : base[key]; +} + +const kCapture = Symbol('kCapture'); +const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); +const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); +const kMaxEventTargetListenersWarned = + Symbol('events.maxEventTargetListenersWarned'); + +const kIsEventTarget = SymbolFor('nodejs.event_target'); + +function isEventTarget(obj) { + return oc(oc(obj, 'constructor'), kIsEventTarget); +} + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @constructs {EventEmitter} + */ +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +module.exports = EventEmitter; +if (host.once) module.exports.once = host.once; +if (host.on) module.exports.on = host.on; +if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, 'EventEmitter.captureRejections'); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +if (host.EventEmitterReferencingAsyncResource) { + const kAsyncResource = Symbol('kAsyncResource'); + const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; + + class EventEmitterAsyncResource extends EventEmitter { + /** + * @param {{ + * name?: string, + * triggerAsyncId?: number, + * requireManualDestroy?: boolean, + * }} [options] + */ + constructor(options = undefined) { + let name; + if (typeof options === 'string') { + name = options; + options = undefined; + } else { + if (new.target === EventEmitterAsyncResource) { + validateString(oc(options, 'name'), 'options.name'); + } + name = oc(options, 'name') || new.target.name; + } + super(options); + + this[kAsyncResource] = + new EventEmitterReferencingAsyncResource(this, name, options); + } + + /** + * @param {symbol,string} event + * @param {...any} args + * @returns {boolean} + */ + emit(event, ...args) { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + const { asyncResource } = this; + ArrayPrototypeUnshift(args, super.emit, this, event); + return ReflectApply(asyncResource.runInAsyncScope, asyncResource, + args); + } + + /** + * @returns {void} + */ + emitDestroy() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + this.asyncResource.emitDestroy(); + } + + /** + * @type {number} + */ + get asyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.asyncId(); + } + + /** + * @type {number} + */ + get triggerAsyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.triggerAsyncId(); + } + + /** + * @type {EventEmitterReferencingAsyncResource} + */ + get asyncResource() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this[kAsyncResource]; + } + } + EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; +} + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, 'listener'); +} + +ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new ERR_OUT_OF_RANGE('defaultMaxListeners', + 'a non-negative number', + arg); + } + defaultMaxListeners = arg; + } +}); + +ObjectDefineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + } +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +EventEmitter.setMaxListeners = + function(n = defaultMaxListeners, ...eventTargets) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (isEventTarget(target)) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === 'function') { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + 'eventTargets', + ['EventEmitter', 'EventTarget'], + target); + } + } + } + }; + +// If you're updating this function definition, please also update any +// re-definitions, such as the one in the Domain module (lib/domain.js). +EventEmitter.init = function(opts) { + + if (this._events === undefined || + this._events === ObjectGetPrototypeOf(this)._events) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + + if (oc(opts, 'captureRejections')) { + validateBoolean(opts.captureRejections, 'options.captureRejections'); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catchable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = (type === 'error'); + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) + this.emit(kErrorMonitor, ...args); + doError = (doError && events.error === undefined); + } else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + try { + const capture = {}; + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); + } catch (e) {} + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch (e) { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = ObjectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + nc(listener.listener, listener)); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error('Possible EventEmitter memory leak detected. ' + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + 'emitter.setMaxListeners() to increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + checkListener(listener); + + const events = this._events; + if (events === undefined) + return this; + + const list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + const events = this._events; + if (events === undefined) + return this; + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else + delete events[type]; + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of ReflectOwnKeys(events)) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = ObjectCreate(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) + return []; + + const evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } + return emitter.listenerCount(type); +}; + +EventEmitter.prototype.listenerCount = listenerCount; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return ArrayPrototypeSlice(arr); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === 'function') + ret[i] = orig; + } + return ret; +} diff --git a/dist/restore-only/setup-node-sandbox.js b/dist/restore-only/setup-node-sandbox.js new file mode 100644 index 0000000..05f38ef --- /dev/null +++ b/dist/restore-only/setup-node-sandbox.js @@ -0,0 +1,469 @@ +/* global host, data, VMError */ + +'use strict'; + +const LocalError = Error; +const LocalTypeError = TypeError; +const LocalWeakMap = WeakMap; + +const { + apply: localReflectApply, + defineProperty: localReflectDefineProperty +} = Reflect; + +const { + set: localWeakMapSet, + get: localWeakMapGet +} = LocalWeakMap.prototype; + +const { + isArray: localArrayIsArray +} = Array; + +function uncurryThis(func) { + return (thiz, ...args) => localReflectApply(func, thiz, args); +} + +const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); +const localArrayPrototypePush = uncurryThis(Array.prototype.push); +const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); +const localStringPrototypeSlice = uncurryThis(String.prototype.slice); +const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); + +const { + argv: optionArgv, + env: optionEnv, + console: optionConsole, + vm, + resolver, + extensions +} = data; + +function ensureSandboxArray(a) { + return localArrayPrototypeSlice(a); +} + +const globalPaths = ensureSandboxArray(resolver.globalPaths); + +class Module { + + constructor(id, path, parent) { + this.id = id; + this.filename = id; + this.path = path; + this.parent = parent; + this.loaded = false; + this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; + this.children = []; + this.exports = {}; + } + + _updateChildren(child, isNew) { + const children = this.children; + if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { + localArrayPrototypePush(children, child); + } + } + + require(id) { + return requireImpl(this, id, false); + } + +} + +const originalRequire = Module.prototype.require; +const cacheBuiltins = {__proto__: null}; + +function requireImpl(mod, id, direct) { + if (direct && mod.require !== originalRequire) { + return mod.require(id); + } + const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); + if (localStringPrototypeStartsWith(filename, 'node:')) { + id = localStringPrototypeSlice(filename, 5); + let nmod = cacheBuiltins[id]; + if (!nmod) { + nmod = resolver.loadBuiltinModule(vm, id); + if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); + cacheBuiltins[id] = nmod; + } + return nmod; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + mod._updateChildren(cachedModule, false); + return cachedModule.exports; + } + + let nmod = cacheBuiltins[id]; + if (nmod) return nmod; + nmod = resolver.loadBuiltinModule(vm, id); + if (nmod) { + cacheBuiltins[id] = nmod; + return nmod; + } + + const path = resolver.fs.dirname(filename); + const module = new Module(filename, path, mod); + resolver.registerModule(module, filename, path, mod, direct); + mod._updateChildren(module, true); + try { + Module._cache[filename] = module; + const handler = findBestExtensionHandler(filename); + handler(module, filename); + module.loaded = true; + } catch (e) { + delete Module._cache[filename]; + const children = mod.children; + if (localArrayIsArray(children)) { + const index = localArrayPrototypeIndexOf(children, module); + if (index !== -1) { + localArrayPrototypeSplice(children, index, 1); + } + } + throw e; + } + + return module.exports; +} + +Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); +Module.globalPaths = globalPaths; +Module._extensions = {__proto__: null}; +Module._cache = {__proto__: null}; + +{ + const keys = Object.getOwnPropertyNames(extensions); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const handler = extensions[key]; + Module._extensions[key] = (mod, filename) => handler(mod, filename); + } +} + +function findBestExtensionHandler(filename) { + const name = resolver.fs.basename(filename); + for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { + const ext = localStringPrototypeSlice(name, i); + const handler = Module._extensions[ext]; + if (handler) return handler; + } + const js = Module._extensions['.js']; + if (js) return js; + const keys = Object.getOwnPropertyNames(Module._extensions); + if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); + return Module._extensions[keys[0]]; +} + +function createRequireForModule(mod) { + // eslint-disable-next-line no-shadow + function require(id) { + return requireImpl(mod, id, true); + } + function resolve(id, options) { + return resolver.resolve(mod, id, options, Module._extensions, true); + } + require.resolve = resolve; + function paths(id) { + return ensureSandboxArray(resolver.lookupPaths(mod, id)); + } + resolve.paths = paths; + + require.extensions = Module._extensions; + + require.cache = Module._cache; + + return require; +} + +/** + * Prepare sandbox. + */ + +const TIMERS = new LocalWeakMap(); + +class Timeout { +} + +class Interval { +} + +class Immediate { +} + +function clearTimer(timer) { + const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); + if (obj) { + obj.clear(obj.value); + } +} + +// This is a function and not an arrow function, since the original is also a function +// eslint-disable-next-line no-shadow +global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Timeout(callback, args); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setTimeout(cb, delay); + + const ref = { + __proto__: null, + clear: host.clearTimeout, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Interval(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setInterval(cb, interval); + + const ref = { + __proto__: null, + clear: host.clearInterval, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Immediate(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setImmediate(cb); + + const ref = { + __proto__: null, + clear: host.clearImmediate, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.clearTimeout = function clearTimeout(timeout) { + clearTimer(timeout); +}; + +// eslint-disable-next-line no-shadow +global.clearInterval = function clearInterval(interval) { + clearTimer(interval); +}; + +// eslint-disable-next-line no-shadow +global.clearImmediate = function clearImmediate(immediate) { + clearTimer(immediate); +}; + +const localProcess = host.process; + +function vmEmitArgs(event, args) { + const allargs = [event]; + for (let i = 0; i < args.length; i++) { + if (!localReflectDefineProperty(allargs, i + 1, { + __proto__: null, + value: args[i], + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + } + return localReflectApply(vm.emit, vm, allargs); +} + +const LISTENERS = new LocalWeakMap(); +const LISTENER_HANDLER = new LocalWeakMap(); + +/** + * + * @param {*} name + * @param {*} handler + * @this process + * @return {this} + */ +function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (!cb) { + cb = () => { + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); + } + + localProcess.on(name, cb); + + return this; +} + +/** + * + * @this process + * @return {this} + */ +// eslint-disable-next-line no-shadow +function process() { + return this; +} + +const baseUptime = localProcess.uptime(); + +// FIXME wrong class structure +global.process = { + __proto__: process.prototype, + argv: optionArgv !== undefined ? optionArgv : [], + title: localProcess.title, + version: localProcess.version, + versions: localProcess.versions, + arch: localProcess.arch, + platform: localProcess.platform, + env: optionEnv !== undefined ? optionEnv : {}, + pid: localProcess.pid, + features: localProcess.features, + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new LocalError('Callback must be a function.'); + } + + localProcess.nextTick(()=>{ + localReflectApply(callback, null, args); + }); + }, + hrtime: function hrtime(time) { + return localProcess.hrtime(time); + }, + uptime: function uptime() { + return localProcess.uptime() - baseUptime; + }, + cwd: function cwd() { + return localProcess.cwd(); + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let triggered = false; + const cb = () => { + if (triggered) return; + triggered = true; + localProcess.removeListener(name, cb); + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + + localProcess.on(name, cb); + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + const all = localProcess.listeners(name); + const filtered = []; + let j = 0; + for (let i = 0; i < all.length; i++) { + const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); + if (h) { + if (!localReflectDefineProperty(filtered, j, { + __proto__: null, + value: h, + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + j++; + } + } + return filtered; + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (cb) localProcess.removeListener(name, cb); + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new LocalError('Access denied to set umask.'); + } + + return localProcess.umask(); + } +}; + +if (optionConsole === 'inherit') { + global.console = host.console; +} else if (optionConsole === 'redirect') { + global.console = { + debug(...args) { + vmEmitArgs('console.debug', args); + }, + log(...args) { + vmEmitArgs('console.log', args); + }, + info(...args) { + vmEmitArgs('console.info', args); + }, + warn(...args) { + vmEmitArgs('console.warn', args); + }, + error(...args) { + vmEmitArgs('console.error', args); + }, + dir(...args) { + vmEmitArgs('console.dir', args); + }, + time() {}, + timeEnd() {}, + trace(...args) { + vmEmitArgs('console.trace', args); + } + }; +} + +return { + __proto__: null, + Module, + jsonParse: JSON.parse, + createRequireForModule, + requireImpl +}; diff --git a/dist/restore-only/setup-sandbox.js b/dist/restore-only/setup-sandbox.js new file mode 100644 index 0000000..539ce04 --- /dev/null +++ b/dist/restore-only/setup-sandbox.js @@ -0,0 +1,474 @@ +/* global host, bridge, data, context */ + +'use strict'; + +const { + Object: localObject, + Array: localArray, + Error: LocalError, + Reflect: localReflect, + Proxy: LocalProxy, + WeakMap: LocalWeakMap, + Function: localFunction, + Promise: localPromise, + eval: localEval +} = global; + +const { + freeze: localObjectFreeze +} = localObject; + +const { + getPrototypeOf: localReflectGetPrototypeOf, + apply: localReflectApply, + deleteProperty: localReflectDeleteProperty, + has: localReflectHas, + defineProperty: localReflectDefineProperty, + setPrototypeOf: localReflectSetPrototypeOf, + getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor +} = localReflect; + +const { + isArray: localArrayIsArray +} = localArray; + +const { + ensureThis, + ReadOnlyHandler, + from, + fromWithFactory, + readonlyFactory, + connect, + addProtoMapping, + VMError, + ReadOnlyMockHandler +} = bridge; + +const { + allowAsync, + GeneratorFunction, + AsyncFunction, + AsyncGeneratorFunction +} = data; + +const { + get: localWeakMapGet, + set: localWeakMapSet +} = LocalWeakMap.prototype; + +function localUnexpected() { + return new VMError('Should not happen'); +} + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); + +Object.defineProperties(global, { + global: {value: global, writable: true, configurable: true, enumerable: true}, + globalThis: {value: global, writable: true, configurable: true}, + GLOBAL: {value: global, writable: true, configurable: true}, + root: {value: global, writable: true, configurable: true}, + Error: {value: LocalError} +}); + +if (!localReflectDefineProperty(global, 'VMError', { + __proto__: null, + value: VMError, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +// Fixes buffer unsafe allocation +/* eslint-disable no-use-before-define */ +class BufferHandler extends ReadOnlyHandler { + + apply(target, thiz, args) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + + construct(target, args, newTarget) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + +} +/* eslint-enable no-use-before-define */ + +const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); + + +if (!localReflectDefineProperty(global, 'Buffer', { + __proto__: null, + value: LocalBuffer, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafe(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafe, host.Buffer.allocUnsafe); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafeSlow(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); + +/** + * Replacement for Buffer inspect + * + * @param {*} recurseTimes + * @param {*} ctx + * @this LocalBuffer + * @return {string} + */ +function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +} + +connect(inspect, host.Buffer.prototype.inspect); + +connect(localFunction.prototype.bind, host.Function.prototype.bind); + +connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); +connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); +connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); +connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); + +/* + * PrepareStackTrace sanitization + */ + +const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); + +let currentPrepareStackTrace = LocalError.prepareStackTrace; +const wrappedPrepareStackTrace = new LocalWeakMap(); +if (typeof currentPrepareStackTrace === 'function') { + wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); +} + +let OriginalCallSite; +LocalError.prepareStackTrace = (e, sst) => { + OriginalCallSite = sst[0].constructor; +}; +new LocalError().stack; +if (typeof OriginalCallSite === 'function') { + LocalError.prepareStackTrace = undefined; + + function makeCallSiteGetters(list) { + const callSiteGetters = []; + for (let i=0; i { + return localReflectApply(func, thiz, []); + } + }; + } + return callSiteGetters; + } + + function applyCallSiteGetters(thiz, callSite, getters) { + for (let i=0; i { + const sandboxSst = ensureThis(sst); + if (localArrayIsArray(sst)) { + if (sst === sandboxSst) { + for (let i=0; i < sst.length; i++) { + const cs = sst[i]; + if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { + sst[i] = new CallSite(cs); + } + } + } else { + sst = []; + for (let i=0; i < sandboxSst.length; i++) { + const cs = sandboxSst[i]; + localReflectDefineProperty(sst, i, { + __proto__: null, + value: new CallSite(cs), + enumerable: true, + configurable: true, + writable: true + }); + } + } + } else { + sst = sandboxSst; + } + return value(error, sst); + }; + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); + currentPrepareStackTrace = newWrapped; + } + })) throw localUnexpected(); +} else if (oldPrepareStackTraceDesc) { + localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); +} else { + localReflectDeleteProperty(LocalError, 'prepareStackTrace'); +} + +/* + * Exception sanitization + */ + +const withProxy = localObjectFreeze({ + __proto__: null, + has(target, key) { + if (key === host.INTERNAL_STATE_NAME) return false; + return localReflectHas(target, key); + } +}); + +const interanState = localObjectFreeze({ + __proto__: null, + wrapWith(x) { + if (x === null || x === undefined) return x; + return new LocalProxy(localObject(x), withProxy); + }, + handleException: ensureThis, + import(what) { + throw new VMError('Dynamic Import not supported'); + } +}); + +if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { + __proto__: null, + configurable: false, + enumerable: false, + writable: false, + value: interanState +})) throw localUnexpected(); + +/* + * Eval sanitization + */ + +function throwAsync() { + return new VMError('Async not available'); +} + +function makeFunction(inputArgs, isAsync, isGenerator) { + const lastArgs = inputArgs.length - 1; + let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; + let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; + for (let i = 1; i < lastArgs; i++) { + args += `,${inputArgs[i]}`; + } + try { + code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); +} + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + return makeFunction(args, this.isAsync, this.isGenerator); + }, + construct(target, args, newTarget) { + return makeFunction(args, this.isAsync, this.isGenerator); + } +}; + +const EvalHandler = { + __proto__: null, + apply(target, thiz, args) { + if (args.length === 0) return undefined; + let code = `${args[0]}`; + try { + code = host.transformAndCheck(null, code, false, false, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); + } +}; + +const AsyncErrorHandler = { + __proto__: null, + apply(target, thiz, args) { + throw throwAsync(); + }, + construct(target, args, newTarget) { + throw throwAsync(); + } +}; + +function makeCheckFunction(isAsync, isGenerator) { + if (isAsync && !allowAsync) return AsyncErrorHandler; + return { + __proto__: FunctionHandler, + isAsync, + isGenerator + }; +} + +function overrideWithProxy(obj, prop, value, handler) { + const proxy = new LocalProxy(value, handler); + if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); + return proxy; +} + +const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); +if (GeneratorFunction) { + if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); +} +if (AsyncFunction) { + if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); +} +if (AsyncGeneratorFunction) { + if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); +} + +global.Function = proxiedFunction; +global.eval = new LocalProxy(localEval, EvalHandler); + +/* + * Promise sanitization + */ + +if (localPromise && !allowAsync) { + + const PromisePrototype = localPromise.prototype; + + overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); + // This seems not to work, and will produce + // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. + // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. + // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (PromisePrototype.finally) { + overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} + +function readonly(other, mock) { + // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) + if (!mock) return fromWithFactory(readonlyFactory, other); + const tmock = from(mock); + return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); +} + +return { + __proto__: null, + readonly, + global +}; diff --git a/dist/restore/bridge.js b/dist/restore/bridge.js new file mode 100644 index 0000000..8690b51 --- /dev/null +++ b/dist/restore/bridge.js @@ -0,0 +1,1010 @@ +'use strict'; + +/** + * __ ___ ____ _ _ ___ _ _ ____ + * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| + * \ \ /\ / / _ \ | |_) | \| || || \| | | _ + * \ V V / ___ \| _ <| |\ || || |\ | |_| | + * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| + * + * This file is critical for vm2. It implements the bridge between the host and the sandbox. + * If you do not know exactly what you are doing, you should NOT edit this file. + * + * The file is loaded in the host and sandbox to handle objects in both directions. + * This is done to ensure that RangeErrors are from the correct context. + * The boundary between the sandbox and host might throw RangeErrors from both contexts. + * Therefore, thisFromOther and friends can handle objects from both domains. + * + * Method parameters have comments to tell from which context they came. + * + */ + +const globalsList = [ + 'Number', + 'String', + 'Boolean', + 'Date', + 'RegExp', + 'Map', + 'WeakMap', + 'Set', + 'WeakSet', + 'Promise', + 'Function' +]; + +const errorsList = [ + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'EvalError', + 'URIError', + 'Error' +]; + +const OPNA = 'Operation not allowed on contextified object.'; + +const thisGlobalPrototypes = { + __proto__: null, + Object: Object.prototype, + Array: Array.prototype +}; + +for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +const { + getPrototypeOf: thisReflectGetPrototypeOf, + setPrototypeOf: thisReflectSetPrototypeOf, + defineProperty: thisReflectDefineProperty, + deleteProperty: thisReflectDeleteProperty, + getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, + isExtensible: thisReflectIsExtensible, + preventExtensions: thisReflectPreventExtensions, + apply: thisReflectApply, + construct: thisReflectConstruct, + set: thisReflectSet, + get: thisReflectGet, + has: thisReflectHas, + ownKeys: thisReflectOwnKeys, + enumerate: thisReflectEnumerate, +} = Reflect; + +const thisObject = Object; +const { + freeze: thisObjectFreeze, + prototype: thisObjectPrototype +} = thisObject; +const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; +const ThisProxy = Proxy; +const ThisWeakMap = WeakMap; +const { + get: thisWeakMapGet, + set: thisWeakMapSet +} = ThisWeakMap.prototype; +const ThisMap = Map; +const thisMapGet = ThisMap.prototype.get; +const thisMapSet = ThisMap.prototype.set; +const thisFunction = Function; +const thisFunctionBind = thisFunction.prototype.bind; +const thisArrayIsArray = Array.isArray; +const thisErrorCaptureStackTrace = Error.captureStackTrace; + +const thisSymbolToString = Symbol.prototype.toString; +const thisSymbolToStringTag = Symbol.toStringTag; +const thisSymbolIterator = Symbol.iterator; +const thisSymbolNodeJSUtilInspectCustom = Symbol.for('nodejs.util.inspect.custom'); + +/** + * VMError. + * + * @public + * @extends {Error} + */ +class VMError extends Error { + + /** + * Create VMError instance. + * + * @public + * @param {string} message - Error message. + * @param {string} code - Error code. + */ + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + thisErrorCaptureStackTrace(this, this.constructor); + } +} + +thisGlobalPrototypes['VMError'] = VMError.prototype; + +function thisUnexpected() { + return new VMError('Unexpected'); +} + +if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); + +function thisSafeGetOwnPropertyDescriptor(obj, key) { + const desc = thisReflectGetOwnPropertyDescriptor(obj, key); + if (!desc) return desc; + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + return desc; +} + +function thisThrowCallerCalleeArgumentsAccess(key) { + 'use strict'; + thisThrowCallerCalleeArgumentsAccess[key]; + return thisUnexpected(); +} + +function thisIdMapping(factory, other) { + return other; +} + +const thisThrowOnKeyAccessHandler = thisObjectFreeze({ + __proto__: null, + get(target, key, receiver) { + if (typeof key === 'symbol') { + key = thisReflectApply(thisSymbolToString, key, []); + } + throw new VMError(`Unexpected access to key '${key}'`); + } +}); + +const emptyForzenObject = thisObjectFreeze({ + __proto__: null +}); + +const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); + +function SafeBase() {} + +if (!thisReflectDefineProperty(SafeBase, 'prototype', { + __proto__: null, + value: thisThrowOnKeyAccess +})) throw thisUnexpected(); + +function SHARED_FUNCTION() {} + +const TEST_PROXY_HANDLER = thisObjectFreeze({ + __proto__: thisThrowOnKeyAccess, + construct() { + return this; + } +}); + +function thisIsConstructor(obj) { + // Note: obj@any(unsafe) + const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); + try { + // eslint-disable-next-line no-new + new Func(); + return true; + } catch (e) { + return false; + } +} + +function thisCreateTargetObject(obj, proto) { + // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) + let base; + if (typeof obj === 'function') { + if (thisIsConstructor(obj)) { + // Bind the function since bound functions do not have a prototype property. + base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); + } else { + base = () => {}; + } + } else if (thisArrayIsArray(obj)) { + base = []; + } else { + return {__proto__: proto}; + } + if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); + return base; +} + +function createBridge(otherInit, registerProxy) { + + const mappingOtherToThis = new ThisWeakMap(); + const protoMappings = new ThisMap(); + const protoName = new ThisMap(); + + function thisAddProtoMapping(proto, other, name) { + // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => thisProxyOther(factory, object, proto)]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + + function thisAddProtoMappingFactory(protoFactory, other, name) { + // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + let proto; + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => { + if (!proto) { + proto = protoFactory(); + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + return thisProxyOther(factory, object, proto); + }]); + } + + const result = { + __proto__: null, + globalPrototypes: thisGlobalPrototypes, + safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, + fromArguments: thisFromOtherArguments, + from: thisFromOther, + fromWithFactory: thisFromOtherWithFactory, + ensureThis: thisEnsureThis, + mapping: mappingOtherToThis, + connect: thisConnect, + reflectSet: thisReflectSet, + reflectGet: thisReflectGet, + reflectDefineProperty: thisReflectDefineProperty, + reflectDeleteProperty: thisReflectDeleteProperty, + reflectApply: thisReflectApply, + reflectConstruct: thisReflectConstruct, + reflectHas: thisReflectHas, + reflectOwnKeys: thisReflectOwnKeys, + reflectEnumerate: thisReflectEnumerate, + reflectGetPrototypeOf: thisReflectGetPrototypeOf, + reflectIsExtensible: thisReflectIsExtensible, + reflectPreventExtensions: thisReflectPreventExtensions, + objectHasOwnProperty: thisObjectHasOwnProperty, + weakMapSet: thisWeakMapSet, + addProtoMapping: thisAddProtoMapping, + addProtoMappingFactory: thisAddProtoMappingFactory, + defaultFactory, + protectedFactory, + readonlyFactory, + VMError + }; + + const isHost = typeof otherInit !== 'object'; + + if (isHost) { + otherInit = otherInit(result, registerProxy); + } + + result.other = otherInit; + + const { + globalPrototypes: otherGlobalPrototypes, + safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, + fromArguments: otherFromThisArguments, + from: otherFromThis, + mapping: mappingThisToOther, + reflectSet: otherReflectSet, + reflectGet: otherReflectGet, + reflectDefineProperty: otherReflectDefineProperty, + reflectDeleteProperty: otherReflectDeleteProperty, + reflectApply: otherReflectApply, + reflectConstruct: otherReflectConstruct, + reflectHas: otherReflectHas, + reflectOwnKeys: otherReflectOwnKeys, + reflectEnumerate: otherReflectEnumerate, + reflectGetPrototypeOf: otherReflectGetPrototypeOf, + reflectIsExtensible: otherReflectIsExtensible, + reflectPreventExtensions: otherReflectPreventExtensions, + objectHasOwnProperty: otherObjectHasOwnProperty, + weakMapSet: otherWeakMapSet + } = otherInit; + + function thisOtherHasOwnProperty(object, key) { + // Note: object@other(safe) key@prim throws@this(unsafe) + try { + return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + function thisDefaultGet(handler, object, key, desc) { + // Note: object@other(unsafe) key@prim desc@other(safe) + let ret; // @other(unsafe) + if (desc.get || desc.set) { + const getter = desc.get; + if (!getter) return undefined; + try { + ret = otherReflectApply(getter, object, [key]); + } catch (e) { + throw thisFromOtherForThrow(e); + } + } else { + ret = desc.value; + } + return handler.fromOtherWithContext(ret); + } + + function otherFromThisIfAvailable(to, from, key) { + // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) + if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; + try { + to[key] = otherFromThis(from[key]); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return true; + } + + class BaseHandler extends SafeBase { + + constructor(object) { + // Note: object@other(unsafe) throws@this(unsafe) + super(); + this.objectWrapper = () => object; + } + + getObject() { + return this.objectWrapper(); + } + + getFactory() { + return defaultFactory; + } + + fromOtherWithContext(other) { + // Note: other@other(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(this.getFactory(), other); + } + + doPreventExtensions(target, object, factory) { + // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) + let keys; // @other(safe-array-of-prim) + try { + keys = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; // @prim + let desc; + try { + desc = otherSafeGetOwnPropertyDescriptor(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!desc) continue; + if (!desc.configurable) { + const current = thisSafeGetOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = this.fromOtherWithContext(desc.get); + desc.set = this.fromOtherWithContext(desc.set); + } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { + desc.value = null; + } else { + desc.value = this.fromOtherWithContext(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); + } + if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + switch (key) { + case 'constructor': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + const proto = thisReflectGetPrototypeOf(target); + return proto === null ? undefined : proto.constructor; + } + case '__proto__': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + return thisReflectGetPrototypeOf(target); + } + case thisSymbolToStringTag: + if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { + const proto = thisReflectGetPrototypeOf(target); + const name = thisReflectApply(thisMapGet, protoName, [proto]); + if (name) return name; + } + break; + case 'arguments': + case 'caller': + case 'callee': + if (typeof object === 'function' && thisOtherHasOwnProperty(object, key)) { + throw thisThrowCallerCalleeArgumentsAccess(key); + } + break; + } + let ret; // @other(unsafe) + try { + ret = otherReflectGet(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(ret); + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { + return this.setPrototypeOf(target, value); + } + try { + value = otherFromThis(value); + return otherReflectSet(object, key, value) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + getPrototypeOf(target) { + // Note: target@this(unsafe) + return thisReflectGetPrototypeOf(target); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + throw new VMError(OPNA); + } + + apply(target, context, args) { + // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + context = otherFromThis(context); + args = otherFromThisArguments(args); + ret = otherReflectApply(object, context, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(ret); + } + + construct(target, args, newTarget) { + // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + args = otherFromThisArguments(args); + ret = otherReflectConstruct(object, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); + } + + getOwnPropertyDescriptorDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; + return desc; + } + + getOwnPropertyDescriptor(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let desc; // @other(safe) + try { + desc = otherSafeGetOwnPropertyDescriptor(object, prop); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); + + if (!desc) return undefined; + + let thisDesc; + if (desc.get || desc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(desc.get), + set: this.fromOtherWithContext(desc.set), + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(desc.value), + writable: desc.writable === true, + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } + if (!thisDesc.configurable) { + const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); + if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + } + return thisDesc; + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + return desc; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + + desc = this.definePropertyDesc(target, prop, desc); + + if (!desc) return false; + + let otherDesc = {__proto__: null}; + let hasFunc = true; + let hasValue = true; + let hasBasic = true; + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); + + try { + if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; + if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { + otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); + } + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + if (!otherDesc.configurable) { + let thisDesc; + if (otherDesc.get || otherDesc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(otherDesc.get), + set: this.fromOtherWithContext(otherDesc.set), + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(otherDesc.value), + writable: otherDesc.writable, + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + return true; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectDeleteProperty(object, prop) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + has(target, key) { + // Note: target@this(unsafe) key@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectHas(object, key) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (otherReflectIsExtensible(object)) return true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return false; + } + + ownKeys(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(res); + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (!otherReflectPreventExtensions(object)) return false; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return true; + } + + enumerate(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectEnumerate(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(res); + } + + } + + BaseHandler.prototype[thisSymbolNodeJSUtilInspectCustom] = undefined; + BaseHandler.prototype[thisSymbolToStringTag] = 'VM2 Wrapper'; + BaseHandler.prototype[thisSymbolIterator] = undefined; + + function defaultFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new BaseHandler(object); + } + + class ProtectedHandler extends BaseHandler { + + getFactory() { + return protectedFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + if (typeof value === 'function') { + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }) === true; + } + return super.set(target, key, value, receiver); + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; + return desc; + } + + } + + function protectedFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ProtectedHandler(object); + } + + class ReadOnlyHandler extends BaseHandler { + + getFactory() { + return readonlyFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + return false; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + return false; + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + } + + function readonlyFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ReadOnlyHandler(object); + } + + class ReadOnlyMockHandler extends ReadOnlyHandler { + + constructor(object, mock) { + // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) + super(object); + this.mock = mock; + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + const mock = this.mock; + if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { + return mock[key]; + } + return super.get(target, key, receiver); + } + + } + + function thisFromOther(other) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(defaultFactory, other); + } + + function thisProxyOther(factory, other, proto) { + const target = thisCreateTargetObject(other, proto); + const handler = factory(other); + const proxy = new ThisProxy(target, handler); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); + registerProxy(proxy, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + if (!isHost) { + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); + return proxy; + } + const proxy2 = new ThisProxy(proxy, emptyForzenObject); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); + registerProxy(proxy2, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); + return proxy2; + } + + function thisEnsureThis(other) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { + return other; + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherForThrow(other) { + for (let loop = 0; loop < 10; loop++) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + let proto; + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(defaultFactory, other, null); + } + for (;;) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(defaultFactory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); + } + break; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + throw new VMError('Exception recursion depth'); + } + + function thisFromOtherWithFactory(factory, other, proto) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + do { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } while (proto); + return thisProxyOther(factory, other, thisObjectPrototype); + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherArguments(args) { + // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) + const arr = []; + for (let i = 0; i < args.length; i++) { + const value = thisFromOther(args[i]); + thisReflectDefineProperty(arr, i, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + return arr; + } + + function thisConnect(obj, other) { + // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); + } + + thisAddProtoMapping(thisGlobalPrototypes.Object, otherGlobalPrototypes.Object); + thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); + + for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, key); + } + + for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, 'Error'); + } + + thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); + + result.BaseHandler = BaseHandler; + result.ProtectedHandler = ProtectedHandler; + result.ReadOnlyHandler = ReadOnlyHandler; + result.ReadOnlyMockHandler = ReadOnlyMockHandler; + + return result; +} + +exports.createBridge = createBridge; +exports.VMError = VMError; diff --git a/dist/restore/events.js b/dist/restore/events.js new file mode 100644 index 0000000..624e827 --- /dev/null +++ b/dist/restore/events.js @@ -0,0 +1,977 @@ +// 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. + +// Modified by the vm2 team to make this a standalone module to be loaded into the sandbox. + +'use strict'; + +const host = fromhost; + +const { + Boolean, + Error, + String, + Symbol +} = globalThis; + +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; + +const ErrorCaptureStackTrace = Error.captureStackTrace; + +const NumberIsNaN = Number.isNaN; + +const ObjectCreate = Object.create; +const ObjectDefineProperty = Object.defineProperty; +const ObjectDefineProperties = Object.defineProperties; +const ObjectGetPrototypeOf = Object.getPrototypeOf; + +const SymbolFor = Symbol.for; + +function uncurryThis(func) { + return (thiz, ...args) => ReflectApply(func, thiz, args); +} + +const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); +const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); + +const kRejection = SymbolFor('nodejs.rejection'); + +function inspect(obj) { + return typeof obj === 'symbol' ? obj.toString() : `${obj}`; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function assert(what, message) { + if (!what) throw new Error(message); +} + +function E(key, msg, Base) { + return function NodeError(...args) { + const error = new Base(); + const message = ReflectApply(msg, error, args); + ObjectDefineProperties(error, { + message: { + value: message, + enumerable: false, + writable: true, + configurable: true, + }, + toString: { + value() { + return `${this.name} [${key}]: ${this.message}`; + }, + enumerable: false, + writable: true, + configurable: true, + }, + }); + error.code = key; + return error; + }; +} + + +const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', + (name, expected, actual) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (StringPrototypeEndsWith(name, ' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (ArrayPrototypeIncludes(kTypes, value)) { + ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); + } else if (RegExpPrototypeTest(classRegExp, value)) { + ArrayPrototypePush(instances, value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + ArrayPrototypePush(other, value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = ArrayPrototypeIndexOf(types, 'object'); + if (pos !== -1) { + ArrayPrototypeSplice(types, pos, 1); + ArrayPrototypePush(instances, 'Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = ArrayPrototypePop(types); + msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = ArrayPrototypePop(instances); + msg += + `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = ArrayPrototypePop(other); + msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (StringPrototypeToLowerCase(other[0]) !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; + }, TypeError); + +const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); + +const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', + (str, range, input, replaceDefaultBoolean = false) => { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean ? str : + `The value of "${str}" is out of range.`; + const received = inspect(input); + msg += ` It must be ${range}. Received ${received}`; + return msg; + }, RangeError); + +const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', + err => { + const msg = 'Unhandled error.'; + if (err === undefined) return msg; + return `${msg} (${err})`; + }, Error); + +function validateBoolean(value, name) { + if (typeof value !== 'boolean') + throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); +} + +function validateFunction(value, name) { + if (typeof value !== 'function') + throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); +} + +function validateString(value, name) { + if (typeof value !== 'string') + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); +} + +function nc(cond, e) { + return cond === undefined || cond === null ? e : cond; +} + +function oc(base, key) { + return base === undefined || base === null ? undefined : base[key]; +} + +const kCapture = Symbol('kCapture'); +const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); +const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); +const kMaxEventTargetListenersWarned = + Symbol('events.maxEventTargetListenersWarned'); + +const kIsEventTarget = SymbolFor('nodejs.event_target'); + +function isEventTarget(obj) { + return oc(oc(obj, 'constructor'), kIsEventTarget); +} + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @constructs {EventEmitter} + */ +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +module.exports = EventEmitter; +if (host.once) module.exports.once = host.once; +if (host.on) module.exports.on = host.on; +if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, 'EventEmitter.captureRejections'); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +if (host.EventEmitterReferencingAsyncResource) { + const kAsyncResource = Symbol('kAsyncResource'); + const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; + + class EventEmitterAsyncResource extends EventEmitter { + /** + * @param {{ + * name?: string, + * triggerAsyncId?: number, + * requireManualDestroy?: boolean, + * }} [options] + */ + constructor(options = undefined) { + let name; + if (typeof options === 'string') { + name = options; + options = undefined; + } else { + if (new.target === EventEmitterAsyncResource) { + validateString(oc(options, 'name'), 'options.name'); + } + name = oc(options, 'name') || new.target.name; + } + super(options); + + this[kAsyncResource] = + new EventEmitterReferencingAsyncResource(this, name, options); + } + + /** + * @param {symbol,string} event + * @param {...any} args + * @returns {boolean} + */ + emit(event, ...args) { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + const { asyncResource } = this; + ArrayPrototypeUnshift(args, super.emit, this, event); + return ReflectApply(asyncResource.runInAsyncScope, asyncResource, + args); + } + + /** + * @returns {void} + */ + emitDestroy() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + this.asyncResource.emitDestroy(); + } + + /** + * @type {number} + */ + get asyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.asyncId(); + } + + /** + * @type {number} + */ + get triggerAsyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.triggerAsyncId(); + } + + /** + * @type {EventEmitterReferencingAsyncResource} + */ + get asyncResource() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this[kAsyncResource]; + } + } + EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; +} + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, 'listener'); +} + +ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new ERR_OUT_OF_RANGE('defaultMaxListeners', + 'a non-negative number', + arg); + } + defaultMaxListeners = arg; + } +}); + +ObjectDefineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + } +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +EventEmitter.setMaxListeners = + function(n = defaultMaxListeners, ...eventTargets) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (isEventTarget(target)) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === 'function') { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + 'eventTargets', + ['EventEmitter', 'EventTarget'], + target); + } + } + } + }; + +// If you're updating this function definition, please also update any +// re-definitions, such as the one in the Domain module (lib/domain.js). +EventEmitter.init = function(opts) { + + if (this._events === undefined || + this._events === ObjectGetPrototypeOf(this)._events) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + + if (oc(opts, 'captureRejections')) { + validateBoolean(opts.captureRejections, 'options.captureRejections'); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catchable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = (type === 'error'); + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) + this.emit(kErrorMonitor, ...args); + doError = (doError && events.error === undefined); + } else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + try { + const capture = {}; + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); + } catch (e) {} + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch (e) { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = ObjectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + nc(listener.listener, listener)); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error('Possible EventEmitter memory leak detected. ' + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + 'emitter.setMaxListeners() to increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + checkListener(listener); + + const events = this._events; + if (events === undefined) + return this; + + const list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + const events = this._events; + if (events === undefined) + return this; + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else + delete events[type]; + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of ReflectOwnKeys(events)) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = ObjectCreate(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) + return []; + + const evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } + return emitter.listenerCount(type); +}; + +EventEmitter.prototype.listenerCount = listenerCount; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return ArrayPrototypeSlice(arr); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === 'function') + ret[i] = orig; + } + return ret; +} diff --git a/dist/restore/setup-node-sandbox.js b/dist/restore/setup-node-sandbox.js new file mode 100644 index 0000000..05f38ef --- /dev/null +++ b/dist/restore/setup-node-sandbox.js @@ -0,0 +1,469 @@ +/* global host, data, VMError */ + +'use strict'; + +const LocalError = Error; +const LocalTypeError = TypeError; +const LocalWeakMap = WeakMap; + +const { + apply: localReflectApply, + defineProperty: localReflectDefineProperty +} = Reflect; + +const { + set: localWeakMapSet, + get: localWeakMapGet +} = LocalWeakMap.prototype; + +const { + isArray: localArrayIsArray +} = Array; + +function uncurryThis(func) { + return (thiz, ...args) => localReflectApply(func, thiz, args); +} + +const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); +const localArrayPrototypePush = uncurryThis(Array.prototype.push); +const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); +const localStringPrototypeSlice = uncurryThis(String.prototype.slice); +const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); + +const { + argv: optionArgv, + env: optionEnv, + console: optionConsole, + vm, + resolver, + extensions +} = data; + +function ensureSandboxArray(a) { + return localArrayPrototypeSlice(a); +} + +const globalPaths = ensureSandboxArray(resolver.globalPaths); + +class Module { + + constructor(id, path, parent) { + this.id = id; + this.filename = id; + this.path = path; + this.parent = parent; + this.loaded = false; + this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; + this.children = []; + this.exports = {}; + } + + _updateChildren(child, isNew) { + const children = this.children; + if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { + localArrayPrototypePush(children, child); + } + } + + require(id) { + return requireImpl(this, id, false); + } + +} + +const originalRequire = Module.prototype.require; +const cacheBuiltins = {__proto__: null}; + +function requireImpl(mod, id, direct) { + if (direct && mod.require !== originalRequire) { + return mod.require(id); + } + const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); + if (localStringPrototypeStartsWith(filename, 'node:')) { + id = localStringPrototypeSlice(filename, 5); + let nmod = cacheBuiltins[id]; + if (!nmod) { + nmod = resolver.loadBuiltinModule(vm, id); + if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); + cacheBuiltins[id] = nmod; + } + return nmod; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + mod._updateChildren(cachedModule, false); + return cachedModule.exports; + } + + let nmod = cacheBuiltins[id]; + if (nmod) return nmod; + nmod = resolver.loadBuiltinModule(vm, id); + if (nmod) { + cacheBuiltins[id] = nmod; + return nmod; + } + + const path = resolver.fs.dirname(filename); + const module = new Module(filename, path, mod); + resolver.registerModule(module, filename, path, mod, direct); + mod._updateChildren(module, true); + try { + Module._cache[filename] = module; + const handler = findBestExtensionHandler(filename); + handler(module, filename); + module.loaded = true; + } catch (e) { + delete Module._cache[filename]; + const children = mod.children; + if (localArrayIsArray(children)) { + const index = localArrayPrototypeIndexOf(children, module); + if (index !== -1) { + localArrayPrototypeSplice(children, index, 1); + } + } + throw e; + } + + return module.exports; +} + +Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); +Module.globalPaths = globalPaths; +Module._extensions = {__proto__: null}; +Module._cache = {__proto__: null}; + +{ + const keys = Object.getOwnPropertyNames(extensions); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const handler = extensions[key]; + Module._extensions[key] = (mod, filename) => handler(mod, filename); + } +} + +function findBestExtensionHandler(filename) { + const name = resolver.fs.basename(filename); + for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { + const ext = localStringPrototypeSlice(name, i); + const handler = Module._extensions[ext]; + if (handler) return handler; + } + const js = Module._extensions['.js']; + if (js) return js; + const keys = Object.getOwnPropertyNames(Module._extensions); + if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); + return Module._extensions[keys[0]]; +} + +function createRequireForModule(mod) { + // eslint-disable-next-line no-shadow + function require(id) { + return requireImpl(mod, id, true); + } + function resolve(id, options) { + return resolver.resolve(mod, id, options, Module._extensions, true); + } + require.resolve = resolve; + function paths(id) { + return ensureSandboxArray(resolver.lookupPaths(mod, id)); + } + resolve.paths = paths; + + require.extensions = Module._extensions; + + require.cache = Module._cache; + + return require; +} + +/** + * Prepare sandbox. + */ + +const TIMERS = new LocalWeakMap(); + +class Timeout { +} + +class Interval { +} + +class Immediate { +} + +function clearTimer(timer) { + const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); + if (obj) { + obj.clear(obj.value); + } +} + +// This is a function and not an arrow function, since the original is also a function +// eslint-disable-next-line no-shadow +global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Timeout(callback, args); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setTimeout(cb, delay); + + const ref = { + __proto__: null, + clear: host.clearTimeout, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Interval(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setInterval(cb, interval); + + const ref = { + __proto__: null, + clear: host.clearInterval, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Immediate(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setImmediate(cb); + + const ref = { + __proto__: null, + clear: host.clearImmediate, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.clearTimeout = function clearTimeout(timeout) { + clearTimer(timeout); +}; + +// eslint-disable-next-line no-shadow +global.clearInterval = function clearInterval(interval) { + clearTimer(interval); +}; + +// eslint-disable-next-line no-shadow +global.clearImmediate = function clearImmediate(immediate) { + clearTimer(immediate); +}; + +const localProcess = host.process; + +function vmEmitArgs(event, args) { + const allargs = [event]; + for (let i = 0; i < args.length; i++) { + if (!localReflectDefineProperty(allargs, i + 1, { + __proto__: null, + value: args[i], + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + } + return localReflectApply(vm.emit, vm, allargs); +} + +const LISTENERS = new LocalWeakMap(); +const LISTENER_HANDLER = new LocalWeakMap(); + +/** + * + * @param {*} name + * @param {*} handler + * @this process + * @return {this} + */ +function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (!cb) { + cb = () => { + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); + } + + localProcess.on(name, cb); + + return this; +} + +/** + * + * @this process + * @return {this} + */ +// eslint-disable-next-line no-shadow +function process() { + return this; +} + +const baseUptime = localProcess.uptime(); + +// FIXME wrong class structure +global.process = { + __proto__: process.prototype, + argv: optionArgv !== undefined ? optionArgv : [], + title: localProcess.title, + version: localProcess.version, + versions: localProcess.versions, + arch: localProcess.arch, + platform: localProcess.platform, + env: optionEnv !== undefined ? optionEnv : {}, + pid: localProcess.pid, + features: localProcess.features, + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new LocalError('Callback must be a function.'); + } + + localProcess.nextTick(()=>{ + localReflectApply(callback, null, args); + }); + }, + hrtime: function hrtime(time) { + return localProcess.hrtime(time); + }, + uptime: function uptime() { + return localProcess.uptime() - baseUptime; + }, + cwd: function cwd() { + return localProcess.cwd(); + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let triggered = false; + const cb = () => { + if (triggered) return; + triggered = true; + localProcess.removeListener(name, cb); + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + + localProcess.on(name, cb); + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + const all = localProcess.listeners(name); + const filtered = []; + let j = 0; + for (let i = 0; i < all.length; i++) { + const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); + if (h) { + if (!localReflectDefineProperty(filtered, j, { + __proto__: null, + value: h, + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + j++; + } + } + return filtered; + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (cb) localProcess.removeListener(name, cb); + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new LocalError('Access denied to set umask.'); + } + + return localProcess.umask(); + } +}; + +if (optionConsole === 'inherit') { + global.console = host.console; +} else if (optionConsole === 'redirect') { + global.console = { + debug(...args) { + vmEmitArgs('console.debug', args); + }, + log(...args) { + vmEmitArgs('console.log', args); + }, + info(...args) { + vmEmitArgs('console.info', args); + }, + warn(...args) { + vmEmitArgs('console.warn', args); + }, + error(...args) { + vmEmitArgs('console.error', args); + }, + dir(...args) { + vmEmitArgs('console.dir', args); + }, + time() {}, + timeEnd() {}, + trace(...args) { + vmEmitArgs('console.trace', args); + } + }; +} + +return { + __proto__: null, + Module, + jsonParse: JSON.parse, + createRequireForModule, + requireImpl +}; diff --git a/dist/restore/setup-sandbox.js b/dist/restore/setup-sandbox.js new file mode 100644 index 0000000..539ce04 --- /dev/null +++ b/dist/restore/setup-sandbox.js @@ -0,0 +1,474 @@ +/* global host, bridge, data, context */ + +'use strict'; + +const { + Object: localObject, + Array: localArray, + Error: LocalError, + Reflect: localReflect, + Proxy: LocalProxy, + WeakMap: LocalWeakMap, + Function: localFunction, + Promise: localPromise, + eval: localEval +} = global; + +const { + freeze: localObjectFreeze +} = localObject; + +const { + getPrototypeOf: localReflectGetPrototypeOf, + apply: localReflectApply, + deleteProperty: localReflectDeleteProperty, + has: localReflectHas, + defineProperty: localReflectDefineProperty, + setPrototypeOf: localReflectSetPrototypeOf, + getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor +} = localReflect; + +const { + isArray: localArrayIsArray +} = localArray; + +const { + ensureThis, + ReadOnlyHandler, + from, + fromWithFactory, + readonlyFactory, + connect, + addProtoMapping, + VMError, + ReadOnlyMockHandler +} = bridge; + +const { + allowAsync, + GeneratorFunction, + AsyncFunction, + AsyncGeneratorFunction +} = data; + +const { + get: localWeakMapGet, + set: localWeakMapSet +} = LocalWeakMap.prototype; + +function localUnexpected() { + return new VMError('Should not happen'); +} + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); + +Object.defineProperties(global, { + global: {value: global, writable: true, configurable: true, enumerable: true}, + globalThis: {value: global, writable: true, configurable: true}, + GLOBAL: {value: global, writable: true, configurable: true}, + root: {value: global, writable: true, configurable: true}, + Error: {value: LocalError} +}); + +if (!localReflectDefineProperty(global, 'VMError', { + __proto__: null, + value: VMError, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +// Fixes buffer unsafe allocation +/* eslint-disable no-use-before-define */ +class BufferHandler extends ReadOnlyHandler { + + apply(target, thiz, args) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + + construct(target, args, newTarget) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + +} +/* eslint-enable no-use-before-define */ + +const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); + + +if (!localReflectDefineProperty(global, 'Buffer', { + __proto__: null, + value: LocalBuffer, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafe(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafe, host.Buffer.allocUnsafe); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafeSlow(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); + +/** + * Replacement for Buffer inspect + * + * @param {*} recurseTimes + * @param {*} ctx + * @this LocalBuffer + * @return {string} + */ +function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +} + +connect(inspect, host.Buffer.prototype.inspect); + +connect(localFunction.prototype.bind, host.Function.prototype.bind); + +connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); +connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); +connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); +connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); + +/* + * PrepareStackTrace sanitization + */ + +const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); + +let currentPrepareStackTrace = LocalError.prepareStackTrace; +const wrappedPrepareStackTrace = new LocalWeakMap(); +if (typeof currentPrepareStackTrace === 'function') { + wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); +} + +let OriginalCallSite; +LocalError.prepareStackTrace = (e, sst) => { + OriginalCallSite = sst[0].constructor; +}; +new LocalError().stack; +if (typeof OriginalCallSite === 'function') { + LocalError.prepareStackTrace = undefined; + + function makeCallSiteGetters(list) { + const callSiteGetters = []; + for (let i=0; i { + return localReflectApply(func, thiz, []); + } + }; + } + return callSiteGetters; + } + + function applyCallSiteGetters(thiz, callSite, getters) { + for (let i=0; i { + const sandboxSst = ensureThis(sst); + if (localArrayIsArray(sst)) { + if (sst === sandboxSst) { + for (let i=0; i < sst.length; i++) { + const cs = sst[i]; + if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { + sst[i] = new CallSite(cs); + } + } + } else { + sst = []; + for (let i=0; i < sandboxSst.length; i++) { + const cs = sandboxSst[i]; + localReflectDefineProperty(sst, i, { + __proto__: null, + value: new CallSite(cs), + enumerable: true, + configurable: true, + writable: true + }); + } + } + } else { + sst = sandboxSst; + } + return value(error, sst); + }; + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); + currentPrepareStackTrace = newWrapped; + } + })) throw localUnexpected(); +} else if (oldPrepareStackTraceDesc) { + localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); +} else { + localReflectDeleteProperty(LocalError, 'prepareStackTrace'); +} + +/* + * Exception sanitization + */ + +const withProxy = localObjectFreeze({ + __proto__: null, + has(target, key) { + if (key === host.INTERNAL_STATE_NAME) return false; + return localReflectHas(target, key); + } +}); + +const interanState = localObjectFreeze({ + __proto__: null, + wrapWith(x) { + if (x === null || x === undefined) return x; + return new LocalProxy(localObject(x), withProxy); + }, + handleException: ensureThis, + import(what) { + throw new VMError('Dynamic Import not supported'); + } +}); + +if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { + __proto__: null, + configurable: false, + enumerable: false, + writable: false, + value: interanState +})) throw localUnexpected(); + +/* + * Eval sanitization + */ + +function throwAsync() { + return new VMError('Async not available'); +} + +function makeFunction(inputArgs, isAsync, isGenerator) { + const lastArgs = inputArgs.length - 1; + let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; + let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; + for (let i = 1; i < lastArgs; i++) { + args += `,${inputArgs[i]}`; + } + try { + code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); +} + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + return makeFunction(args, this.isAsync, this.isGenerator); + }, + construct(target, args, newTarget) { + return makeFunction(args, this.isAsync, this.isGenerator); + } +}; + +const EvalHandler = { + __proto__: null, + apply(target, thiz, args) { + if (args.length === 0) return undefined; + let code = `${args[0]}`; + try { + code = host.transformAndCheck(null, code, false, false, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); + } +}; + +const AsyncErrorHandler = { + __proto__: null, + apply(target, thiz, args) { + throw throwAsync(); + }, + construct(target, args, newTarget) { + throw throwAsync(); + } +}; + +function makeCheckFunction(isAsync, isGenerator) { + if (isAsync && !allowAsync) return AsyncErrorHandler; + return { + __proto__: FunctionHandler, + isAsync, + isGenerator + }; +} + +function overrideWithProxy(obj, prop, value, handler) { + const proxy = new LocalProxy(value, handler); + if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); + return proxy; +} + +const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); +if (GeneratorFunction) { + if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); +} +if (AsyncFunction) { + if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); +} +if (AsyncGeneratorFunction) { + if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); +} + +global.Function = proxiedFunction; +global.eval = new LocalProxy(localEval, EvalHandler); + +/* + * Promise sanitization + */ + +if (localPromise && !allowAsync) { + + const PromisePrototype = localPromise.prototype; + + overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); + // This seems not to work, and will produce + // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. + // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. + // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (PromisePrototype.finally) { + overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} + +function readonly(other, mock) { + // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) + if (!mock) return fromWithFactory(readonlyFactory, other); + const tmock = from(mock); + return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); +} + +return { + __proto__: null, + readonly, + global +}; diff --git a/dist/save-only/bridge.js b/dist/save-only/bridge.js new file mode 100644 index 0000000..8690b51 --- /dev/null +++ b/dist/save-only/bridge.js @@ -0,0 +1,1010 @@ +'use strict'; + +/** + * __ ___ ____ _ _ ___ _ _ ____ + * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| + * \ \ /\ / / _ \ | |_) | \| || || \| | | _ + * \ V V / ___ \| _ <| |\ || || |\ | |_| | + * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| + * + * This file is critical for vm2. It implements the bridge between the host and the sandbox. + * If you do not know exactly what you are doing, you should NOT edit this file. + * + * The file is loaded in the host and sandbox to handle objects in both directions. + * This is done to ensure that RangeErrors are from the correct context. + * The boundary between the sandbox and host might throw RangeErrors from both contexts. + * Therefore, thisFromOther and friends can handle objects from both domains. + * + * Method parameters have comments to tell from which context they came. + * + */ + +const globalsList = [ + 'Number', + 'String', + 'Boolean', + 'Date', + 'RegExp', + 'Map', + 'WeakMap', + 'Set', + 'WeakSet', + 'Promise', + 'Function' +]; + +const errorsList = [ + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'EvalError', + 'URIError', + 'Error' +]; + +const OPNA = 'Operation not allowed on contextified object.'; + +const thisGlobalPrototypes = { + __proto__: null, + Object: Object.prototype, + Array: Array.prototype +}; + +for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +const { + getPrototypeOf: thisReflectGetPrototypeOf, + setPrototypeOf: thisReflectSetPrototypeOf, + defineProperty: thisReflectDefineProperty, + deleteProperty: thisReflectDeleteProperty, + getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, + isExtensible: thisReflectIsExtensible, + preventExtensions: thisReflectPreventExtensions, + apply: thisReflectApply, + construct: thisReflectConstruct, + set: thisReflectSet, + get: thisReflectGet, + has: thisReflectHas, + ownKeys: thisReflectOwnKeys, + enumerate: thisReflectEnumerate, +} = Reflect; + +const thisObject = Object; +const { + freeze: thisObjectFreeze, + prototype: thisObjectPrototype +} = thisObject; +const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; +const ThisProxy = Proxy; +const ThisWeakMap = WeakMap; +const { + get: thisWeakMapGet, + set: thisWeakMapSet +} = ThisWeakMap.prototype; +const ThisMap = Map; +const thisMapGet = ThisMap.prototype.get; +const thisMapSet = ThisMap.prototype.set; +const thisFunction = Function; +const thisFunctionBind = thisFunction.prototype.bind; +const thisArrayIsArray = Array.isArray; +const thisErrorCaptureStackTrace = Error.captureStackTrace; + +const thisSymbolToString = Symbol.prototype.toString; +const thisSymbolToStringTag = Symbol.toStringTag; +const thisSymbolIterator = Symbol.iterator; +const thisSymbolNodeJSUtilInspectCustom = Symbol.for('nodejs.util.inspect.custom'); + +/** + * VMError. + * + * @public + * @extends {Error} + */ +class VMError extends Error { + + /** + * Create VMError instance. + * + * @public + * @param {string} message - Error message. + * @param {string} code - Error code. + */ + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + thisErrorCaptureStackTrace(this, this.constructor); + } +} + +thisGlobalPrototypes['VMError'] = VMError.prototype; + +function thisUnexpected() { + return new VMError('Unexpected'); +} + +if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); + +function thisSafeGetOwnPropertyDescriptor(obj, key) { + const desc = thisReflectGetOwnPropertyDescriptor(obj, key); + if (!desc) return desc; + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + return desc; +} + +function thisThrowCallerCalleeArgumentsAccess(key) { + 'use strict'; + thisThrowCallerCalleeArgumentsAccess[key]; + return thisUnexpected(); +} + +function thisIdMapping(factory, other) { + return other; +} + +const thisThrowOnKeyAccessHandler = thisObjectFreeze({ + __proto__: null, + get(target, key, receiver) { + if (typeof key === 'symbol') { + key = thisReflectApply(thisSymbolToString, key, []); + } + throw new VMError(`Unexpected access to key '${key}'`); + } +}); + +const emptyForzenObject = thisObjectFreeze({ + __proto__: null +}); + +const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); + +function SafeBase() {} + +if (!thisReflectDefineProperty(SafeBase, 'prototype', { + __proto__: null, + value: thisThrowOnKeyAccess +})) throw thisUnexpected(); + +function SHARED_FUNCTION() {} + +const TEST_PROXY_HANDLER = thisObjectFreeze({ + __proto__: thisThrowOnKeyAccess, + construct() { + return this; + } +}); + +function thisIsConstructor(obj) { + // Note: obj@any(unsafe) + const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); + try { + // eslint-disable-next-line no-new + new Func(); + return true; + } catch (e) { + return false; + } +} + +function thisCreateTargetObject(obj, proto) { + // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) + let base; + if (typeof obj === 'function') { + if (thisIsConstructor(obj)) { + // Bind the function since bound functions do not have a prototype property. + base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); + } else { + base = () => {}; + } + } else if (thisArrayIsArray(obj)) { + base = []; + } else { + return {__proto__: proto}; + } + if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); + return base; +} + +function createBridge(otherInit, registerProxy) { + + const mappingOtherToThis = new ThisWeakMap(); + const protoMappings = new ThisMap(); + const protoName = new ThisMap(); + + function thisAddProtoMapping(proto, other, name) { + // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => thisProxyOther(factory, object, proto)]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + + function thisAddProtoMappingFactory(protoFactory, other, name) { + // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + let proto; + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => { + if (!proto) { + proto = protoFactory(); + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + return thisProxyOther(factory, object, proto); + }]); + } + + const result = { + __proto__: null, + globalPrototypes: thisGlobalPrototypes, + safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, + fromArguments: thisFromOtherArguments, + from: thisFromOther, + fromWithFactory: thisFromOtherWithFactory, + ensureThis: thisEnsureThis, + mapping: mappingOtherToThis, + connect: thisConnect, + reflectSet: thisReflectSet, + reflectGet: thisReflectGet, + reflectDefineProperty: thisReflectDefineProperty, + reflectDeleteProperty: thisReflectDeleteProperty, + reflectApply: thisReflectApply, + reflectConstruct: thisReflectConstruct, + reflectHas: thisReflectHas, + reflectOwnKeys: thisReflectOwnKeys, + reflectEnumerate: thisReflectEnumerate, + reflectGetPrototypeOf: thisReflectGetPrototypeOf, + reflectIsExtensible: thisReflectIsExtensible, + reflectPreventExtensions: thisReflectPreventExtensions, + objectHasOwnProperty: thisObjectHasOwnProperty, + weakMapSet: thisWeakMapSet, + addProtoMapping: thisAddProtoMapping, + addProtoMappingFactory: thisAddProtoMappingFactory, + defaultFactory, + protectedFactory, + readonlyFactory, + VMError + }; + + const isHost = typeof otherInit !== 'object'; + + if (isHost) { + otherInit = otherInit(result, registerProxy); + } + + result.other = otherInit; + + const { + globalPrototypes: otherGlobalPrototypes, + safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, + fromArguments: otherFromThisArguments, + from: otherFromThis, + mapping: mappingThisToOther, + reflectSet: otherReflectSet, + reflectGet: otherReflectGet, + reflectDefineProperty: otherReflectDefineProperty, + reflectDeleteProperty: otherReflectDeleteProperty, + reflectApply: otherReflectApply, + reflectConstruct: otherReflectConstruct, + reflectHas: otherReflectHas, + reflectOwnKeys: otherReflectOwnKeys, + reflectEnumerate: otherReflectEnumerate, + reflectGetPrototypeOf: otherReflectGetPrototypeOf, + reflectIsExtensible: otherReflectIsExtensible, + reflectPreventExtensions: otherReflectPreventExtensions, + objectHasOwnProperty: otherObjectHasOwnProperty, + weakMapSet: otherWeakMapSet + } = otherInit; + + function thisOtherHasOwnProperty(object, key) { + // Note: object@other(safe) key@prim throws@this(unsafe) + try { + return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + function thisDefaultGet(handler, object, key, desc) { + // Note: object@other(unsafe) key@prim desc@other(safe) + let ret; // @other(unsafe) + if (desc.get || desc.set) { + const getter = desc.get; + if (!getter) return undefined; + try { + ret = otherReflectApply(getter, object, [key]); + } catch (e) { + throw thisFromOtherForThrow(e); + } + } else { + ret = desc.value; + } + return handler.fromOtherWithContext(ret); + } + + function otherFromThisIfAvailable(to, from, key) { + // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) + if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; + try { + to[key] = otherFromThis(from[key]); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return true; + } + + class BaseHandler extends SafeBase { + + constructor(object) { + // Note: object@other(unsafe) throws@this(unsafe) + super(); + this.objectWrapper = () => object; + } + + getObject() { + return this.objectWrapper(); + } + + getFactory() { + return defaultFactory; + } + + fromOtherWithContext(other) { + // Note: other@other(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(this.getFactory(), other); + } + + doPreventExtensions(target, object, factory) { + // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) + let keys; // @other(safe-array-of-prim) + try { + keys = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; // @prim + let desc; + try { + desc = otherSafeGetOwnPropertyDescriptor(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!desc) continue; + if (!desc.configurable) { + const current = thisSafeGetOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = this.fromOtherWithContext(desc.get); + desc.set = this.fromOtherWithContext(desc.set); + } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { + desc.value = null; + } else { + desc.value = this.fromOtherWithContext(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); + } + if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + switch (key) { + case 'constructor': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + const proto = thisReflectGetPrototypeOf(target); + return proto === null ? undefined : proto.constructor; + } + case '__proto__': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + return thisReflectGetPrototypeOf(target); + } + case thisSymbolToStringTag: + if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { + const proto = thisReflectGetPrototypeOf(target); + const name = thisReflectApply(thisMapGet, protoName, [proto]); + if (name) return name; + } + break; + case 'arguments': + case 'caller': + case 'callee': + if (typeof object === 'function' && thisOtherHasOwnProperty(object, key)) { + throw thisThrowCallerCalleeArgumentsAccess(key); + } + break; + } + let ret; // @other(unsafe) + try { + ret = otherReflectGet(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(ret); + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { + return this.setPrototypeOf(target, value); + } + try { + value = otherFromThis(value); + return otherReflectSet(object, key, value) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + getPrototypeOf(target) { + // Note: target@this(unsafe) + return thisReflectGetPrototypeOf(target); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + throw new VMError(OPNA); + } + + apply(target, context, args) { + // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + context = otherFromThis(context); + args = otherFromThisArguments(args); + ret = otherReflectApply(object, context, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(ret); + } + + construct(target, args, newTarget) { + // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + args = otherFromThisArguments(args); + ret = otherReflectConstruct(object, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); + } + + getOwnPropertyDescriptorDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; + return desc; + } + + getOwnPropertyDescriptor(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let desc; // @other(safe) + try { + desc = otherSafeGetOwnPropertyDescriptor(object, prop); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); + + if (!desc) return undefined; + + let thisDesc; + if (desc.get || desc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(desc.get), + set: this.fromOtherWithContext(desc.set), + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(desc.value), + writable: desc.writable === true, + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } + if (!thisDesc.configurable) { + const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); + if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + } + return thisDesc; + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + return desc; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + + desc = this.definePropertyDesc(target, prop, desc); + + if (!desc) return false; + + let otherDesc = {__proto__: null}; + let hasFunc = true; + let hasValue = true; + let hasBasic = true; + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); + + try { + if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; + if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { + otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); + } + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + if (!otherDesc.configurable) { + let thisDesc; + if (otherDesc.get || otherDesc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(otherDesc.get), + set: this.fromOtherWithContext(otherDesc.set), + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(otherDesc.value), + writable: otherDesc.writable, + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + return true; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectDeleteProperty(object, prop) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + has(target, key) { + // Note: target@this(unsafe) key@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectHas(object, key) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (otherReflectIsExtensible(object)) return true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return false; + } + + ownKeys(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(res); + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (!otherReflectPreventExtensions(object)) return false; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return true; + } + + enumerate(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectEnumerate(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(res); + } + + } + + BaseHandler.prototype[thisSymbolNodeJSUtilInspectCustom] = undefined; + BaseHandler.prototype[thisSymbolToStringTag] = 'VM2 Wrapper'; + BaseHandler.prototype[thisSymbolIterator] = undefined; + + function defaultFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new BaseHandler(object); + } + + class ProtectedHandler extends BaseHandler { + + getFactory() { + return protectedFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + if (typeof value === 'function') { + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }) === true; + } + return super.set(target, key, value, receiver); + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; + return desc; + } + + } + + function protectedFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ProtectedHandler(object); + } + + class ReadOnlyHandler extends BaseHandler { + + getFactory() { + return readonlyFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + return false; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + return false; + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + } + + function readonlyFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ReadOnlyHandler(object); + } + + class ReadOnlyMockHandler extends ReadOnlyHandler { + + constructor(object, mock) { + // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) + super(object); + this.mock = mock; + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + const mock = this.mock; + if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { + return mock[key]; + } + return super.get(target, key, receiver); + } + + } + + function thisFromOther(other) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(defaultFactory, other); + } + + function thisProxyOther(factory, other, proto) { + const target = thisCreateTargetObject(other, proto); + const handler = factory(other); + const proxy = new ThisProxy(target, handler); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); + registerProxy(proxy, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + if (!isHost) { + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); + return proxy; + } + const proxy2 = new ThisProxy(proxy, emptyForzenObject); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); + registerProxy(proxy2, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); + return proxy2; + } + + function thisEnsureThis(other) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { + return other; + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherForThrow(other) { + for (let loop = 0; loop < 10; loop++) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + let proto; + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(defaultFactory, other, null); + } + for (;;) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(defaultFactory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); + } + break; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + throw new VMError('Exception recursion depth'); + } + + function thisFromOtherWithFactory(factory, other, proto) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + do { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } while (proto); + return thisProxyOther(factory, other, thisObjectPrototype); + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherArguments(args) { + // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) + const arr = []; + for (let i = 0; i < args.length; i++) { + const value = thisFromOther(args[i]); + thisReflectDefineProperty(arr, i, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + return arr; + } + + function thisConnect(obj, other) { + // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); + } + + thisAddProtoMapping(thisGlobalPrototypes.Object, otherGlobalPrototypes.Object); + thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); + + for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, key); + } + + for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, 'Error'); + } + + thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); + + result.BaseHandler = BaseHandler; + result.ProtectedHandler = ProtectedHandler; + result.ReadOnlyHandler = ReadOnlyHandler; + result.ReadOnlyMockHandler = ReadOnlyMockHandler; + + return result; +} + +exports.createBridge = createBridge; +exports.VMError = VMError; diff --git a/dist/save-only/events.js b/dist/save-only/events.js new file mode 100644 index 0000000..624e827 --- /dev/null +++ b/dist/save-only/events.js @@ -0,0 +1,977 @@ +// 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. + +// Modified by the vm2 team to make this a standalone module to be loaded into the sandbox. + +'use strict'; + +const host = fromhost; + +const { + Boolean, + Error, + String, + Symbol +} = globalThis; + +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; + +const ErrorCaptureStackTrace = Error.captureStackTrace; + +const NumberIsNaN = Number.isNaN; + +const ObjectCreate = Object.create; +const ObjectDefineProperty = Object.defineProperty; +const ObjectDefineProperties = Object.defineProperties; +const ObjectGetPrototypeOf = Object.getPrototypeOf; + +const SymbolFor = Symbol.for; + +function uncurryThis(func) { + return (thiz, ...args) => ReflectApply(func, thiz, args); +} + +const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); +const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); + +const kRejection = SymbolFor('nodejs.rejection'); + +function inspect(obj) { + return typeof obj === 'symbol' ? obj.toString() : `${obj}`; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function assert(what, message) { + if (!what) throw new Error(message); +} + +function E(key, msg, Base) { + return function NodeError(...args) { + const error = new Base(); + const message = ReflectApply(msg, error, args); + ObjectDefineProperties(error, { + message: { + value: message, + enumerable: false, + writable: true, + configurable: true, + }, + toString: { + value() { + return `${this.name} [${key}]: ${this.message}`; + }, + enumerable: false, + writable: true, + configurable: true, + }, + }); + error.code = key; + return error; + }; +} + + +const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', + (name, expected, actual) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (StringPrototypeEndsWith(name, ' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (ArrayPrototypeIncludes(kTypes, value)) { + ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); + } else if (RegExpPrototypeTest(classRegExp, value)) { + ArrayPrototypePush(instances, value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + ArrayPrototypePush(other, value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = ArrayPrototypeIndexOf(types, 'object'); + if (pos !== -1) { + ArrayPrototypeSplice(types, pos, 1); + ArrayPrototypePush(instances, 'Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = ArrayPrototypePop(types); + msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = ArrayPrototypePop(instances); + msg += + `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = ArrayPrototypePop(other); + msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (StringPrototypeToLowerCase(other[0]) !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; + }, TypeError); + +const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); + +const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', + (str, range, input, replaceDefaultBoolean = false) => { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean ? str : + `The value of "${str}" is out of range.`; + const received = inspect(input); + msg += ` It must be ${range}. Received ${received}`; + return msg; + }, RangeError); + +const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', + err => { + const msg = 'Unhandled error.'; + if (err === undefined) return msg; + return `${msg} (${err})`; + }, Error); + +function validateBoolean(value, name) { + if (typeof value !== 'boolean') + throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); +} + +function validateFunction(value, name) { + if (typeof value !== 'function') + throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); +} + +function validateString(value, name) { + if (typeof value !== 'string') + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); +} + +function nc(cond, e) { + return cond === undefined || cond === null ? e : cond; +} + +function oc(base, key) { + return base === undefined || base === null ? undefined : base[key]; +} + +const kCapture = Symbol('kCapture'); +const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); +const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); +const kMaxEventTargetListenersWarned = + Symbol('events.maxEventTargetListenersWarned'); + +const kIsEventTarget = SymbolFor('nodejs.event_target'); + +function isEventTarget(obj) { + return oc(oc(obj, 'constructor'), kIsEventTarget); +} + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @constructs {EventEmitter} + */ +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +module.exports = EventEmitter; +if (host.once) module.exports.once = host.once; +if (host.on) module.exports.on = host.on; +if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, 'EventEmitter.captureRejections'); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +if (host.EventEmitterReferencingAsyncResource) { + const kAsyncResource = Symbol('kAsyncResource'); + const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; + + class EventEmitterAsyncResource extends EventEmitter { + /** + * @param {{ + * name?: string, + * triggerAsyncId?: number, + * requireManualDestroy?: boolean, + * }} [options] + */ + constructor(options = undefined) { + let name; + if (typeof options === 'string') { + name = options; + options = undefined; + } else { + if (new.target === EventEmitterAsyncResource) { + validateString(oc(options, 'name'), 'options.name'); + } + name = oc(options, 'name') || new.target.name; + } + super(options); + + this[kAsyncResource] = + new EventEmitterReferencingAsyncResource(this, name, options); + } + + /** + * @param {symbol,string} event + * @param {...any} args + * @returns {boolean} + */ + emit(event, ...args) { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + const { asyncResource } = this; + ArrayPrototypeUnshift(args, super.emit, this, event); + return ReflectApply(asyncResource.runInAsyncScope, asyncResource, + args); + } + + /** + * @returns {void} + */ + emitDestroy() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + this.asyncResource.emitDestroy(); + } + + /** + * @type {number} + */ + get asyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.asyncId(); + } + + /** + * @type {number} + */ + get triggerAsyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.triggerAsyncId(); + } + + /** + * @type {EventEmitterReferencingAsyncResource} + */ + get asyncResource() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this[kAsyncResource]; + } + } + EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; +} + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, 'listener'); +} + +ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new ERR_OUT_OF_RANGE('defaultMaxListeners', + 'a non-negative number', + arg); + } + defaultMaxListeners = arg; + } +}); + +ObjectDefineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + } +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +EventEmitter.setMaxListeners = + function(n = defaultMaxListeners, ...eventTargets) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (isEventTarget(target)) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === 'function') { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + 'eventTargets', + ['EventEmitter', 'EventTarget'], + target); + } + } + } + }; + +// If you're updating this function definition, please also update any +// re-definitions, such as the one in the Domain module (lib/domain.js). +EventEmitter.init = function(opts) { + + if (this._events === undefined || + this._events === ObjectGetPrototypeOf(this)._events) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + + if (oc(opts, 'captureRejections')) { + validateBoolean(opts.captureRejections, 'options.captureRejections'); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catchable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = (type === 'error'); + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) + this.emit(kErrorMonitor, ...args); + doError = (doError && events.error === undefined); + } else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + try { + const capture = {}; + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); + } catch (e) {} + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch (e) { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = ObjectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + nc(listener.listener, listener)); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error('Possible EventEmitter memory leak detected. ' + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + 'emitter.setMaxListeners() to increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + checkListener(listener); + + const events = this._events; + if (events === undefined) + return this; + + const list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + const events = this._events; + if (events === undefined) + return this; + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else + delete events[type]; + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of ReflectOwnKeys(events)) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = ObjectCreate(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) + return []; + + const evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } + return emitter.listenerCount(type); +}; + +EventEmitter.prototype.listenerCount = listenerCount; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return ArrayPrototypeSlice(arr); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === 'function') + ret[i] = orig; + } + return ret; +} diff --git a/dist/save-only/setup-node-sandbox.js b/dist/save-only/setup-node-sandbox.js new file mode 100644 index 0000000..05f38ef --- /dev/null +++ b/dist/save-only/setup-node-sandbox.js @@ -0,0 +1,469 @@ +/* global host, data, VMError */ + +'use strict'; + +const LocalError = Error; +const LocalTypeError = TypeError; +const LocalWeakMap = WeakMap; + +const { + apply: localReflectApply, + defineProperty: localReflectDefineProperty +} = Reflect; + +const { + set: localWeakMapSet, + get: localWeakMapGet +} = LocalWeakMap.prototype; + +const { + isArray: localArrayIsArray +} = Array; + +function uncurryThis(func) { + return (thiz, ...args) => localReflectApply(func, thiz, args); +} + +const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); +const localArrayPrototypePush = uncurryThis(Array.prototype.push); +const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); +const localStringPrototypeSlice = uncurryThis(String.prototype.slice); +const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); + +const { + argv: optionArgv, + env: optionEnv, + console: optionConsole, + vm, + resolver, + extensions +} = data; + +function ensureSandboxArray(a) { + return localArrayPrototypeSlice(a); +} + +const globalPaths = ensureSandboxArray(resolver.globalPaths); + +class Module { + + constructor(id, path, parent) { + this.id = id; + this.filename = id; + this.path = path; + this.parent = parent; + this.loaded = false; + this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; + this.children = []; + this.exports = {}; + } + + _updateChildren(child, isNew) { + const children = this.children; + if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { + localArrayPrototypePush(children, child); + } + } + + require(id) { + return requireImpl(this, id, false); + } + +} + +const originalRequire = Module.prototype.require; +const cacheBuiltins = {__proto__: null}; + +function requireImpl(mod, id, direct) { + if (direct && mod.require !== originalRequire) { + return mod.require(id); + } + const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); + if (localStringPrototypeStartsWith(filename, 'node:')) { + id = localStringPrototypeSlice(filename, 5); + let nmod = cacheBuiltins[id]; + if (!nmod) { + nmod = resolver.loadBuiltinModule(vm, id); + if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); + cacheBuiltins[id] = nmod; + } + return nmod; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + mod._updateChildren(cachedModule, false); + return cachedModule.exports; + } + + let nmod = cacheBuiltins[id]; + if (nmod) return nmod; + nmod = resolver.loadBuiltinModule(vm, id); + if (nmod) { + cacheBuiltins[id] = nmod; + return nmod; + } + + const path = resolver.fs.dirname(filename); + const module = new Module(filename, path, mod); + resolver.registerModule(module, filename, path, mod, direct); + mod._updateChildren(module, true); + try { + Module._cache[filename] = module; + const handler = findBestExtensionHandler(filename); + handler(module, filename); + module.loaded = true; + } catch (e) { + delete Module._cache[filename]; + const children = mod.children; + if (localArrayIsArray(children)) { + const index = localArrayPrototypeIndexOf(children, module); + if (index !== -1) { + localArrayPrototypeSplice(children, index, 1); + } + } + throw e; + } + + return module.exports; +} + +Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); +Module.globalPaths = globalPaths; +Module._extensions = {__proto__: null}; +Module._cache = {__proto__: null}; + +{ + const keys = Object.getOwnPropertyNames(extensions); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const handler = extensions[key]; + Module._extensions[key] = (mod, filename) => handler(mod, filename); + } +} + +function findBestExtensionHandler(filename) { + const name = resolver.fs.basename(filename); + for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { + const ext = localStringPrototypeSlice(name, i); + const handler = Module._extensions[ext]; + if (handler) return handler; + } + const js = Module._extensions['.js']; + if (js) return js; + const keys = Object.getOwnPropertyNames(Module._extensions); + if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); + return Module._extensions[keys[0]]; +} + +function createRequireForModule(mod) { + // eslint-disable-next-line no-shadow + function require(id) { + return requireImpl(mod, id, true); + } + function resolve(id, options) { + return resolver.resolve(mod, id, options, Module._extensions, true); + } + require.resolve = resolve; + function paths(id) { + return ensureSandboxArray(resolver.lookupPaths(mod, id)); + } + resolve.paths = paths; + + require.extensions = Module._extensions; + + require.cache = Module._cache; + + return require; +} + +/** + * Prepare sandbox. + */ + +const TIMERS = new LocalWeakMap(); + +class Timeout { +} + +class Interval { +} + +class Immediate { +} + +function clearTimer(timer) { + const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); + if (obj) { + obj.clear(obj.value); + } +} + +// This is a function and not an arrow function, since the original is also a function +// eslint-disable-next-line no-shadow +global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Timeout(callback, args); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setTimeout(cb, delay); + + const ref = { + __proto__: null, + clear: host.clearTimeout, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Interval(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setInterval(cb, interval); + + const ref = { + __proto__: null, + clear: host.clearInterval, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Immediate(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setImmediate(cb); + + const ref = { + __proto__: null, + clear: host.clearImmediate, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.clearTimeout = function clearTimeout(timeout) { + clearTimer(timeout); +}; + +// eslint-disable-next-line no-shadow +global.clearInterval = function clearInterval(interval) { + clearTimer(interval); +}; + +// eslint-disable-next-line no-shadow +global.clearImmediate = function clearImmediate(immediate) { + clearTimer(immediate); +}; + +const localProcess = host.process; + +function vmEmitArgs(event, args) { + const allargs = [event]; + for (let i = 0; i < args.length; i++) { + if (!localReflectDefineProperty(allargs, i + 1, { + __proto__: null, + value: args[i], + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + } + return localReflectApply(vm.emit, vm, allargs); +} + +const LISTENERS = new LocalWeakMap(); +const LISTENER_HANDLER = new LocalWeakMap(); + +/** + * + * @param {*} name + * @param {*} handler + * @this process + * @return {this} + */ +function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (!cb) { + cb = () => { + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); + } + + localProcess.on(name, cb); + + return this; +} + +/** + * + * @this process + * @return {this} + */ +// eslint-disable-next-line no-shadow +function process() { + return this; +} + +const baseUptime = localProcess.uptime(); + +// FIXME wrong class structure +global.process = { + __proto__: process.prototype, + argv: optionArgv !== undefined ? optionArgv : [], + title: localProcess.title, + version: localProcess.version, + versions: localProcess.versions, + arch: localProcess.arch, + platform: localProcess.platform, + env: optionEnv !== undefined ? optionEnv : {}, + pid: localProcess.pid, + features: localProcess.features, + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new LocalError('Callback must be a function.'); + } + + localProcess.nextTick(()=>{ + localReflectApply(callback, null, args); + }); + }, + hrtime: function hrtime(time) { + return localProcess.hrtime(time); + }, + uptime: function uptime() { + return localProcess.uptime() - baseUptime; + }, + cwd: function cwd() { + return localProcess.cwd(); + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let triggered = false; + const cb = () => { + if (triggered) return; + triggered = true; + localProcess.removeListener(name, cb); + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + + localProcess.on(name, cb); + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + const all = localProcess.listeners(name); + const filtered = []; + let j = 0; + for (let i = 0; i < all.length; i++) { + const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); + if (h) { + if (!localReflectDefineProperty(filtered, j, { + __proto__: null, + value: h, + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + j++; + } + } + return filtered; + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (cb) localProcess.removeListener(name, cb); + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new LocalError('Access denied to set umask.'); + } + + return localProcess.umask(); + } +}; + +if (optionConsole === 'inherit') { + global.console = host.console; +} else if (optionConsole === 'redirect') { + global.console = { + debug(...args) { + vmEmitArgs('console.debug', args); + }, + log(...args) { + vmEmitArgs('console.log', args); + }, + info(...args) { + vmEmitArgs('console.info', args); + }, + warn(...args) { + vmEmitArgs('console.warn', args); + }, + error(...args) { + vmEmitArgs('console.error', args); + }, + dir(...args) { + vmEmitArgs('console.dir', args); + }, + time() {}, + timeEnd() {}, + trace(...args) { + vmEmitArgs('console.trace', args); + } + }; +} + +return { + __proto__: null, + Module, + jsonParse: JSON.parse, + createRequireForModule, + requireImpl +}; diff --git a/dist/save-only/setup-sandbox.js b/dist/save-only/setup-sandbox.js new file mode 100644 index 0000000..539ce04 --- /dev/null +++ b/dist/save-only/setup-sandbox.js @@ -0,0 +1,474 @@ +/* global host, bridge, data, context */ + +'use strict'; + +const { + Object: localObject, + Array: localArray, + Error: LocalError, + Reflect: localReflect, + Proxy: LocalProxy, + WeakMap: LocalWeakMap, + Function: localFunction, + Promise: localPromise, + eval: localEval +} = global; + +const { + freeze: localObjectFreeze +} = localObject; + +const { + getPrototypeOf: localReflectGetPrototypeOf, + apply: localReflectApply, + deleteProperty: localReflectDeleteProperty, + has: localReflectHas, + defineProperty: localReflectDefineProperty, + setPrototypeOf: localReflectSetPrototypeOf, + getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor +} = localReflect; + +const { + isArray: localArrayIsArray +} = localArray; + +const { + ensureThis, + ReadOnlyHandler, + from, + fromWithFactory, + readonlyFactory, + connect, + addProtoMapping, + VMError, + ReadOnlyMockHandler +} = bridge; + +const { + allowAsync, + GeneratorFunction, + AsyncFunction, + AsyncGeneratorFunction +} = data; + +const { + get: localWeakMapGet, + set: localWeakMapSet +} = LocalWeakMap.prototype; + +function localUnexpected() { + return new VMError('Should not happen'); +} + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); + +Object.defineProperties(global, { + global: {value: global, writable: true, configurable: true, enumerable: true}, + globalThis: {value: global, writable: true, configurable: true}, + GLOBAL: {value: global, writable: true, configurable: true}, + root: {value: global, writable: true, configurable: true}, + Error: {value: LocalError} +}); + +if (!localReflectDefineProperty(global, 'VMError', { + __proto__: null, + value: VMError, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +// Fixes buffer unsafe allocation +/* eslint-disable no-use-before-define */ +class BufferHandler extends ReadOnlyHandler { + + apply(target, thiz, args) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + + construct(target, args, newTarget) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + +} +/* eslint-enable no-use-before-define */ + +const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); + + +if (!localReflectDefineProperty(global, 'Buffer', { + __proto__: null, + value: LocalBuffer, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafe(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafe, host.Buffer.allocUnsafe); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafeSlow(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); + +/** + * Replacement for Buffer inspect + * + * @param {*} recurseTimes + * @param {*} ctx + * @this LocalBuffer + * @return {string} + */ +function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +} + +connect(inspect, host.Buffer.prototype.inspect); + +connect(localFunction.prototype.bind, host.Function.prototype.bind); + +connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); +connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); +connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); +connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); + +/* + * PrepareStackTrace sanitization + */ + +const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); + +let currentPrepareStackTrace = LocalError.prepareStackTrace; +const wrappedPrepareStackTrace = new LocalWeakMap(); +if (typeof currentPrepareStackTrace === 'function') { + wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); +} + +let OriginalCallSite; +LocalError.prepareStackTrace = (e, sst) => { + OriginalCallSite = sst[0].constructor; +}; +new LocalError().stack; +if (typeof OriginalCallSite === 'function') { + LocalError.prepareStackTrace = undefined; + + function makeCallSiteGetters(list) { + const callSiteGetters = []; + for (let i=0; i { + return localReflectApply(func, thiz, []); + } + }; + } + return callSiteGetters; + } + + function applyCallSiteGetters(thiz, callSite, getters) { + for (let i=0; i { + const sandboxSst = ensureThis(sst); + if (localArrayIsArray(sst)) { + if (sst === sandboxSst) { + for (let i=0; i < sst.length; i++) { + const cs = sst[i]; + if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { + sst[i] = new CallSite(cs); + } + } + } else { + sst = []; + for (let i=0; i < sandboxSst.length; i++) { + const cs = sandboxSst[i]; + localReflectDefineProperty(sst, i, { + __proto__: null, + value: new CallSite(cs), + enumerable: true, + configurable: true, + writable: true + }); + } + } + } else { + sst = sandboxSst; + } + return value(error, sst); + }; + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); + currentPrepareStackTrace = newWrapped; + } + })) throw localUnexpected(); +} else if (oldPrepareStackTraceDesc) { + localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); +} else { + localReflectDeleteProperty(LocalError, 'prepareStackTrace'); +} + +/* + * Exception sanitization + */ + +const withProxy = localObjectFreeze({ + __proto__: null, + has(target, key) { + if (key === host.INTERNAL_STATE_NAME) return false; + return localReflectHas(target, key); + } +}); + +const interanState = localObjectFreeze({ + __proto__: null, + wrapWith(x) { + if (x === null || x === undefined) return x; + return new LocalProxy(localObject(x), withProxy); + }, + handleException: ensureThis, + import(what) { + throw new VMError('Dynamic Import not supported'); + } +}); + +if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { + __proto__: null, + configurable: false, + enumerable: false, + writable: false, + value: interanState +})) throw localUnexpected(); + +/* + * Eval sanitization + */ + +function throwAsync() { + return new VMError('Async not available'); +} + +function makeFunction(inputArgs, isAsync, isGenerator) { + const lastArgs = inputArgs.length - 1; + let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; + let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; + for (let i = 1; i < lastArgs; i++) { + args += `,${inputArgs[i]}`; + } + try { + code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); +} + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + return makeFunction(args, this.isAsync, this.isGenerator); + }, + construct(target, args, newTarget) { + return makeFunction(args, this.isAsync, this.isGenerator); + } +}; + +const EvalHandler = { + __proto__: null, + apply(target, thiz, args) { + if (args.length === 0) return undefined; + let code = `${args[0]}`; + try { + code = host.transformAndCheck(null, code, false, false, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); + } +}; + +const AsyncErrorHandler = { + __proto__: null, + apply(target, thiz, args) { + throw throwAsync(); + }, + construct(target, args, newTarget) { + throw throwAsync(); + } +}; + +function makeCheckFunction(isAsync, isGenerator) { + if (isAsync && !allowAsync) return AsyncErrorHandler; + return { + __proto__: FunctionHandler, + isAsync, + isGenerator + }; +} + +function overrideWithProxy(obj, prop, value, handler) { + const proxy = new LocalProxy(value, handler); + if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); + return proxy; +} + +const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); +if (GeneratorFunction) { + if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); +} +if (AsyncFunction) { + if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); +} +if (AsyncGeneratorFunction) { + if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); +} + +global.Function = proxiedFunction; +global.eval = new LocalProxy(localEval, EvalHandler); + +/* + * Promise sanitization + */ + +if (localPromise && !allowAsync) { + + const PromisePrototype = localPromise.prototype; + + overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); + // This seems not to work, and will produce + // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. + // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. + // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (PromisePrototype.finally) { + overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} + +function readonly(other, mock) { + // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) + if (!mock) return fromWithFactory(readonlyFactory, other); + const tmock = from(mock); + return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); +} + +return { + __proto__: null, + readonly, + global +}; diff --git a/dist/save/bridge.js b/dist/save/bridge.js new file mode 100644 index 0000000..8690b51 --- /dev/null +++ b/dist/save/bridge.js @@ -0,0 +1,1010 @@ +'use strict'; + +/** + * __ ___ ____ _ _ ___ _ _ ____ + * \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| + * \ \ /\ / / _ \ | |_) | \| || || \| | | _ + * \ V V / ___ \| _ <| |\ || || |\ | |_| | + * \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| + * + * This file is critical for vm2. It implements the bridge between the host and the sandbox. + * If you do not know exactly what you are doing, you should NOT edit this file. + * + * The file is loaded in the host and sandbox to handle objects in both directions. + * This is done to ensure that RangeErrors are from the correct context. + * The boundary between the sandbox and host might throw RangeErrors from both contexts. + * Therefore, thisFromOther and friends can handle objects from both domains. + * + * Method parameters have comments to tell from which context they came. + * + */ + +const globalsList = [ + 'Number', + 'String', + 'Boolean', + 'Date', + 'RegExp', + 'Map', + 'WeakMap', + 'Set', + 'WeakSet', + 'Promise', + 'Function' +]; + +const errorsList = [ + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'EvalError', + 'URIError', + 'Error' +]; + +const OPNA = 'Operation not allowed on contextified object.'; + +const thisGlobalPrototypes = { + __proto__: null, + Object: Object.prototype, + Array: Array.prototype +}; + +for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const g = global[key]; + if (g) thisGlobalPrototypes[key] = g.prototype; +} + +const { + getPrototypeOf: thisReflectGetPrototypeOf, + setPrototypeOf: thisReflectSetPrototypeOf, + defineProperty: thisReflectDefineProperty, + deleteProperty: thisReflectDeleteProperty, + getOwnPropertyDescriptor: thisReflectGetOwnPropertyDescriptor, + isExtensible: thisReflectIsExtensible, + preventExtensions: thisReflectPreventExtensions, + apply: thisReflectApply, + construct: thisReflectConstruct, + set: thisReflectSet, + get: thisReflectGet, + has: thisReflectHas, + ownKeys: thisReflectOwnKeys, + enumerate: thisReflectEnumerate, +} = Reflect; + +const thisObject = Object; +const { + freeze: thisObjectFreeze, + prototype: thisObjectPrototype +} = thisObject; +const thisObjectHasOwnProperty = thisObjectPrototype.hasOwnProperty; +const ThisProxy = Proxy; +const ThisWeakMap = WeakMap; +const { + get: thisWeakMapGet, + set: thisWeakMapSet +} = ThisWeakMap.prototype; +const ThisMap = Map; +const thisMapGet = ThisMap.prototype.get; +const thisMapSet = ThisMap.prototype.set; +const thisFunction = Function; +const thisFunctionBind = thisFunction.prototype.bind; +const thisArrayIsArray = Array.isArray; +const thisErrorCaptureStackTrace = Error.captureStackTrace; + +const thisSymbolToString = Symbol.prototype.toString; +const thisSymbolToStringTag = Symbol.toStringTag; +const thisSymbolIterator = Symbol.iterator; +const thisSymbolNodeJSUtilInspectCustom = Symbol.for('nodejs.util.inspect.custom'); + +/** + * VMError. + * + * @public + * @extends {Error} + */ +class VMError extends Error { + + /** + * Create VMError instance. + * + * @public + * @param {string} message - Error message. + * @param {string} code - Error code. + */ + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + thisErrorCaptureStackTrace(this, this.constructor); + } +} + +thisGlobalPrototypes['VMError'] = VMError.prototype; + +function thisUnexpected() { + return new VMError('Unexpected'); +} + +if (!thisReflectSetPrototypeOf(exports, null)) throw thisUnexpected(); + +function thisSafeGetOwnPropertyDescriptor(obj, key) { + const desc = thisReflectGetOwnPropertyDescriptor(obj, key); + if (!desc) return desc; + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + return desc; +} + +function thisThrowCallerCalleeArgumentsAccess(key) { + 'use strict'; + thisThrowCallerCalleeArgumentsAccess[key]; + return thisUnexpected(); +} + +function thisIdMapping(factory, other) { + return other; +} + +const thisThrowOnKeyAccessHandler = thisObjectFreeze({ + __proto__: null, + get(target, key, receiver) { + if (typeof key === 'symbol') { + key = thisReflectApply(thisSymbolToString, key, []); + } + throw new VMError(`Unexpected access to key '${key}'`); + } +}); + +const emptyForzenObject = thisObjectFreeze({ + __proto__: null +}); + +const thisThrowOnKeyAccess = new ThisProxy(emptyForzenObject, thisThrowOnKeyAccessHandler); + +function SafeBase() {} + +if (!thisReflectDefineProperty(SafeBase, 'prototype', { + __proto__: null, + value: thisThrowOnKeyAccess +})) throw thisUnexpected(); + +function SHARED_FUNCTION() {} + +const TEST_PROXY_HANDLER = thisObjectFreeze({ + __proto__: thisThrowOnKeyAccess, + construct() { + return this; + } +}); + +function thisIsConstructor(obj) { + // Note: obj@any(unsafe) + const Func = new ThisProxy(obj, TEST_PROXY_HANDLER); + try { + // eslint-disable-next-line no-new + new Func(); + return true; + } catch (e) { + return false; + } +} + +function thisCreateTargetObject(obj, proto) { + // Note: obj@any(unsafe) proto@any(unsafe) returns@this(unsafe) throws@this(unsafe) + let base; + if (typeof obj === 'function') { + if (thisIsConstructor(obj)) { + // Bind the function since bound functions do not have a prototype property. + base = thisReflectApply(thisFunctionBind, SHARED_FUNCTION, [null]); + } else { + base = () => {}; + } + } else if (thisArrayIsArray(obj)) { + base = []; + } else { + return {__proto__: proto}; + } + if (!thisReflectSetPrototypeOf(base, proto)) throw thisUnexpected(); + return base; +} + +function createBridge(otherInit, registerProxy) { + + const mappingOtherToThis = new ThisWeakMap(); + const protoMappings = new ThisMap(); + const protoName = new ThisMap(); + + function thisAddProtoMapping(proto, other, name) { + // Note: proto@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => thisProxyOther(factory, object, proto)]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + + function thisAddProtoMappingFactory(protoFactory, other, name) { + // Note: protoFactory@this(unsafe) other@other(unsafe) name@this(unsafe) throws@this(unsafe) + let proto; + thisReflectApply(thisMapSet, protoMappings, [other, + (factory, object) => { + if (!proto) { + proto = protoFactory(); + thisReflectApply(thisMapSet, protoMappings, [proto, thisIdMapping]); + if (name) thisReflectApply(thisMapSet, protoName, [proto, name]); + } + return thisProxyOther(factory, object, proto); + }]); + } + + const result = { + __proto__: null, + globalPrototypes: thisGlobalPrototypes, + safeGetOwnPropertyDescriptor: thisSafeGetOwnPropertyDescriptor, + fromArguments: thisFromOtherArguments, + from: thisFromOther, + fromWithFactory: thisFromOtherWithFactory, + ensureThis: thisEnsureThis, + mapping: mappingOtherToThis, + connect: thisConnect, + reflectSet: thisReflectSet, + reflectGet: thisReflectGet, + reflectDefineProperty: thisReflectDefineProperty, + reflectDeleteProperty: thisReflectDeleteProperty, + reflectApply: thisReflectApply, + reflectConstruct: thisReflectConstruct, + reflectHas: thisReflectHas, + reflectOwnKeys: thisReflectOwnKeys, + reflectEnumerate: thisReflectEnumerate, + reflectGetPrototypeOf: thisReflectGetPrototypeOf, + reflectIsExtensible: thisReflectIsExtensible, + reflectPreventExtensions: thisReflectPreventExtensions, + objectHasOwnProperty: thisObjectHasOwnProperty, + weakMapSet: thisWeakMapSet, + addProtoMapping: thisAddProtoMapping, + addProtoMappingFactory: thisAddProtoMappingFactory, + defaultFactory, + protectedFactory, + readonlyFactory, + VMError + }; + + const isHost = typeof otherInit !== 'object'; + + if (isHost) { + otherInit = otherInit(result, registerProxy); + } + + result.other = otherInit; + + const { + globalPrototypes: otherGlobalPrototypes, + safeGetOwnPropertyDescriptor: otherSafeGetOwnPropertyDescriptor, + fromArguments: otherFromThisArguments, + from: otherFromThis, + mapping: mappingThisToOther, + reflectSet: otherReflectSet, + reflectGet: otherReflectGet, + reflectDefineProperty: otherReflectDefineProperty, + reflectDeleteProperty: otherReflectDeleteProperty, + reflectApply: otherReflectApply, + reflectConstruct: otherReflectConstruct, + reflectHas: otherReflectHas, + reflectOwnKeys: otherReflectOwnKeys, + reflectEnumerate: otherReflectEnumerate, + reflectGetPrototypeOf: otherReflectGetPrototypeOf, + reflectIsExtensible: otherReflectIsExtensible, + reflectPreventExtensions: otherReflectPreventExtensions, + objectHasOwnProperty: otherObjectHasOwnProperty, + weakMapSet: otherWeakMapSet + } = otherInit; + + function thisOtherHasOwnProperty(object, key) { + // Note: object@other(safe) key@prim throws@this(unsafe) + try { + return otherReflectApply(otherObjectHasOwnProperty, object, [key]) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + function thisDefaultGet(handler, object, key, desc) { + // Note: object@other(unsafe) key@prim desc@other(safe) + let ret; // @other(unsafe) + if (desc.get || desc.set) { + const getter = desc.get; + if (!getter) return undefined; + try { + ret = otherReflectApply(getter, object, [key]); + } catch (e) { + throw thisFromOtherForThrow(e); + } + } else { + ret = desc.value; + } + return handler.fromOtherWithContext(ret); + } + + function otherFromThisIfAvailable(to, from, key) { + // Note: to@other(safe) from@this(safe) key@prim throws@this(unsafe) + if (!thisReflectApply(thisObjectHasOwnProperty, from, [key])) return false; + try { + to[key] = otherFromThis(from[key]); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return true; + } + + class BaseHandler extends SafeBase { + + constructor(object) { + // Note: object@other(unsafe) throws@this(unsafe) + super(); + this.objectWrapper = () => object; + } + + getObject() { + return this.objectWrapper(); + } + + getFactory() { + return defaultFactory; + } + + fromOtherWithContext(other) { + // Note: other@other(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(this.getFactory(), other); + } + + doPreventExtensions(target, object, factory) { + // Note: target@this(unsafe) object@other(unsafe) throws@this(unsafe) + let keys; // @other(safe-array-of-prim) + try { + keys = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; // @prim + let desc; + try { + desc = otherSafeGetOwnPropertyDescriptor(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!desc) continue; + if (!desc.configurable) { + const current = thisSafeGetOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = this.fromOtherWithContext(desc.get); + desc.set = this.fromOtherWithContext(desc.set); + } else if (typeof object === 'function' && (key === 'caller' || key === 'callee' || key === 'arguments')) { + desc.value = null; + } else { + desc.value = this.fromOtherWithContext(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!thisReflectDefineProperty(target, key, desc)) throw thisUnexpected(); + } + if (!thisReflectPreventExtensions(target)) throw thisUnexpected(); + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + switch (key) { + case 'constructor': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + const proto = thisReflectGetPrototypeOf(target); + return proto === null ? undefined : proto.constructor; + } + case '__proto__': { + const desc = otherSafeGetOwnPropertyDescriptor(object, key); + if (desc) return thisDefaultGet(this, object, key, desc); + return thisReflectGetPrototypeOf(target); + } + case thisSymbolToStringTag: + if (!thisOtherHasOwnProperty(object, thisSymbolToStringTag)) { + const proto = thisReflectGetPrototypeOf(target); + const name = thisReflectApply(thisMapGet, protoName, [proto]); + if (name) return name; + } + break; + case 'arguments': + case 'caller': + case 'callee': + if (typeof object === 'function' && thisOtherHasOwnProperty(object, key)) { + throw thisThrowCallerCalleeArgumentsAccess(key); + } + break; + } + let ret; // @other(unsafe) + try { + ret = otherReflectGet(object, key); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(ret); + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (key === '__proto__' && !thisOtherHasOwnProperty(object, key)) { + return this.setPrototypeOf(target, value); + } + try { + value = otherFromThis(value); + return otherReflectSet(object, key, value) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + getPrototypeOf(target) { + // Note: target@this(unsafe) + return thisReflectGetPrototypeOf(target); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + throw new VMError(OPNA); + } + + apply(target, context, args) { + // Note: target@this(unsafe) context@this(unsafe) args@this(safe-array) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + context = otherFromThis(context); + args = otherFromThisArguments(args); + ret = otherReflectApply(object, context, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(ret); + } + + construct(target, args, newTarget) { + // Note: target@this(unsafe) args@this(safe-array) newTarget@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let ret; // @other(unsafe) + try { + args = otherFromThisArguments(args); + ret = otherReflectConstruct(object, args); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOtherWithFactory(this.getFactory(), ret, thisFromOther(object)); + } + + getOwnPropertyDescriptorDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@other{safe} throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (desc && typeof object === 'function' && (prop === 'arguments' || prop === 'caller' || prop === 'callee')) desc.value = null; + return desc; + } + + getOwnPropertyDescriptor(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let desc; // @other(safe) + try { + desc = otherSafeGetOwnPropertyDescriptor(object, prop); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + desc = this.getOwnPropertyDescriptorDesc(target, prop, desc); + + if (!desc) return undefined; + + let thisDesc; + if (desc.get || desc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(desc.get), + set: this.fromOtherWithContext(desc.set), + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(desc.value), + writable: desc.writable === true, + enumerable: desc.enumerable === true, + configurable: desc.configurable === true + }; + } + if (!thisDesc.configurable) { + const oldDesc = thisSafeGetOwnPropertyDescriptor(target, prop); + if (!oldDesc || oldDesc.configurable || oldDesc.writable !== thisDesc.writable) { + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + } + return thisDesc; + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + return desc; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + if (!thisReflectSetPrototypeOf(desc, null)) throw thisUnexpected(); + + desc = this.definePropertyDesc(target, prop, desc); + + if (!desc) return false; + + let otherDesc = {__proto__: null}; + let hasFunc = true; + let hasValue = true; + let hasBasic = true; + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'get'); + hasFunc &= otherFromThisIfAvailable(otherDesc, desc, 'set'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'value'); + hasValue &= otherFromThisIfAvailable(otherDesc, desc, 'writable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'enumerable'); + hasBasic &= otherFromThisIfAvailable(otherDesc, desc, 'configurable'); + + try { + if (!otherReflectDefineProperty(object, prop, otherDesc)) return false; + if (otherDesc.configurable !== true && (!hasBasic || !(hasFunc || hasValue))) { + otherDesc = otherSafeGetOwnPropertyDescriptor(object, prop); + } + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + + if (!otherDesc.configurable) { + let thisDesc; + if (otherDesc.get || otherDesc.set) { + thisDesc = { + __proto__: null, + get: this.fromOtherWithContext(otherDesc.get), + set: this.fromOtherWithContext(otherDesc.set), + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } else { + thisDesc = { + __proto__: null, + value: this.fromOtherWithContext(otherDesc.value), + writable: otherDesc.writable, + enumerable: otherDesc.enumerable, + configurable: otherDesc.configurable + }; + } + if (!thisReflectDefineProperty(target, prop, thisDesc)) throw thisUnexpected(); + } + return true; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectDeleteProperty(object, prop) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + has(target, key) { + // Note: target@this(unsafe) key@prim throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + return otherReflectHas(object, key) === true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (otherReflectIsExtensible(object)) return true; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return false; + } + + ownKeys(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectOwnKeys(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return thisFromOther(res); + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + try { + if (!otherReflectPreventExtensions(object)) return false; + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (thisReflectIsExtensible(target)) { + this.doPreventExtensions(target, object, this); + } + return true; + } + + enumerate(target) { + // Note: target@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + let res; // @other(unsafe) + try { + res = otherReflectEnumerate(object); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + return this.fromOtherWithContext(res); + } + + } + + BaseHandler.prototype[thisSymbolNodeJSUtilInspectCustom] = undefined; + BaseHandler.prototype[thisSymbolToStringTag] = 'VM2 Wrapper'; + BaseHandler.prototype[thisSymbolIterator] = undefined; + + function defaultFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new BaseHandler(object); + } + + class ProtectedHandler extends BaseHandler { + + getFactory() { + return protectedFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + if (typeof value === 'function') { + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }) === true; + } + return super.set(target, key, value, receiver); + } + + definePropertyDesc(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(safe) throws@this(unsafe) + if (desc && (desc.set || desc.get || typeof desc.value === 'function')) return undefined; + return desc; + } + + } + + function protectedFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ProtectedHandler(object); + } + + class ReadOnlyHandler extends BaseHandler { + + getFactory() { + return readonlyFactory; + } + + set(target, key, value, receiver) { + // Note: target@this(unsafe) key@prim value@this(unsafe) receiver@this(unsafe) throws@this(unsafe) + return thisReflectDefineProperty(receiver, key, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + + setPrototypeOf(target, value) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + defineProperty(target, prop, desc) { + // Note: target@this(unsafe) prop@prim desc@this(unsafe) throws@this(unsafe) + return false; + } + + deleteProperty(target, prop) { + // Note: target@this(unsafe) prop@prim throws@this(unsafe) + return false; + } + + isExtensible(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + preventExtensions(target) { + // Note: target@this(unsafe) throws@this(unsafe) + return false; + } + + } + + function readonlyFactory(object) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return new ReadOnlyHandler(object); + } + + class ReadOnlyMockHandler extends ReadOnlyHandler { + + constructor(object, mock) { + // Note: object@other(unsafe) mock:this(unsafe) throws@this(unsafe) + super(object); + this.mock = mock; + } + + get(target, key, receiver) { + // Note: target@this(unsafe) key@prim receiver@this(unsafe) throws@this(unsafe) + const object = this.getObject(); // @other(unsafe) + const mock = this.mock; + if (thisReflectApply(thisObjectHasOwnProperty, mock, key) && !thisOtherHasOwnProperty(object, key)) { + return mock[key]; + } + return super.get(target, key, receiver); + } + + } + + function thisFromOther(other) { + // Note: other@other(unsafe) returns@this(unsafe) throws@this(unsafe) + return thisFromOtherWithFactory(defaultFactory, other); + } + + function thisProxyOther(factory, other, proto) { + const target = thisCreateTargetObject(other, proto); + const handler = factory(other); + const proxy = new ThisProxy(target, handler); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy, other]); + registerProxy(proxy, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + if (!isHost) { + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy]); + return proxy; + } + const proxy2 = new ThisProxy(proxy, emptyForzenObject); + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [proxy2, other]); + registerProxy(proxy2, handler); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, proxy2]); + return proxy2; + } + + function thisEnsureThis(other) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + let proto = thisReflectGetPrototypeOf(other); + if (!proto) { + return other; + } + while (proto) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) { + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + return mapping(defaultFactory, other); + } + proto = thisReflectGetPrototypeOf(proto); + } + return other; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherForThrow(other) { + for (let loop = 0; loop < 10; loop++) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + let proto; + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) { + return thisProxyOther(defaultFactory, other, null); + } + for (;;) { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(defaultFactory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + other = e; + break; + } + if (!proto) return thisProxyOther(defaultFactory, other, thisObjectPrototype); + } + break; + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + throw new VMError('Exception recursion depth'); + } + + function thisFromOtherWithFactory(factory, other, proto) { + const type = typeof other; + switch (type) { + case 'object': + if (other === null) { + return null; + } + // fallthrough + case 'function': + const mapped = thisReflectApply(thisWeakMapGet, mappingOtherToThis, [other]); + if (mapped) return mapped; + if (proto) { + return thisProxyOther(factory, other, proto); + } + try { + proto = otherReflectGetPrototypeOf(other); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + if (!proto) { + return thisProxyOther(factory, other, null); + } + do { + const mapping = thisReflectApply(thisMapGet, protoMappings, [proto]); + if (mapping) return mapping(factory, other); + try { + proto = otherReflectGetPrototypeOf(proto); + } catch (e) { // @other(unsafe) + throw thisFromOtherForThrow(e); + } + } while (proto); + return thisProxyOther(factory, other, thisObjectPrototype); + case 'undefined': + case 'string': + case 'number': + case 'boolean': + case 'symbol': + case 'bigint': + return other; + + default: // new, unknown types can be dangerous + throw new VMError(`Unknown type '${type}'`); + } + } + + function thisFromOtherArguments(args) { + // Note: args@other(safe-array) returns@this(safe-array) throws@this(unsafe) + const arr = []; + for (let i = 0; i < args.length; i++) { + const value = thisFromOther(args[i]); + thisReflectDefineProperty(arr, i, { + __proto__: null, + value: value, + writable: true, + enumerable: true, + configurable: true + }); + } + return arr; + } + + function thisConnect(obj, other) { + // Note: obj@this(unsafe) other@other(unsafe) throws@this(unsafe) + try { + otherReflectApply(otherWeakMapSet, mappingThisToOther, [obj, other]); + } catch (e) { + throw new VMError('Unexpected error'); + } + thisReflectApply(thisWeakMapSet, mappingOtherToThis, [other, obj]); + } + + thisAddProtoMapping(thisGlobalPrototypes.Object, otherGlobalPrototypes.Object); + thisAddProtoMapping(thisGlobalPrototypes.Array, otherGlobalPrototypes.Array); + + for (let i = 0; i < globalsList.length; i++) { + const key = globalsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, key); + } + + for (let i = 0; i < errorsList.length; i++) { + const key = errorsList[i]; + const tp = thisGlobalPrototypes[key]; + const op = otherGlobalPrototypes[key]; + if (tp && op) thisAddProtoMapping(tp, op, 'Error'); + } + + thisAddProtoMapping(thisGlobalPrototypes.VMError, otherGlobalPrototypes.VMError, 'Error'); + + result.BaseHandler = BaseHandler; + result.ProtectedHandler = ProtectedHandler; + result.ReadOnlyHandler = ReadOnlyHandler; + result.ReadOnlyMockHandler = ReadOnlyMockHandler; + + return result; +} + +exports.createBridge = createBridge; +exports.VMError = VMError; diff --git a/dist/save/events.js b/dist/save/events.js new file mode 100644 index 0000000..624e827 --- /dev/null +++ b/dist/save/events.js @@ -0,0 +1,977 @@ +// 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. + +// Modified by the vm2 team to make this a standalone module to be loaded into the sandbox. + +'use strict'; + +const host = fromhost; + +const { + Boolean, + Error, + String, + Symbol +} = globalThis; + +const ReflectApply = Reflect.apply; +const ReflectOwnKeys = Reflect.ownKeys; + +const ErrorCaptureStackTrace = Error.captureStackTrace; + +const NumberIsNaN = Number.isNaN; + +const ObjectCreate = Object.create; +const ObjectDefineProperty = Object.defineProperty; +const ObjectDefineProperties = Object.defineProperties; +const ObjectGetPrototypeOf = Object.getPrototypeOf; + +const SymbolFor = Symbol.for; + +function uncurryThis(func) { + return (thiz, ...args) => ReflectApply(func, thiz, args); +} + +const ArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const ArrayPrototypeJoin = uncurryThis(Array.prototype.join); +const ArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const ArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const ArrayPrototypeUnshift = uncurryThis(Array.prototype.unshift); + +const kRejection = SymbolFor('nodejs.rejection'); + +function inspect(obj) { + return typeof obj === 'symbol' ? obj.toString() : `${obj}`; +} + +function spliceOne(list, index) { + for (; index + 1 < list.length; index++) + list[index] = list[index + 1]; + list.pop(); +} + +function assert(what, message) { + if (!what) throw new Error(message); +} + +function E(key, msg, Base) { + return function NodeError(...args) { + const error = new Base(); + const message = ReflectApply(msg, error, args); + ObjectDefineProperties(error, { + message: { + value: message, + enumerable: false, + writable: true, + configurable: true, + }, + toString: { + value() { + return `${this.name} [${key}]: ${this.message}`; + }, + enumerable: false, + writable: true, + configurable: true, + }, + }); + error.code = key; + return error; + }; +} + + +const ERR_INVALID_ARG_TYPE = E('ERR_INVALID_ARG_TYPE', + (name, expected, actual) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (StringPrototypeEndsWith(name, ' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = StringPrototypeIncludes(name, '.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (ArrayPrototypeIncludes(kTypes, value)) { + ArrayPrototypePush(types, StringPrototypeToLowerCase(value)); + } else if (RegExpPrototypeTest(classRegExp, value)) { + ArrayPrototypePush(instances, value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + ArrayPrototypePush(other, value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = ArrayPrototypeIndexOf(types, 'object'); + if (pos !== -1) { + ArrayPrototypeSplice(types, pos, 1); + ArrayPrototypePush(instances, 'Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = ArrayPrototypePop(types); + msg += `one of type ${ArrayPrototypeJoin(types, ', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = ArrayPrototypePop(instances); + msg += + `an instance of ${ArrayPrototypeJoin(instances, ', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = ArrayPrototypePop(other); + msg += `one of ${ArrayPrototypeJoin(other, ', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (StringPrototypeToLowerCase(other[0]) !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; + }, TypeError); + +const ERR_INVALID_THIS = E('ERR_INVALID_THIS', s => `Value of "this" must be of type ${s}`, TypeError); + +const ERR_OUT_OF_RANGE = E('ERR_OUT_OF_RANGE', + (str, range, input, replaceDefaultBoolean = false) => { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean ? str : + `The value of "${str}" is out of range.`; + const received = inspect(input); + msg += ` It must be ${range}. Received ${received}`; + return msg; + }, RangeError); + +const ERR_UNHANDLED_ERROR = E('ERR_UNHANDLED_ERROR', + err => { + const msg = 'Unhandled error.'; + if (err === undefined) return msg; + return `${msg} (${err})`; + }, Error); + +function validateBoolean(value, name) { + if (typeof value !== 'boolean') + throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); +} + +function validateFunction(value, name) { + if (typeof value !== 'function') + throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); +} + +function validateString(value, name) { + if (typeof value !== 'string') + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); +} + +function nc(cond, e) { + return cond === undefined || cond === null ? e : cond; +} + +function oc(base, key) { + return base === undefined || base === null ? undefined : base[key]; +} + +const kCapture = Symbol('kCapture'); +const kErrorMonitor = host.kErrorMonitor || Symbol('events.errorMonitor'); +const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners'); +const kMaxEventTargetListenersWarned = + Symbol('events.maxEventTargetListenersWarned'); + +const kIsEventTarget = SymbolFor('nodejs.event_target'); + +function isEventTarget(obj) { + return oc(oc(obj, 'constructor'), kIsEventTarget); +} + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @constructs {EventEmitter} + */ +function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +module.exports = EventEmitter; +if (host.once) module.exports.once = host.once; +if (host.on) module.exports.on = host.on; +if (host.getEventListeners) module.exports.getEventListeners = host.getEventListeners; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +ObjectDefineProperty(EventEmitter, 'captureRejections', { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, 'EventEmitter.captureRejections'); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true +}); + +if (host.EventEmitterReferencingAsyncResource) { + const kAsyncResource = Symbol('kAsyncResource'); + const EventEmitterReferencingAsyncResource = host.EventEmitterReferencingAsyncResource; + + class EventEmitterAsyncResource extends EventEmitter { + /** + * @param {{ + * name?: string, + * triggerAsyncId?: number, + * requireManualDestroy?: boolean, + * }} [options] + */ + constructor(options = undefined) { + let name; + if (typeof options === 'string') { + name = options; + options = undefined; + } else { + if (new.target === EventEmitterAsyncResource) { + validateString(oc(options, 'name'), 'options.name'); + } + name = oc(options, 'name') || new.target.name; + } + super(options); + + this[kAsyncResource] = + new EventEmitterReferencingAsyncResource(this, name, options); + } + + /** + * @param {symbol,string} event + * @param {...any} args + * @returns {boolean} + */ + emit(event, ...args) { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + const { asyncResource } = this; + ArrayPrototypeUnshift(args, super.emit, this, event); + return ReflectApply(asyncResource.runInAsyncScope, asyncResource, + args); + } + + /** + * @returns {void} + */ + emitDestroy() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + this.asyncResource.emitDestroy(); + } + + /** + * @type {number} + */ + get asyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.asyncId(); + } + + /** + * @type {number} + */ + get triggerAsyncId() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this.asyncResource.triggerAsyncId(); + } + + /** + * @type {EventEmitterReferencingAsyncResource} + */ + get asyncResource() { + if (this[kAsyncResource] === undefined) + throw new ERR_INVALID_THIS('EventEmitterAsyncResource'); + return this[kAsyncResource]; + } + } + EventEmitter.EventEmitterAsyncResource = EventEmitterAsyncResource; +} + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +ObjectDefineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, 'listener'); +} + +ObjectDefineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) { + throw new ERR_OUT_OF_RANGE('defaultMaxListeners', + 'a non-negative number', + arg); + } + defaultMaxListeners = arg; + } +}); + +ObjectDefineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + } +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +EventEmitter.setMaxListeners = + function(n = defaultMaxListeners, ...eventTargets) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (isEventTarget(target)) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === 'function') { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + 'eventTargets', + ['EventEmitter', 'EventTarget'], + target); + } + } + } + }; + +// If you're updating this function definition, please also update any +// re-definitions, such as the one in the Domain module (lib/domain.js). +EventEmitter.init = function(opts) { + + if (this._events === undefined || + this._events === ObjectGetPrototypeOf(this)._events) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + + if (oc(opts, 'captureRejections')) { + validateBoolean(opts.captureRejections, 'options.captureRejections'); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === 'function') { + then.call(promise, undefined, function(err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit('error', err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === 'function') { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catchable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit('error', err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) { + throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = (type === 'error'); + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) + this.emit(kErrorMonitor, ...args); + doError = (doError && events.error === undefined); + } else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) + er = args[0]; + if (er instanceof Error) { + try { + const capture = {}; + ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit); + } catch (e) {} + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch (e) { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) + return false; + + if (typeof handler === 'function') { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = ObjectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit('newListener', type, + nc(listener.listener, listener)); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error('Possible EventEmitter memory leak detected. ' + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + 'emitter.setMaxListeners() to increase limit'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) + return this.listener.call(this.target); + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + checkListener(listener); + + const events = this._events; + if (events === undefined) + return this; + + const list = events[type]; + if (list === undefined) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else { + spliceOne(list, position); + } + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener !== undefined) + this.emit('removeListener', type, listener); + } + + return this; + }; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + const events = this._events; + if (events === undefined) + return this; + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = ObjectCreate(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) + this._events = ObjectCreate(null); + else + delete events[type]; + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of ReflectOwnKeys(events)) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = ObjectCreate(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) + return []; + + const evlistener = events[type]; + if (evlistener === undefined) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? + unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } + return emitter.listenerCount(type); +}; + +EventEmitter.prototype.listenerCount = listenerCount; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: return [arr[0], arr[1]]; + case 3: return [arr[0], arr[1], arr[2]]; + case 4: return [arr[0], arr[1], arr[2], arr[3]]; + case 5: return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return ArrayPrototypeSlice(arr); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === 'function') + ret[i] = orig; + } + return ret; +} diff --git a/dist/save/setup-node-sandbox.js b/dist/save/setup-node-sandbox.js new file mode 100644 index 0000000..05f38ef --- /dev/null +++ b/dist/save/setup-node-sandbox.js @@ -0,0 +1,469 @@ +/* global host, data, VMError */ + +'use strict'; + +const LocalError = Error; +const LocalTypeError = TypeError; +const LocalWeakMap = WeakMap; + +const { + apply: localReflectApply, + defineProperty: localReflectDefineProperty +} = Reflect; + +const { + set: localWeakMapSet, + get: localWeakMapGet +} = LocalWeakMap.prototype; + +const { + isArray: localArrayIsArray +} = Array; + +function uncurryThis(func) { + return (thiz, ...args) => localReflectApply(func, thiz, args); +} + +const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); +const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); +const localArrayPrototypePush = uncurryThis(Array.prototype.push); +const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); +const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); +const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); +const localStringPrototypeSlice = uncurryThis(String.prototype.slice); +const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); + +const { + argv: optionArgv, + env: optionEnv, + console: optionConsole, + vm, + resolver, + extensions +} = data; + +function ensureSandboxArray(a) { + return localArrayPrototypeSlice(a); +} + +const globalPaths = ensureSandboxArray(resolver.globalPaths); + +class Module { + + constructor(id, path, parent) { + this.id = id; + this.filename = id; + this.path = path; + this.parent = parent; + this.loaded = false; + this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; + this.children = []; + this.exports = {}; + } + + _updateChildren(child, isNew) { + const children = this.children; + if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { + localArrayPrototypePush(children, child); + } + } + + require(id) { + return requireImpl(this, id, false); + } + +} + +const originalRequire = Module.prototype.require; +const cacheBuiltins = {__proto__: null}; + +function requireImpl(mod, id, direct) { + if (direct && mod.require !== originalRequire) { + return mod.require(id); + } + const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); + if (localStringPrototypeStartsWith(filename, 'node:')) { + id = localStringPrototypeSlice(filename, 5); + let nmod = cacheBuiltins[id]; + if (!nmod) { + nmod = resolver.loadBuiltinModule(vm, id); + if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); + cacheBuiltins[id] = nmod; + } + return nmod; + } + + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + mod._updateChildren(cachedModule, false); + return cachedModule.exports; + } + + let nmod = cacheBuiltins[id]; + if (nmod) return nmod; + nmod = resolver.loadBuiltinModule(vm, id); + if (nmod) { + cacheBuiltins[id] = nmod; + return nmod; + } + + const path = resolver.fs.dirname(filename); + const module = new Module(filename, path, mod); + resolver.registerModule(module, filename, path, mod, direct); + mod._updateChildren(module, true); + try { + Module._cache[filename] = module; + const handler = findBestExtensionHandler(filename); + handler(module, filename); + module.loaded = true; + } catch (e) { + delete Module._cache[filename]; + const children = mod.children; + if (localArrayIsArray(children)) { + const index = localArrayPrototypeIndexOf(children, module); + if (index !== -1) { + localArrayPrototypeSplice(children, index, 1); + } + } + throw e; + } + + return module.exports; +} + +Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); +Module.globalPaths = globalPaths; +Module._extensions = {__proto__: null}; +Module._cache = {__proto__: null}; + +{ + const keys = Object.getOwnPropertyNames(extensions); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const handler = extensions[key]; + Module._extensions[key] = (mod, filename) => handler(mod, filename); + } +} + +function findBestExtensionHandler(filename) { + const name = resolver.fs.basename(filename); + for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { + const ext = localStringPrototypeSlice(name, i); + const handler = Module._extensions[ext]; + if (handler) return handler; + } + const js = Module._extensions['.js']; + if (js) return js; + const keys = Object.getOwnPropertyNames(Module._extensions); + if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); + return Module._extensions[keys[0]]; +} + +function createRequireForModule(mod) { + // eslint-disable-next-line no-shadow + function require(id) { + return requireImpl(mod, id, true); + } + function resolve(id, options) { + return resolver.resolve(mod, id, options, Module._extensions, true); + } + require.resolve = resolve; + function paths(id) { + return ensureSandboxArray(resolver.lookupPaths(mod, id)); + } + resolve.paths = paths; + + require.extensions = Module._extensions; + + require.cache = Module._cache; + + return require; +} + +/** + * Prepare sandbox. + */ + +const TIMERS = new LocalWeakMap(); + +class Timeout { +} + +class Interval { +} + +class Immediate { +} + +function clearTimer(timer) { + const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); + if (obj) { + obj.clear(obj.value); + } +} + +// This is a function and not an arrow function, since the original is also a function +// eslint-disable-next-line no-shadow +global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Timeout(callback, args); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setTimeout(cb, delay); + + const ref = { + __proto__: null, + clear: host.clearTimeout, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Interval(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setInterval(cb, interval); + + const ref = { + __proto__: null, + clear: host.clearInterval, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); + const obj = new Immediate(); + const cb = () => { + localReflectApply(callback, null, args); + }; + const tmr = host.setImmediate(cb); + + const ref = { + __proto__: null, + clear: host.clearImmediate, + value: tmr + }; + + localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); + return obj; +}; + +// eslint-disable-next-line no-shadow +global.clearTimeout = function clearTimeout(timeout) { + clearTimer(timeout); +}; + +// eslint-disable-next-line no-shadow +global.clearInterval = function clearInterval(interval) { + clearTimer(interval); +}; + +// eslint-disable-next-line no-shadow +global.clearImmediate = function clearImmediate(immediate) { + clearTimer(immediate); +}; + +const localProcess = host.process; + +function vmEmitArgs(event, args) { + const allargs = [event]; + for (let i = 0; i < args.length; i++) { + if (!localReflectDefineProperty(allargs, i + 1, { + __proto__: null, + value: args[i], + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + } + return localReflectApply(vm.emit, vm, allargs); +} + +const LISTENERS = new LocalWeakMap(); +const LISTENER_HANDLER = new LocalWeakMap(); + +/** + * + * @param {*} name + * @param {*} handler + * @this process + * @return {this} + */ +function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (!cb) { + cb = () => { + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); + } + + localProcess.on(name, cb); + + return this; +} + +/** + * + * @this process + * @return {this} + */ +// eslint-disable-next-line no-shadow +function process() { + return this; +} + +const baseUptime = localProcess.uptime(); + +// FIXME wrong class structure +global.process = { + __proto__: process.prototype, + argv: optionArgv !== undefined ? optionArgv : [], + title: localProcess.title, + version: localProcess.version, + versions: localProcess.versions, + arch: localProcess.arch, + platform: localProcess.platform, + env: optionEnv !== undefined ? optionEnv : {}, + pid: localProcess.pid, + features: localProcess.features, + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new LocalError('Callback must be a function.'); + } + + localProcess.nextTick(()=>{ + localReflectApply(callback, null, args); + }); + }, + hrtime: function hrtime(time) { + return localProcess.hrtime(time); + }, + uptime: function uptime() { + return localProcess.uptime() - baseUptime; + }, + cwd: function cwd() { + return localProcess.cwd(); + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new LocalError(`Access denied to listen for '${name}' event.`); + } + + let triggered = false; + const cb = () => { + if (triggered) return; + triggered = true; + localProcess.removeListener(name, cb); + handler(); + }; + localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); + + localProcess.on(name, cb); + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + const all = localProcess.listeners(name); + const filtered = []; + let j = 0; + for (let i = 0; i < all.length; i++) { + const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); + if (h) { + if (!localReflectDefineProperty(filtered, j, { + __proto__: null, + value: h, + writable: true, + enumerable: true, + configurable: true + })) throw new LocalError('Unexpected'); + j++; + } + } + return filtered; + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); + if (cb) localProcess.removeListener(name, cb); + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new LocalError('Access denied to set umask.'); + } + + return localProcess.umask(); + } +}; + +if (optionConsole === 'inherit') { + global.console = host.console; +} else if (optionConsole === 'redirect') { + global.console = { + debug(...args) { + vmEmitArgs('console.debug', args); + }, + log(...args) { + vmEmitArgs('console.log', args); + }, + info(...args) { + vmEmitArgs('console.info', args); + }, + warn(...args) { + vmEmitArgs('console.warn', args); + }, + error(...args) { + vmEmitArgs('console.error', args); + }, + dir(...args) { + vmEmitArgs('console.dir', args); + }, + time() {}, + timeEnd() {}, + trace(...args) { + vmEmitArgs('console.trace', args); + } + }; +} + +return { + __proto__: null, + Module, + jsonParse: JSON.parse, + createRequireForModule, + requireImpl +}; diff --git a/dist/save/setup-sandbox.js b/dist/save/setup-sandbox.js new file mode 100644 index 0000000..539ce04 --- /dev/null +++ b/dist/save/setup-sandbox.js @@ -0,0 +1,474 @@ +/* global host, bridge, data, context */ + +'use strict'; + +const { + Object: localObject, + Array: localArray, + Error: LocalError, + Reflect: localReflect, + Proxy: LocalProxy, + WeakMap: LocalWeakMap, + Function: localFunction, + Promise: localPromise, + eval: localEval +} = global; + +const { + freeze: localObjectFreeze +} = localObject; + +const { + getPrototypeOf: localReflectGetPrototypeOf, + apply: localReflectApply, + deleteProperty: localReflectDeleteProperty, + has: localReflectHas, + defineProperty: localReflectDefineProperty, + setPrototypeOf: localReflectSetPrototypeOf, + getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor +} = localReflect; + +const { + isArray: localArrayIsArray +} = localArray; + +const { + ensureThis, + ReadOnlyHandler, + from, + fromWithFactory, + readonlyFactory, + connect, + addProtoMapping, + VMError, + ReadOnlyMockHandler +} = bridge; + +const { + allowAsync, + GeneratorFunction, + AsyncFunction, + AsyncGeneratorFunction +} = data; + +const { + get: localWeakMapGet, + set: localWeakMapSet +} = LocalWeakMap.prototype; + +function localUnexpected() { + return new VMError('Should not happen'); +} + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); + +Object.defineProperties(global, { + global: {value: global, writable: true, configurable: true, enumerable: true}, + globalThis: {value: global, writable: true, configurable: true}, + GLOBAL: {value: global, writable: true, configurable: true}, + root: {value: global, writable: true, configurable: true}, + Error: {value: LocalError} +}); + +if (!localReflectDefineProperty(global, 'VMError', { + __proto__: null, + value: VMError, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +// Fixes buffer unsafe allocation +/* eslint-disable no-use-before-define */ +class BufferHandler extends ReadOnlyHandler { + + apply(target, thiz, args) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + + construct(target, args, newTarget) { + if (args.length > 0 && typeof args[0] === 'number') { + return LocalBuffer.alloc(args[0]); + } + return localReflectApply(LocalBuffer.from, LocalBuffer, args); + } + +} +/* eslint-enable no-use-before-define */ + +const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); + + +if (!localReflectDefineProperty(global, 'Buffer', { + __proto__: null, + value: LocalBuffer, + writable: true, + enumerable: false, + configurable: true +})) throw localUnexpected(); + +addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafe(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafe, host.Buffer.allocUnsafe); + +/** + * + * @param {*} size Size of new buffer + * @this LocalBuffer + * @return {LocalBuffer} + */ +function allocUnsafeSlow(size) { + return LocalBuffer.alloc(size); +} + +connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); + +/** + * Replacement for Buffer inspect + * + * @param {*} recurseTimes + * @param {*} ctx + * @this LocalBuffer + * @return {string} + */ +function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +} + +connect(inspect, host.Buffer.prototype.inspect); + +connect(localFunction.prototype.bind, host.Function.prototype.bind); + +connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); +connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); +connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); +connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); + +/* + * PrepareStackTrace sanitization + */ + +const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); + +let currentPrepareStackTrace = LocalError.prepareStackTrace; +const wrappedPrepareStackTrace = new LocalWeakMap(); +if (typeof currentPrepareStackTrace === 'function') { + wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); +} + +let OriginalCallSite; +LocalError.prepareStackTrace = (e, sst) => { + OriginalCallSite = sst[0].constructor; +}; +new LocalError().stack; +if (typeof OriginalCallSite === 'function') { + LocalError.prepareStackTrace = undefined; + + function makeCallSiteGetters(list) { + const callSiteGetters = []; + for (let i=0; i { + return localReflectApply(func, thiz, []); + } + }; + } + return callSiteGetters; + } + + function applyCallSiteGetters(thiz, callSite, getters) { + for (let i=0; i { + const sandboxSst = ensureThis(sst); + if (localArrayIsArray(sst)) { + if (sst === sandboxSst) { + for (let i=0; i < sst.length; i++) { + const cs = sst[i]; + if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { + sst[i] = new CallSite(cs); + } + } + } else { + sst = []; + for (let i=0; i < sandboxSst.length; i++) { + const cs = sandboxSst[i]; + localReflectDefineProperty(sst, i, { + __proto__: null, + value: new CallSite(cs), + enumerable: true, + configurable: true, + writable: true + }); + } + } + } else { + sst = sandboxSst; + } + return value(error, sst); + }; + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); + localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); + currentPrepareStackTrace = newWrapped; + } + })) throw localUnexpected(); +} else if (oldPrepareStackTraceDesc) { + localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); +} else { + localReflectDeleteProperty(LocalError, 'prepareStackTrace'); +} + +/* + * Exception sanitization + */ + +const withProxy = localObjectFreeze({ + __proto__: null, + has(target, key) { + if (key === host.INTERNAL_STATE_NAME) return false; + return localReflectHas(target, key); + } +}); + +const interanState = localObjectFreeze({ + __proto__: null, + wrapWith(x) { + if (x === null || x === undefined) return x; + return new LocalProxy(localObject(x), withProxy); + }, + handleException: ensureThis, + import(what) { + throw new VMError('Dynamic Import not supported'); + } +}); + +if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { + __proto__: null, + configurable: false, + enumerable: false, + writable: false, + value: interanState +})) throw localUnexpected(); + +/* + * Eval sanitization + */ + +function throwAsync() { + return new VMError('Async not available'); +} + +function makeFunction(inputArgs, isAsync, isGenerator) { + const lastArgs = inputArgs.length - 1; + let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; + let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; + for (let i = 1; i < lastArgs; i++) { + args += `,${inputArgs[i]}`; + } + try { + code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); +} + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + return makeFunction(args, this.isAsync, this.isGenerator); + }, + construct(target, args, newTarget) { + return makeFunction(args, this.isAsync, this.isGenerator); + } +}; + +const EvalHandler = { + __proto__: null, + apply(target, thiz, args) { + if (args.length === 0) return undefined; + let code = `${args[0]}`; + try { + code = host.transformAndCheck(null, code, false, false, allowAsync); + } catch (e) { + throw bridge.from(e); + } + return localEval(code); + } +}; + +const AsyncErrorHandler = { + __proto__: null, + apply(target, thiz, args) { + throw throwAsync(); + }, + construct(target, args, newTarget) { + throw throwAsync(); + } +}; + +function makeCheckFunction(isAsync, isGenerator) { + if (isAsync && !allowAsync) return AsyncErrorHandler; + return { + __proto__: FunctionHandler, + isAsync, + isGenerator + }; +} + +function overrideWithProxy(obj, prop, value, handler) { + const proxy = new LocalProxy(value, handler); + if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); + return proxy; +} + +const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); +if (GeneratorFunction) { + if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); +} +if (AsyncFunction) { + if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); +} +if (AsyncGeneratorFunction) { + if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); + overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); +} + +global.Function = proxiedFunction; +global.eval = new LocalProxy(localEval, EvalHandler); + +/* + * Promise sanitization + */ + +if (localPromise && !allowAsync) { + + const PromisePrototype = localPromise.prototype; + + overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); + // This seems not to work, and will produce + // UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]. + // This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object. + // Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (PromisePrototype.finally) { + overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); + // Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} + +function readonly(other, mock) { + // Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe) + if (!mock) return fromWithFactory(readonlyFactory, other); + const tmock = from(mock); + return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); +} + +return { + __proto__: null, + readonly, + global +};