diff --git a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java index 42cd2a20b8..ca0012084e 100644 --- a/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java +++ b/rhino/src/main/java/org/mozilla/javascript/LambdaConstructor.java @@ -127,9 +127,17 @@ protected Constructable getTargetConstructor() { return targetConstructor; } + public boolean isCallable() { + return (flags & CONSTRUCTOR_FUNCTION) != 0; + } + + public boolean isConstructable() { + return (flags & CONSTRUCTOR_NEW) != 0; + } + @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - if ((flags & CONSTRUCTOR_FUNCTION) == 0) { + if (!isCallable()) { throw ScriptRuntime.typeErrorById("msg.constructor.no.function", getFunctionName()); } scope = getDeclarationScope(); @@ -141,7 +149,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar @Override public Scriptable construct(Context cx, Scriptable scope, Object[] args) { - if ((flags & CONSTRUCTOR_NEW) == 0) { + if (!isConstructable()) { throw ScriptRuntime.typeErrorById("msg.no.new", getFunctionName()); } return fireConstructor(cx, getDeclarationScope(), args); diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java b/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java index 84c67b6299..0d409266e9 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeProxy.java @@ -15,6 +15,7 @@ * This class implements the Proxy object. * * @author Ronald Brill + * @author Lai Quang Duong */ class NativeProxy extends ScriptableObject { private static final long serialVersionUID = 6676871870513494844L; @@ -569,7 +570,7 @@ public void put(String name, Scriptable start, Object value) { Function trap = getTrap(TRAP_SET); if (trap != null) { boolean booleanTrapResult = - ScriptRuntime.toBoolean(callTrap(trap, new Object[] {target, name, value})); + ScriptRuntime.toBoolean(callTrap(trap, new Object[] {target, name, value, this})); if (!booleanTrapResult) { return; // false } @@ -631,7 +632,7 @@ public void put(int index, Scriptable start, Object value) { ScriptRuntime.toBoolean( callTrap( trap, - new Object[] {target, ScriptRuntime.toString(index), value})); + new Object[] {target, ScriptRuntime.toString(index), value, this})); if (!booleanTrapResult) { return; // false } @@ -691,7 +692,7 @@ public void put(Symbol key, Scriptable start, Object value) { Function trap = getTrap(TRAP_SET); if (trap != null) { boolean booleanTrapResult = - ScriptRuntime.toBoolean(callTrap(trap, new Object[] {target, key, value})); + ScriptRuntime.toBoolean(callTrap(trap, new Object[] {target, key, value, this})); if (!booleanTrapResult) { return; // false } diff --git a/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java b/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java index 94dde4c832..9c3b1a93f2 100644 --- a/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java +++ b/rhino/src/main/java/org/mozilla/javascript/NativeReflect.java @@ -8,12 +8,14 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.mozilla.javascript.ScriptRuntime.StringIdOrIndex; /** * This class implements the Reflect object. * * @author Ronald Brill + * @author Lai Quang Duong */ final class NativeReflect extends ScriptableObject { private static final long serialVersionUID = 2920773905356325445L; @@ -143,6 +145,15 @@ private static Scriptable construct( if (result != null) { result.setPrototype((Scriptable) newTargetPrototype); + // LambdaConstructor could be non-callable (requires "new") or + // non-constructable (no "new"). Check its flag to decide. + if (ctorBaseFunction instanceof LambdaConstructor + && ((LambdaConstructor) ctorBaseFunction).isConstructable()) { + Scriptable newScriptable = ctorBaseFunction.construct(cx, scope, callArgs); + newScriptable.setPrototype((Scriptable) newTargetPrototype); + return newScriptable; + } + Object val = ctorBaseFunction.call(cx, scope, result, callArgs); if (val instanceof Scriptable) { return (Scriptable) val; @@ -204,23 +215,101 @@ private static Object deleteProperty( return false; } + /* + * https://tc39.es/ecma262/#sec-reflect.get + * 1. If target is not an Object, throw a TypeError exception. + * 2. Let key be ? ToPropertyKey(propertyKey). + * 3. If receiver is not present, then + * a. Set receiver to target. + * 4. Return ? target.[[Get]](key, receiver). + */ private static Object get(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - ScriptableObject target = checkTarget(args); - - if (args.length > 1) { - if (ScriptRuntime.isSymbol(args[1])) { - Object prop = ScriptableObject.getProperty(target, (Symbol) args[1]); - return prop == Scriptable.NOT_FOUND ? Undefined.SCRIPTABLE_UNDEFINED : prop; + final ScriptableObject target = checkTarget(args); + final Object propertyKey = args.length > 1 ? args[1] : Undefined.instance; + final Object receiver = args.length > 2 ? args[2] : target; + + // If target is a proxy, delegate to the proxy handler + if (target instanceof NativeProxy) { + final NativeProxy proxy = (NativeProxy) target; + final Function trap = proxy.getTrap("get"); + if (trap != null) { + final ScriptableObject proxyTarget = proxy.getTargetThrowIfRevoked(); + final Object[] trapArgs = {proxyTarget, propertyKey, receiver}; + final Object trapResult = proxy.callTrap(trap, trapArgs); + + // checks for non-configurable properties + // https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver steps 9 + final DescriptorInfo targetDesc = proxyTarget.getOwnPropertyDescriptor(cx, propertyKey); + if (targetDesc != null && targetDesc.isConfigurable(false)) { + if (targetDesc.isDataDescriptor() && targetDesc.isWritable(false)) { + if (!Objects.equals(trapResult, targetDesc.value)) { + throw ScriptRuntime.typeError( + "proxy must report the same value for the non-writable," + + " non-configurable property '\"" + propertyKey + "\"'"); + } + } + if (targetDesc.isAccessorDescriptor() + && (targetDesc.getter == null + || targetDesc.getter == Scriptable.NOT_FOUND + || Undefined.isUndefined(targetDesc.getter))) { + if (!Undefined.isUndefined(trapResult)) { + throw ScriptRuntime.typeError( + "proxy must report the same value for the non-writable," + + " non-configurable property '\"" + propertyKey + "\"'"); + } + } + } + return trapResult; } - if (args[1] instanceof Number) { - Object prop = ScriptableObject.getProperty(target, ScriptRuntime.toIndex(args[1])); - return prop == Scriptable.NOT_FOUND ? Undefined.SCRIPTABLE_UNDEFINED : prop; + } + + return internalGet(cx, target, propertyKey, receiver); + } + + /* + * https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver + * 1. Let desc be ? O.[[GetOwnProperty]](P). + * 2. If desc is undefined, then + * a. Let parent be ? O.[[GetPrototypeOf]](). + * b. If parent is null, return undefined. + * c. Return ? parent.[[Get]](P, Receiver). + * 3. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + * 4. Assert: IsAccessorDescriptor(desc) is true. + * 5. Let getter be desc.[[Get]]. + * 6. If getter is undefined, return undefined. + * 7. Return ? Call(getter, Receiver). + */ + private static Object internalGet( + Context cx, ScriptableObject target, Object propertyKey, Object receiver) { + final DescriptorInfo desc = target.getOwnPropertyDescriptor(cx, propertyKey); + if (desc == null) { + final Scriptable parent = target.getPrototype(); + if (parent == null) { + return Undefined.SCRIPTABLE_UNDEFINED; } + return internalGet(cx, ScriptableObject.ensureScriptableObject(parent), propertyKey, receiver); + } - Object prop = ScriptableObject.getProperty(target, ScriptRuntime.toString(args[1])); - return prop == Scriptable.NOT_FOUND ? Undefined.SCRIPTABLE_UNDEFINED : prop; + if (desc.isDataDescriptor()) { + return desc.value == Scriptable.NOT_FOUND + ? Undefined.SCRIPTABLE_UNDEFINED + : desc.value; } - return Undefined.SCRIPTABLE_UNDEFINED; + + final Object getter = desc.getter; + if (getter == null || getter == Scriptable.NOT_FOUND || Undefined.isUndefined(getter)) { + return Undefined.SCRIPTABLE_UNDEFINED; + } + + final Scriptable receiverForCall; + if (receiver == null || Undefined.isUndefined(receiver)) { + receiverForCall = cx.isStrictMode() + ? null + : ScriptableObject.getTopLevelScope(target); + } else { + receiverForCall = ScriptableObject.ensureScriptable(receiver); + } + return ((Function) getter).call(cx, target, receiverForCall, ScriptRuntime.emptyArgs); } private static Scriptable getOwnPropertyDescriptor( @@ -298,41 +387,165 @@ private static Object preventExtensions( return target.preventExtensions(); } + /* + * https://tc39.es/ecma262/#sec-reflect.set + * 1. If target is not an Object, throw a TypeError exception. + * 2. Let key be ? ToPropertyKey(propertyKey). + * 3. If receiver is not present, then + * a. Set receiver to target. + * 4. Return ? target.[[Set]](key, V, receiver). + */ private static Object set(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { - ScriptableObject target = checkTarget(args); - if (args.length < 2) { - return true; + final ScriptableObject target = checkTarget(args); + final Object propertyKey = args.length > 1 ? args[1] : Undefined.instance; + final Object value = args.length > 2 ? args[2] : Undefined.instance; + final Object receiver = args.length > 3 ? args[3] : target; + + // If target is a proxy, delegate to the proxy handler + if (target instanceof NativeProxy) { + final NativeProxy proxy = (NativeProxy) target; + final Function trap = proxy.getTrap("set"); + if (trap != null) { + final ScriptableObject proxyTarget = proxy.getTargetThrowIfRevoked(); + final Object[] trapArgs = {proxyTarget, propertyKey, value, receiver}; + final boolean booleanTrapResult = ScriptRuntime.toBoolean(proxy.callTrap(trap, trapArgs)); + if (!booleanTrapResult) { + return false; + } + + // checks for non-configurable properties + // https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver steps 10 + final DescriptorInfo targetDesc = proxyTarget.getOwnPropertyDescriptor(cx, propertyKey); + if (targetDesc != null && targetDesc.isConfigurable(false)) { + if (targetDesc.isDataDescriptor() && targetDesc.isWritable(false)) { + if (!Objects.equals(value, targetDesc.value)) { + throw ScriptRuntime.typeError( + "proxy can't successfully set a non-writable," + + " non-configurable property '\"" + propertyKey + "\"'"); + } + } + if (targetDesc.isAccessorDescriptor() + && (targetDesc.setter == null + || targetDesc.setter == Scriptable.NOT_FOUND + || Undefined.isUndefined(targetDesc.setter))) { + throw ScriptRuntime.typeError( + "proxy can't successfully set a non-writable," + + " non-configurable property '\"" + propertyKey + "\"'"); + } + } + return true; + } } - ScriptableObject receiver = - args.length > 3 ? ScriptableObject.ensureScriptableObject(args[3]) : target; - if (receiver != target) { - DescriptorInfo descriptor = target.getOwnPropertyDescriptor(cx, args[1]); - if (descriptor != null) { - Object setter = descriptor.setter; - if (setter != null && setter != NOT_FOUND) { - ((Function) setter).call(cx, scope, receiver, new Object[] {args[2]}); - return true; + return internalSet(cx, target, propertyKey, value, receiver); + } + + /* + * https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver + * 1. Let ownDesc be ? O.[[GetOwnProperty]](P). + * 2. If ownDesc is undefined, then + * a. Let parent be ? O.[[GetPrototypeOf]](). + * b. If parent is not null, then + * i. Return ? parent.[[Set]](P, V, Receiver). + * c. Else, + * i. Set ownDesc to the PropertyDescriptor + * { [[Value]]: undefined, [[Writable]]: true, + * [[Enumerable]]: true, [[Configurable]]: true }. + * 3. If IsDataDescriptor(ownDesc) is true, then + * a. If ownDesc.[[Writable]] is false, return false. + * b. If Receiver is not an Object, return false. + * c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). + * d. If existingDescriptor is not undefined, then + * i. If IsAccessorDescriptor(existingDescriptor) is true, return false. + * ii. If existingDescriptor.[[Writable]] is false, return false. + * iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. + * iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). + * e. Else, + * i. Assert: Receiver does not currently have a property P. + * ii. Return ? CreateDataProperty(Receiver, P, V). + * 4. Assert: IsAccessorDescriptor(ownDesc) is true. + * 5. Let setter be ownDesc.[[Set]]. + * 6. If setter is undefined, return false. + * 7. Perform ? Call(setter, Receiver, « V »). + * 8. Return true. + */ + private static boolean internalSet(Context cx, ScriptableObject target, Object propertyKey, + Object value, Object receiver) { + try { + DescriptorInfo ownDesc = target.getOwnPropertyDescriptor(cx, propertyKey); + if (ownDesc == null) { + final Scriptable parent = target.getPrototype(); + if (parent != null) { + return internalSet(cx, ScriptableObject.ensureScriptableObject(parent), propertyKey, value, receiver); + } + ownDesc = new DescriptorInfo(true, true, true, Undefined.instance); + } + + if (ownDesc.isDataDescriptor()) { + if (ownDesc.isWritable(false)) { + return false; + } + if (!ScriptRuntime.isObject(receiver)) { + return false; } - if (descriptor.isConfigurable(false)) { + final ScriptableObject receiverObj = ScriptableObject.ensureScriptableObject(receiver); + final DescriptorInfo existingDescriptor = receiverObj.getOwnPropertyDescriptor(cx, propertyKey); + if (existingDescriptor != null) { + if (existingDescriptor.isAccessorDescriptor()) { + return false; + } + if (existingDescriptor.isWritable(false)) { + return false; + } + } else if (!receiverObj.isExtensible()) { return false; } + + // If receiver is a proxy, set property directly on the proxy's target + // to avoid recursion (reflect <-> proxy) + final ScriptableObject realReceiverObj = receiverObj instanceof NativeProxy + ? ((NativeProxy) receiverObj).getTargetThrowIfRevoked() + : receiverObj; + + if (ScriptRuntime.isSymbol(propertyKey)) { + realReceiverObj.put((Symbol) propertyKey, realReceiverObj, value); + } else { + final StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(propertyKey); + if (s.stringId == null) { + realReceiverObj.put(s.index, realReceiverObj, value); + } else { + realReceiverObj.put(s.stringId, realReceiverObj, value); + } + } + + return true; } - } - if (ScriptRuntime.isSymbol(args[1])) { - receiver.put((Symbol) args[1], receiver, args[2]); - } else { - StringIdOrIndex s = ScriptRuntime.toStringIdOrIndex(args[1]); - if (s.stringId == null) { - receiver.put(s.index, receiver, args[2]); - } else { - receiver.put(s.stringId, receiver, args[2]); + if (ownDesc.isAccessorDescriptor()) { + final Object setter = ownDesc.setter; + if (setter == null + || setter == Scriptable.NOT_FOUND + || Undefined.isUndefined(setter)) { + return false; + } + final Scriptable receiverForCall; + if (receiver == null || Undefined.isUndefined(receiver)) { + receiverForCall = cx.isStrictMode() + ? null + : ScriptableObject.getTopLevelScope(target); + } else { + receiverForCall = ScriptableObject.ensureScriptable(receiver); + } + + ((Function) setter).call(cx, target, receiverForCall, new Object[] {value}); } - } - return true; + return true; + + } catch (EcmaError e) { + return false; + } } private static Object setPrototypeOf( diff --git a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeProxyTest.java b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeProxyTest.java index 224381403d..5620a325dc 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeProxyTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeProxyTest.java @@ -954,4 +954,56 @@ public void keysPreventExtensions() { Utils.assertWithAllModes_ES6(true, js); } + + @Test + public void setTrapReceivesReceiver() { + String js = + "var _receiver;\n" + + "var target = {};\n" + + "var proxy = new Proxy(target, {\n" + + " set: function(t, prop, value, receiver) {\n" + + " _receiver = receiver;\n" + + " t[prop] = value;\n" + + " return true;\n" + + " }\n" + + "});\n" + + "proxy.x = 1;\n" + + "'' + (_receiver === proxy) + ' ' + target.x"; + Utils.assertWithAllModes_ES6("true 1", js); + } + + @Test + public void setTrapReceiverWithIndexProperty() { + String js = + "var _receiver;\n" + + "var target = [10, 20, 30];\n" + + "var proxy = new Proxy(target, {\n" + + " set: function(t, prop, value, receiver) {\n" + + " _receiver = receiver;\n" + + " t[prop] = value;\n" + + " return true;\n" + + " }\n" + + "});\n" + + "proxy[1] = 99;\n" + + "'' + (_receiver === proxy) + ' ' + target[1]"; + Utils.assertWithAllModes_ES6("true 99", js); + } + + @Test + public void setTrapReceiverWithSymbolProperty() { + String js = + "var _receiver;\n" + + "var sym = Symbol('test');\n" + + "var target = {};\n" + + "var proxy = new Proxy(target, {\n" + + " set: function(t, prop, value, receiver) {\n" + + " _receiver = receiver;\n" + + " t[prop] = value;\n" + + " return true;\n" + + " }\n" + + "});\n" + + "proxy[sym] = 'hello';\n" + + "'' + (_receiver === proxy) + ' ' + target[sym]"; + Utils.assertWithAllModes_ES6("true hello", js); + } } diff --git a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeReflectTest.java b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeReflectTest.java index f0f23d679d..08d8734fb2 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeReflectTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/es6/NativeReflectTest.java @@ -460,4 +460,150 @@ public void setPrototypeOfSame() { + "+ ' ' + Reflect.setPrototypeOf(o3, proto)"; Utils.assertWithAllModes_ES6("true true true", js); } + + @Test + public void constructSubclassBuiltin() { + String js = + "function CustomSet() {\n" + + " return Reflect.construct(Set, arguments, this.constructor);\n" + + "}\n" + + "CustomSet.prototype = Object.create(Set.prototype);\n" + + "CustomSet.prototype.constructor = CustomSet;\n" + + "var set = new CustomSet([1, 2, 3]);\n" + + "set.add(4);\n" + + "'' + Array.from(set)" + + " + ' ' + (set instanceof CustomSet)" + + " + ' ' + (set instanceof Set)" + + " + ' ' + (Object.getPrototypeOf(set) === CustomSet.prototype)"; + Utils.assertWithAllModes_ES6("1,2,3,4 true true true", js); + } + + @Test + public void getWithReceiver() { + String js = + // accessor: receiver is used as 'this' in getter + "var target = {};\n" + + "Object.defineProperty(target, 'x', {\n" + + " get: function() { return this.value; }\n" + + "});\n" + + "var accessorResult = Reflect.get(target, 'x', { value: 42 });\n" + // inherited accessor: receiver is still used as 'this' + + "var child = Object.create(target);\n" + + "var protoResult = Reflect.get(child, 'x', { value: 'hello' });\n" + // data property: receiver is ignored + + "var dataResult = Reflect.get({ x: 10 }, 'x', { x: 99 });\n" + + "accessorResult + ' ' + protoResult + ' ' + dataResult"; + Utils.assertWithAllModes_ES6("42 hello 10", js); + } + + @Test + public void setWithReceiver() { + String js = + // accessor: receiver is used as 'this' in setter + "var target = {};\n" + + "Object.defineProperty(target, 'x', {\n" + + " set: function(v) { this.result = v; },\n" + + " get: function() { return this.result; }\n" + + "});\n" + + "var receiver = {};\n" + + "Reflect.set(target, 'x', 42, receiver);\n" + + "var accessorResult = receiver.result + ' ' + (target.result === undefined);\n" + // non-writable target: returns false + + "var nonWritableTarget = {};\n" + + "Object.defineProperty(nonWritableTarget, 'x'," + + " { value: 1, writable: false, configurable: true });\n" + + "var nonWritableResult = Reflect.set(nonWritableTarget, 'x', 2);\n" + // receiver constraints: accessor, non-writable, non-extensible all return false + + "var accessorReceiver = {};\n" + + "Object.defineProperty(accessorReceiver, 'x'," + + " { get: function() {}, set: function() {} });\n" + + "var readonlyReceiver = {};\n" + + "Object.defineProperty(readonlyReceiver, 'x'," + + " { value: 99, writable: false });\n" + + "var sealedReceiver = {}; Object.preventExtensions(sealedReceiver);\n" + + "var receiverResults = Reflect.set({ x: 1 }, 'x', 2, accessorReceiver)" + + " + ' ' + Reflect.set({ x: 1 }, 'x', 2, readonlyReceiver)" + + " + ' ' + Reflect.set(Object.create({ x: 1 }), 'x', 2, sealedReceiver);\n" + + "accessorResult" + + " + ' ' + nonWritableResult" + + " + ' ' + receiverResults"; + Utils.assertWithAllModes_ES6("42 true false false false false", js); + } + + @Test + public void getWithProxyTarget() { + String js = + "var target = { x: 1 };\n" + + "var proxy = new Proxy(target, {\n" + + " get: function(target, prop, receiver) {\n" + + " return 'trapped:' + prop;\n" + + " }\n" + + "});\n" + + "var trapResult = Reflect.get(proxy, 'x');\n" + + "var frozenTarget = {};\n" + + "Object.defineProperty(frozenTarget, 'x'," + + " { value: 42, writable: false, configurable: false });\n" + + "var nonConfigurableProxy = new Proxy(frozenTarget, {\n" + + " get: function(target, prop, receiver) { return 'wrong'; }\n" + + "});\n" + + "var nonConfigurableResult;\n" + + "try { Reflect.get(nonConfigurableProxy, 'x'); nonConfigurableResult = 'no error'; }" + + " catch (e) { nonConfigurableResult = 'threw'; }\n" + + "trapResult + ' ' + nonConfigurableResult"; + Utils.assertWithAllModes_ES6("trapped:x threw", js); + } + + @Test + public void setWithProxyTarget() { + String js = + "var trapLog = '';\n" + + "var target = {};\n" + + "var proxy = new Proxy(target, {\n" + + " set: function(target, prop, value, receiver) {\n" + + " trapLog = prop + '=' + value;\n" + + " target[prop] = value;\n" + + " return true;\n" + + " }\n" + + "});\n" + + "var trapResult = Reflect.set(proxy, 'x', 42);\n" + + "var frozenTarget = {};\n" + + "Object.defineProperty(frozenTarget, 'x'," + + " { value: 42, writable: false, configurable: false });\n" + + "var nonConfigurableProxy = new Proxy(frozenTarget, {\n" + + " set: function(target, prop, value, receiver) { return true; }\n" + + "});\n" + + "var nonConfigurableResult;\n" + + "try { Reflect.set(nonConfigurableProxy, 'x', 99); nonConfigurableResult = 'no error'; }" + + " catch (e) { nonConfigurableResult = 'threw'; }\n" + + "trapResult + ' ' + trapLog + ' ' + target.x" + + " + ' ' + nonConfigurableResult"; + Utils.assertWithAllModes_ES6("true x=42 42 threw", js); + } + + @Test + public void proxyTrapForwardsViaReflect() { + String js = + "var target = { x: 'hello' };\n" + + "Object.defineProperty(target, 'context', {\n" + + " get: function() { return 'context-' + (this.id || 'default'); }\n" + + "});\n" + + "var accessLog = [];\n" + + "var proxy = new Proxy(target, {\n" + + " get: function(target, key, receiver) {\n" + + " accessLog.push('get:' + key);\n" + + " return Reflect.get(target, key, receiver);\n" + + " },\n" + + " set: function(target, key, value, receiver) {\n" + + " accessLog.push('set:' + key);\n" + + " return Reflect.set(target, key, value, receiver);\n" + + " }\n" + + "});\n" + + "var getResult = proxy.x;\n" + + "var accessorResult = Reflect.get(proxy, 'context', { id: 'custom' });\n" + + "proxy.y = 99;\n" + + "var setResult = target.y;\n" + + "getResult + ' ' + accessorResult + ' ' + setResult" + + " + ' | ' + accessLog"; + Utils.assertWithAllModes_ES6("hello context-custom 99 | get:x,get:context,set:y", js); + } }