/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.types.builtins;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.ScriptRuntime.PrepareForTailCall;
import static com.github.anba.es6draft.runtime.types.Null.NULL;
import static com.github.anba.es6draft.runtime.types.PropertyDescriptor.CompletePropertyDescriptor;
import static com.github.anba.es6draft.runtime.types.PropertyDescriptor.FromPropertyDescriptor;
import static com.github.anba.es6draft.runtime.types.PropertyDescriptor.ToPropertyDescriptor;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import static com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject.IsCompatiblePropertyDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.ScriptIterator;
import com.github.anba.es6draft.runtime.types.Callable;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Property;
import com.github.anba.es6draft.runtime.types.PropertyDescriptor;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Symbol;
import com.github.anba.es6draft.runtime.types.Type;
/**
* <h1>9 Ordinary and Exotic Objects Behaviours</h1>
* <ul>
* <li>9.5 Proxy Object Internal Methods and Internal Slots
* </ul>
*/
public class ProxyObject implements ScriptObject {
/** [[ProxyTarget]] */
private ScriptObject proxyTarget;
/** [[ProxyHandler]] */
private ScriptObject proxyHandler;
/**
* Constructs a new Proxy object.
*
* @param target
* the proxy target object
* @param handler
* the proxy handler object
*/
protected ProxyObject(ScriptObject target, ScriptObject handler) {
assert target != null && handler != null;
this.proxyTarget = target;
this.proxyHandler = handler;
}
/**
* Returns the proxy target object.
*
* @return the proxy target object
*/
protected final ScriptObject getProxyTarget() {
assert proxyTarget != null : "Proxy is revoked";
return proxyTarget;
}
/**
* Returns the proxy handler object.
*
* @return the proxy handler object
*/
protected final ScriptObject getProxyHandler() {
assert proxyHandler != null : "Proxy is revoked";
return proxyHandler;
}
/**
* Returns the proxy target object or throws a script exception of the proxy has been revoked.
*
* @param cx
* the execution context
* @return the proxy handler object
*/
public final ScriptObject getProxyTarget(ExecutionContext cx) {
if (isRevoked()) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
return getProxyTarget();
}
/**
* Returns the proxy handler object or throws a script exception of the proxy has been revoked.
*
* @param cx
* the execution context
* @return the proxy handler object
*/
public final ScriptObject getProxyHandler(ExecutionContext cx) {
if (isRevoked()) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
return getProxyHandler();
}
/**
* Returns {@code true} if the proxy has been revoked.
*
* @return {@code true} if the proxy has been revoked
*/
protected final boolean isRevoked() {
return proxyHandler == null;
}
/**
* Revoke this proxy, that means set both, [[ProxyTarget]] and [[ProxyHandler]], to {@code null} and by that prevent
* further operations on this proxy object.
*/
public final void revoke() {
assert !isRevoked() : "Proxy already revoked";
this.proxyHandler = null;
this.proxyTarget = null;
}
/**
* Repeatedly unwraps the proxy object and returns the underlying target object.
*
* @param cx
* the execution context
* @return the proxy target object
*/
public final ScriptObject unwrap(ExecutionContext cx) {
ScriptObject target;
for (ProxyObject proxy = this; (target = proxy.proxyTarget) instanceof ProxyObject;) {
proxy = (ProxyObject) target;
}
if (target == null) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
return target;
}
@Override
public String toString() {
return String.format("%s@%x {{%n\tTarget=%s%n\tHandler=%s%n}}", getClass().getSimpleName(),
System.identityHashCode(this), proxyTarget, proxyHandler);
}
private static class CallableProxyObject extends ProxyObject implements Callable {
/**
* Constructs a new Proxy object.
*
* @param target
* the proxy target object
* @param handler
* the proxy handler object
*/
public CallableProxyObject(ScriptObject target, ScriptObject handler) {
super(target, handler);
}
/**
* 9.5.13 [[Call]] (thisArgument, argumentsList)
*/
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(callerContext);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(callerContext, handler, "apply");
/* step 7 */
if (trap == null) {
return ((Callable) target).call(callerContext, thisValue, args);
}
/* step 8 */
ArrayObject argArray = CreateArrayFromList(callerContext, Arrays.asList(args));
/* step 9 */
return trap.call(callerContext, handler, target, thisValue, argArray);
}
/**
* 9.5.13 [[Call]] (thisArgument, argumentsList)
*/
@Override
public Object tailCall(ExecutionContext callerContext, Object thisValue, Object... args) throws Throwable {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(callerContext);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(callerContext, handler, "apply");
/* step 7 */
if (trap == null) {
return ((Callable) target).tailCall(callerContext, thisValue, args);
}
/* step 8 */
ArrayObject argArray = CreateArrayFromList(callerContext, Arrays.asList(args));
/* step 9 */
// NB: PrepareForTailCall is necessary to handle the case when trap is this proxy
// object, or a bound function instance of this proxy object.
return PrepareForTailCall(trap, handler, new Object[] { target, thisValue, argArray });
}
@Override
public Callable clone(ExecutionContext cx) {
throw newTypeError(cx, Messages.Key.FunctionNotCloneable);
}
@Override
public Realm getRealm(ExecutionContext cx) {
/* 7.3.22 GetFunctionRealm ( obj ) */
if (isRevoked()) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
return ((Callable) getProxyTarget()).getRealm(cx);
}
@Override
public String toSource(ExecutionContext cx) {
throw newTypeError(cx, Messages.Key.IncompatibleObject);
}
}
private static class ConstructorProxyObject extends CallableProxyObject implements Constructor {
/**
* Constructs a new Proxy object.
*
* @param target
* the proxy target object
* @param handler
* the proxy handler object
*/
public ConstructorProxyObject(ScriptObject target, ScriptObject handler) {
super(target, handler);
}
/**
* 9.5.14 [[Construct]] Internal Method
*/
@Override
public ScriptObject construct(ExecutionContext callerContext, Constructor newTarget, Object... args) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(callerContext);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(callerContext, handler, "construct");
/* step 7 */
if (trap == null) {
return ((Constructor) target).construct(callerContext, newTarget, args);
}
/* step 8 */
ArrayObject argArray = CreateArrayFromList(callerContext, Arrays.asList(args));
/* steps 9-10 */
Object newObj = trap.call(callerContext, handler, target, argArray, newTarget);
/* step 11 */
if (!Type.isObject(newObj)) {
throw newTypeError(callerContext, Messages.Key.ProxyNotObject);
}
/* step 12 */
return Type.objectValue(newObj);
}
}
/**
* 9.5.15 ProxyCreate(target, handler)
*
* @param cx
* the execution context
* @param target
* the proxy target object
* @param handler
* the proxy handler object
* @return the new proxy object
*/
public static ProxyObject ProxyCreate(ExecutionContext cx, Object target, Object handler) {
/* step 1 */
if (!Type.isObject(target)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject proxyTarget = Type.objectValue(target);
/* step 2 */
if (proxyTarget instanceof ProxyObject && ((ProxyObject) proxyTarget).isRevoked()) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
/* step 3 */
if (!Type.isObject(handler)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject proxyHandler = Type.objectValue(handler);
/* step 4 */
if (proxyHandler instanceof ProxyObject && ((ProxyObject) proxyHandler).isRevoked()) {
throw newTypeError(cx, Messages.Key.ProxyRevoked);
}
/* steps 8-9 */
ProxyObject proxy;
if (IsCallable(proxyTarget)) {
if (IsConstructor(proxyTarget)) {
proxy = new ConstructorProxyObject(proxyTarget, proxyHandler);
} else {
proxy = new CallableProxyObject(proxyTarget, proxyHandler);
}
} else {
proxy = new ProxyObject(proxyTarget, proxyHandler);
}
/* step 10 */
return proxy;
}
/**
* 9.5.1 [[GetPrototypeOf]] ( )
*/
@Override
public ScriptObject getPrototypeOf(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "getPrototypeOf");
/* step 7 */
if (trap == null) {
return target.getPrototypeOf(cx);
}
/* steps 8-9 */
Object handlerProto = trap.call(cx, handler, target);
/* step 10 */
if (!Type.isObjectOrNull(handlerProto)) {
throw newTypeError(cx, Messages.Key.NotObjectOrNull);
}
ScriptObject handlerProtoObj = Type.objectValueOrNull(handlerProto);
/* steps 11-12 */
boolean extensibleTarget = IsExtensible(cx, target);
/* step 13 */
if (extensibleTarget) {
return handlerProtoObj;
}
/* steps 14-15 */
ScriptObject targetProto = target.getPrototypeOf(cx);
/* step 16 */
if (handlerProtoObj != targetProto) {
throw newTypeError(cx, Messages.Key.ProxySamePrototype);
}
/* step 17 */
return handlerProtoObj;
}
/**
* 9.5.2 [[SetPrototypeOf]] (V)
*/
@Override
public boolean setPrototypeOf(ExecutionContext cx, ScriptObject prototype) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* step 6 */
Callable trap = GetMethod(cx, handler, "setPrototypeOf");
/* step 7 */
if (trap == null) {
return target.setPrototypeOf(cx, prototype);
}
/* step 8 */
Object prototypeValue = prototype != null ? prototype : NULL;
boolean trapResult = ToBoolean(trap.call(cx, handler, target, prototypeValue));
/* step 9 */
if (!trapResult) {
return false;
}
/* step 10 */
boolean extensibleTarget = IsExtensible(cx, target);
/* step 11 */
if (extensibleTarget) {
return true;
}
/* step 12 */
ScriptObject targetProto = target.getPrototypeOf(cx);
/* step 13 */
if (prototype != targetProto) {
throw newTypeError(cx, Messages.Key.ProxySamePrototype);
}
/* step 14 */
return true;
}
/**
* 9.5.3 [[IsExtensible]] ( )
*/
@Override
public boolean isExtensible(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "isExtensible");
/* step 7 */
if (trap == null) {
return target.isExtensible(cx);
}
/* steps 8-9 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target));
/* steps 10-11 */
boolean targetResult = target.isExtensible(cx);
/* step 12 */
if (trapResult != targetResult) {
throw newTypeError(cx, Messages.Key.ProxyExtensible);
}
/* step 13 */
return trapResult;
}
/**
* 9.5.4 [[PreventExtensions]] ( )
*/
@Override
public boolean preventExtensions(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "preventExtensions");
/* step 7 */
if (trap == null) {
return target.preventExtensions(cx);
}
/* steps 8-9 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target));
/* step 10 */
if (trapResult) {
boolean targetIsExtensible = target.isExtensible(cx);
if (targetIsExtensible) {
throw newTypeError(cx, Messages.Key.ProxyExtensible);
}
}
/* step 11 */
return trapResult;
}
/**
* 9.5.5 [[GetOwnProperty]] (P)
*/
@Override
public Property getOwnProperty(ExecutionContext cx, long propertyKey) {
return getOwnProperty(cx, ToString(propertyKey));
}
/**
* 9.5.5 [[GetOwnProperty]] (P)
*/
@Override
public Property getOwnProperty(ExecutionContext cx, String propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "getOwnPropertyDescriptor");
/* step 8 */
if (trap == null) {
return target.getOwnProperty(cx, propertyKey);
}
/* steps 9-10 */
Object trapResultObj = trap.call(cx, handler, target, propertyKey);
/* step 11 */
if (!(Type.isObject(trapResultObj) || Type.isUndefined(trapResultObj))) {
throw newTypeError(cx, Messages.Key.ProxyNotObjectOrUndefined);
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-23 */
return validateGetOwnProperty(cx, target, trapResultObj, targetDesc);
}
/**
* 9.5.5 [[GetOwnProperty]] (P)
*/
@Override
public Property getOwnProperty(ExecutionContext cx, Symbol propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "getOwnPropertyDescriptor");
/* step 8 */
if (trap == null) {
return target.getOwnProperty(cx, propertyKey);
}
/* steps 9-10 */
Object trapResultObj = trap.call(cx, handler, target, propertyKey);
/* step 11 */
if (!(Type.isObject(trapResultObj) || Type.isUndefined(trapResultObj))) {
throw newTypeError(cx, Messages.Key.ProxyNotObjectOrUndefined);
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-23 */
return validateGetOwnProperty(cx, target, trapResultObj, targetDesc);
}
private Property validateGetOwnProperty(ExecutionContext cx, ScriptObject target, Object trapResultObj,
Property targetDesc) {
/* step 14 */
if (Type.isUndefined(trapResultObj)) {
if (targetDesc == null) {
return null;
}
if (!targetDesc.isConfigurable()) {
throw newTypeError(cx, Messages.Key.ProxyNotConfigurable);
}
boolean extensibleTarget = IsExtensible(cx, target);
if (!extensibleTarget) {
throw newTypeError(cx, Messages.Key.ProxyNotExtensible);
}
return null;
}
if (targetDesc != null) {
// need copy because of possible side-effects in IsExtensible()
targetDesc = targetDesc.clone();
}
/* steps 15-16 */
boolean extensibleTarget = IsExtensible(cx, target);
/* steps 17-18 */
PropertyDescriptor resultDesc = ToPropertyDescriptor(cx, trapResultObj);
/* step 19 */
CompletePropertyDescriptor(resultDesc);
/* step 20 */
boolean valid = IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc);
/* step 21 */
if (!valid) {
throw newTypeError(cx, Messages.Key.ProxyIncompatibleDescriptor);
}
/* step 22 */
if (!resultDesc.isConfigurable()) {
if (targetDesc == null || targetDesc.isConfigurable()) {
throw newTypeError(cx, Messages.Key.ProxyAbsentOrConfigurable);
}
}
/* step 23 */
return resultDesc.toProperty();
}
/**
* 9.5.6 [[DefineOwnProperty]] (P, Desc)
*/
@Override
public boolean defineOwnProperty(ExecutionContext cx, long propertyKey, PropertyDescriptor desc) {
return defineOwnProperty(cx, ToString(propertyKey), desc);
}
/**
* 9.5.6 [[DefineOwnProperty]] (P, Desc)
*/
@Override
public boolean defineOwnProperty(ExecutionContext cx, String propertyKey, PropertyDescriptor desc) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "defineProperty");
/* step 8 */
if (trap == null) {
return target.defineOwnProperty(cx, propertyKey, desc);
}
/* step 9 */
Object descObj = FromPropertyDescriptor(cx, desc);
/* steps 10-11 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey, descObj));
/* step 12 */
if (!trapResult) {
return false;
}
/* steps 13-14 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 15-21 */
return validateDefineOwnProperty(cx, desc, target, targetDesc);
}
/**
* 9.5.6 [[DefineOwnProperty]] (P, Desc)
*/
@Override
public boolean defineOwnProperty(ExecutionContext cx, Symbol propertyKey, PropertyDescriptor desc) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "defineProperty");
/* step 8 */
if (trap == null) {
return target.defineOwnProperty(cx, propertyKey, desc);
}
/* step 9 */
Object descObj = FromPropertyDescriptor(cx, desc);
/* steps 10-11 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey, descObj));
/* step 12 */
if (!trapResult) {
return false;
}
/* steps 13-14 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 15-21 */
return validateDefineOwnProperty(cx, desc, target, targetDesc);
}
private boolean validateDefineOwnProperty(ExecutionContext cx, PropertyDescriptor desc, ScriptObject target,
Property targetDesc) {
if (targetDesc != null) {
// need copy because of possible side-effects in IsExtensible()
targetDesc = targetDesc.clone();
}
/* steps 15-16 */
boolean extensibleTarget = IsExtensible(cx, target);
/* steps 17-18 */
boolean settingConfigFalse = desc.hasConfigurable() && !desc.isConfigurable();
/* steps 19-20 */
if (targetDesc == null) {
if (!extensibleTarget) {
throw newTypeError(cx, Messages.Key.ProxyAbsentNotExtensible);
}
if (settingConfigFalse) {
throw newTypeError(cx, Messages.Key.ProxyAbsentOrConfigurable);
}
} else {
if (!IsCompatiblePropertyDescriptor(extensibleTarget, desc, targetDesc)) {
throw newTypeError(cx, Messages.Key.ProxyIncompatibleDescriptor);
}
if (settingConfigFalse && targetDesc.isConfigurable()) {
throw newTypeError(cx, Messages.Key.ProxyAbsentOrConfigurable);
}
}
/* step 21 */
return true;
}
/**
* 9.5.7 [[HasProperty]] (P)
*/
@Override
public boolean hasProperty(ExecutionContext cx, long propertyKey) {
return hasProperty(cx, ToString(propertyKey));
}
/**
* 9.5.7 [[HasProperty]] (P)
*/
@Override
public boolean hasProperty(ExecutionContext cx, String propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "has");
/* step 8 */
if (trap == null) {
return target.hasProperty(cx, propertyKey);
}
/* steps 9-10 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey));
/* step 11 */
if (!trapResult) {
/* steps 11.a-11.b */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* step 11.c */
validateHasProperty(cx, target, targetDesc);
}
/* step 12 */
return trapResult;
}
/**
* 9.5.7 [[HasProperty]] (P)
*/
@Override
public boolean hasProperty(ExecutionContext cx, Symbol propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "has");
/* step 8 */
if (trap == null) {
return target.hasProperty(cx, propertyKey);
}
/* steps 9-10 */
boolean booleanTrapResult = ToBoolean(trap.call(cx, handler, target, propertyKey));
/* step 11 */
if (!booleanTrapResult) {
/* steps 11.a-11.b */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* step 11.c */
validateHasProperty(cx, target, targetDesc);
}
/* step 12 */
return booleanTrapResult;
}
private void validateHasProperty(ExecutionContext cx, ScriptObject target, Property targetDesc) {
/* step 11.c */
if (targetDesc != null) {
if (!targetDesc.isConfigurable()) {
throw newTypeError(cx, Messages.Key.ProxyNotConfigurable);
}
boolean extensibleTarget = IsExtensible(cx, target);
if (!extensibleTarget) {
throw newTypeError(cx, Messages.Key.ProxyNotExtensible);
}
}
}
/**
* 9.5.8 [[Get]] (P, Receiver)
*/
@Override
public Object get(ExecutionContext cx, long propertyKey, Object receiver) {
return get(cx, ToString(propertyKey), receiver);
}
/**
* 9.5.8 [[Get]] (P, Receiver)
*/
@Override
public Object get(ExecutionContext cx, String propertyKey, Object receiver) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "get");
/* step 8 */
if (trap == null) {
return target.get(cx, propertyKey, receiver);
}
/* steps 9-10 */
Object trapResult = trap.call(cx, handler, target, propertyKey, receiver);
/* steps 11-12 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 13-14 */
return validateGet(cx, trapResult, targetDesc);
}
/**
* 9.5.8 [[Get]] (P, Receiver)
*/
@Override
public Object get(ExecutionContext cx, Symbol propertyKey, Object receiver) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "get");
/* step 8 */
if (trap == null) {
return target.get(cx, propertyKey, receiver);
}
/* steps 9-10 */
Object trapResult = trap.call(cx, handler, target, propertyKey, receiver);
/* steps 11-12 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 13-14 */
return validateGet(cx, trapResult, targetDesc);
}
private Object validateGet(ExecutionContext cx, Object trapResult, Property targetDesc) {
/* step 13 */
if (targetDesc != null && !targetDesc.isConfigurable()) {
if (targetDesc.isDataDescriptor()) {
if (!targetDesc.isWritable() && !SameValue(trapResult, targetDesc.getValue())) {
throw newTypeError(cx, Messages.Key.ProxySameValue);
}
} else {
if (targetDesc.getGetter() == null && trapResult != UNDEFINED) {
throw newTypeError(cx, Messages.Key.ProxyNoGetter);
}
}
}
/* step 14 */
return trapResult;
}
/**
* 9.5.9 [[Set]] ( P, V, Receiver)
*/
@Override
public boolean set(ExecutionContext cx, long propertyKey, Object value, Object receiver) {
return set(cx, ToString(propertyKey), value, receiver);
}
/**
* 9.5.9 [[Set]] ( P, V, Receiver)
*/
@Override
public boolean set(ExecutionContext cx, String propertyKey, Object value, Object receiver) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "set");
/* step 8 */
if (trap == null) {
return target.set(cx, propertyKey, value, receiver);
}
/* steps 9-10 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey, value, receiver));
/* step 11 */
if (!trapResult) {
return false;
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-15 */
return validateSet(cx, value, targetDesc);
}
/**
* 9.5.9 [[Set]] ( P, V, Receiver)
*/
@Override
public boolean set(ExecutionContext cx, Symbol propertyKey, Object value, Object receiver) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "set");
/* step 8 */
if (trap == null) {
return target.set(cx, propertyKey, value, receiver);
}
/* steps 9-10 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey, value, receiver));
/* step 11 */
if (!trapResult) {
return false;
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-15 */
return validateSet(cx, value, targetDesc);
}
private boolean validateSet(ExecutionContext cx, Object value, Property targetDesc) {
/* step 14 */
if (targetDesc != null && !targetDesc.isConfigurable()) {
if (targetDesc.isDataDescriptor()) {
if (!targetDesc.isWritable() && !SameValue(value, targetDesc.getValue())) {
throw newTypeError(cx, Messages.Key.ProxySameValue);
}
} else {
if (targetDesc.getSetter() == null) {
throw newTypeError(cx, Messages.Key.ProxyNoSetter);
}
}
}
/* step 15 */
return true;
}
/**
* 9.5.10 [[Delete]] (P)
*/
@Override
public boolean delete(ExecutionContext cx, long propertyKey) {
return delete(cx, ToString(propertyKey));
}
/**
* 9.5.10 [[Delete]] (P)
*/
@Override
public boolean delete(ExecutionContext cx, String propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "deleteProperty");
/* step 8 */
if (trap == null) {
return target.delete(cx, propertyKey);
}
/* steps 9-10 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey));
/* step 11 */
if (!trapResult) {
return false;
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-16 */
return validateDelete(cx, targetDesc);
}
/**
* 9.5.10 [[Delete]] (P)
*/
@Override
public boolean delete(ExecutionContext cx, Symbol propertyKey) {
/* step 1 (implicit) */
/* steps 2-4 */
ScriptObject handler = getProxyHandler(cx);
/* step 5 */
ScriptObject target = getProxyTarget();
/* steps 6-7 */
Callable trap = GetMethod(cx, handler, "deleteProperty");
/* step 8 */
if (trap == null) {
return target.delete(cx, propertyKey);
}
/* steps 9-10 */
boolean trapResult = ToBoolean(trap.call(cx, handler, target, propertyKey));
/* step 11 */
if (!trapResult) {
return false;
}
/* steps 12-13 */
Property targetDesc = target.getOwnProperty(cx, propertyKey);
/* steps 14-16 */
return validateDelete(cx, targetDesc);
}
private boolean validateDelete(ExecutionContext cx, Property targetDesc) {
/* step 14 */
if (targetDesc == null) {
return true;
}
/* step 15 */
if (!targetDesc.isConfigurable()) {
throw newTypeError(cx, Messages.Key.ProxyDeleteNonConfigurable);
}
/* step 16 */
return true;
}
/**
* 9.5.11 [[Enumerate]] ()
*/
@Override
public ScriptObject enumerate(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "enumerate");
/* step 7 */
if (trap == null) {
return target.enumerate(cx);
}
/* steps 8-9 */
Object trapResult = trap.call(cx, handler, target);
/* step 10 */
if (!Type.isObject(trapResult)) {
throw newTypeError(cx, Messages.Key.ProxyNotObject);
}
/* step 11 */
return Type.objectValue(trapResult);
}
/**
* 9.5.11 [[Enumerate]] ()
*/
@Override
public ScriptIterator<?> enumerateKeys(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "enumerate");
/* step 7 */
if (trap == null) {
return target.enumerateKeys(cx);
}
/* steps 8-9 */
Object trapResult = trap.call(cx, handler, target);
/* step 10 */
if (!Type.isObject(trapResult)) {
throw newTypeError(cx, Messages.Key.ProxyNotObject);
}
/* step 11 */
return ToScriptIterator(cx, Type.objectValue(trapResult));
}
/**
* 9.5.12 [[OwnPropertyKeys]] ()
*/
@Override
public Iterator<String> ownEnumerablePropertyKeys(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "ownKeys");
/* step 7 */
if (trap == null) {
return target.ownEnumerablePropertyKeys(cx);
}
/* step 8 */
Object trapResultArray = trap.call(cx, handler, target);
/* steps 9-25 */
List<?> ownKeys = validateOwnPropertyKeys(cx, target, trapResultArray);
List<String> enumerableKeys = new ArrayList<>();
for (Object key : ownKeys) {
if (key instanceof String) {
enumerableKeys.add((String) key);
}
}
return enumerableKeys.iterator();
}
/**
* 9.5.12 [[OwnPropertyKeys]] ()
*/
@Override
public List<?> ownPropertyKeys(ExecutionContext cx) {
/* steps 1-3 */
ScriptObject handler = getProxyHandler(cx);
/* step 4 */
ScriptObject target = getProxyTarget();
/* steps 5-6 */
Callable trap = GetMethod(cx, handler, "ownKeys");
/* step 7 */
if (trap == null) {
return target.ownPropertyKeys(cx);
}
/* step 8 */
Object trapResultArray = trap.call(cx, handler, target);
/* steps 9-25 */
return validateOwnPropertyKeys(cx, target, trapResultArray);
}
private List<Object> validateOwnPropertyKeys(ExecutionContext cx, ScriptObject target, Object trapResultArray) {
/* steps 9-10 */
List<Object> trapResult = CreateListFromArrayLike(cx, trapResultArray, EnumSet.of(Type.String, Type.Symbol));
/* steps 11-12 */
boolean extensibleTarget = target.isExtensible(cx);
/* steps 13-15 */
List<?> targetKeys = target.ownPropertyKeys(cx);
/* step 16 */
ArrayList<Object> targetConfigurableKeys = new ArrayList<>();
/* step 17 */
ArrayList<Object> targetNonConfigurableKeys = new ArrayList<>();
/* step 18 */
for (Object key : targetKeys) {
Property desc = target.getOwnProperty(cx, key);
if (desc != null && !desc.isConfigurable()) {
targetNonConfigurableKeys.add(key);
} else {
targetConfigurableKeys.add(key);
}
}
/* step 19 */
if (extensibleTarget && targetNonConfigurableKeys.isEmpty()) {
return trapResult;
}
/* step 20 */
final Integer zero = Integer.valueOf(0);
HashMap<Object, Integer> uncheckedResultKeys = new HashMap<>();
for (Object key : trapResult) {
Integer c = uncheckedResultKeys.put(key, zero);
if (c != null) {
uncheckedResultKeys.put(key, c + 1);
}
}
/* step 21 */
for (Object key : targetNonConfigurableKeys) {
Integer c = uncheckedResultKeys.remove(key);
if (c == null) {
throw newTypeError(cx, Messages.Key.ProxyNotConfigurable);
}
if (c > 0) {
uncheckedResultKeys.put(key, c - 1);
}
}
/* step 22 */
if (extensibleTarget) {
return trapResult;
}
/* step 23 */
for (Object key : targetConfigurableKeys) {
Integer c = uncheckedResultKeys.remove(key);
if (c == null) {
throw newTypeError(cx, Messages.Key.ProxyNotExtensible);
}
if (c > 0) {
uncheckedResultKeys.put(key, c - 1);
}
}
/* step 24 */
if (!uncheckedResultKeys.isEmpty()) {
throw newTypeError(cx, Messages.Key.ProxyAbsentNotExtensible);
}
/* step 25 */
return trapResult;
}
/**
* 9.5.12 [[OwnPropertyKeys]] ()
*/
@Override
public Iterator<?> ownKeys(ExecutionContext cx) {
return ownPropertyKeys(cx).iterator();
}
}