1010 lines
28 KiB
JavaScript
1010 lines
28 KiB
JavaScript
'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;
|