NativeProxy: fix set trap not passing receiver object#2347
Conversation
|
will work on the failing tests |
Co-authored-by: Lai Quang Duong
|
This is much bigger than expected, i have to fix the NativeReflect impl (inf fact bring it closer to the spec) to fix the StackOverflow. |
…low in set-mutable-binding-idref-with-proxy-env.js additionally limited return code handling for put for NativeProxy was implemented and used by NativeReflect. This fixes some more tests
|
@duonglaiquang sorry for being late on this, but is was harder than expected. My plan is to make a HtmlUnit 5.1.0 release this weekend, i fear this will not be reviewed/merged for the release. Hope this is ok for you... |
There was a problem hiding this comment.
Pull request overview
Updates Rhino’s Proxy/Reflect behavior around receiver forwarding (notably for Proxy set and Reflect get/set) and expands the ES6 test suite to cover receiver-sensitive semantics, aiming to align more closely with ECMA-262.
Changes:
- Add/expand JUnit coverage for
Reflect.get,Reflect.set, and Proxysetreceiver behavior (includingwith(proxy)scenarios). - Refactor parts of
NativeReflectto forward receivers and inline portions ofOrdinarySetWithOwnDescriptor. - Introduce
NativeProxy.putAndReturn(...)helpers to expose a boolean result for use byReflect.set.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 27 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/testsrc/test262.properties | Updates recorded test262 support/unsupported counts for Proxy/Reflect. |
| tests/src/test/java/org/mozilla/javascript/tests/es6/NativeReflectTest.java | Adds extensive coverage for Reflect.get/Reflect.set receiver semantics and related invariants. |
| tests/src/test/java/org/mozilla/javascript/tests/es6/NativeProxyTest.java | Adds tests asserting Proxy set trap receives a receiver and exercises with(proxy) behavior. |
| rhino/src/main/java/org/mozilla/javascript/NativeReflect.java | Adjusts Reflect operations to use receiver-aware access and implements a custom set path. |
| rhino/src/main/java/org/mozilla/javascript/NativeProxy.java | Adds putAndReturn(...) overloads and changes set trap invocation argument list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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) { |
| Function trap = getTrap(TRAP_SET); | ||
| if (trap != null) { | ||
| boolean booleanTrapResult = | ||
| ScriptRuntime.toBoolean( | ||
| callTrap( | ||
| trap, | ||
| new Object[] {target, ScriptRuntime.toString(index), value})); | ||
| new Object[] { | ||
| target, ScriptRuntime.toString(index), value, this | ||
| })); |
| 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) { |
| // receiver == target (or target has no own property P): | ||
| // delegate to [[Set]] on the receiver, which correctly fires any proxy | ||
| // set trap when receiver/target is a Proxy. | ||
|
|
||
| // For a NativeProxy, we use putAndReturn() to capture the result. | ||
| if (target instanceof NativeProxy proxyTarget) { | ||
| if (key instanceof Symbol) { | ||
| return proxyTarget.putAndReturn((Symbol) key, receiver, args[2]); | ||
| } | ||
|
|
||
| StringIdOrIndex soi = ScriptRuntime.toStringIdOrIndex(key); | ||
| if (soi.stringId == null) { | ||
| return proxyTarget.putAndReturn(soi.index, receiver, args[2]); | ||
| } | ||
| return proxyTarget.putAndReturn(soi.stringId, receiver, args[2]); | ||
| } | ||
|
|
||
| if (ScriptRuntime.isSymbol(args[1])) { | ||
| receiver.put((Symbol) args[1], receiver, args[2]); | ||
| // For a plain ScriptableObject put() has no return value, we | ||
| // assume true. | ||
| if (key instanceof Symbol) { | ||
| receiver.put((Symbol) key, receiver, args[2]); | ||
| } else { | ||
| StringIdOrIndex soi = ScriptRuntime.toStringIdOrIndex(args[1]); | ||
| StringIdOrIndex soi = ScriptRuntime.toStringIdOrIndex(key); | ||
| if (soi.stringId == null) { | ||
| receiver.put(soi.index, receiver, args[2]); | ||
| } else { | ||
| receiver.put(soi.stringId, receiver, args[2]); | ||
| } | ||
| } |
| // Step 3: default receiver to target when not supplied. | ||
| Scriptable receiver = | ||
| (args.length > 2 && args[2] instanceof Scriptable) ? (Scriptable) args[2] : target; | ||
|
|
| // Step 3: default receiver to target when not supplied. | ||
| Scriptable receiver = | ||
| (args.length > 2 && args[2] instanceof Scriptable) ? (Scriptable) args[2] : target; | ||
|
|
| // Step 3: default receiver to target when not supplied (args[3]). | ||
| // Note: receiver is intentionally typed as Scriptable, not ScriptableObject, | ||
| // because the caller may pass any object (e.g. a Proxy) as the receiver. | ||
| Scriptable receiver = args.length > 3 ? ScriptableObject.ensureScriptable(args[3]) : target; | ||
|
|
| ScriptableObject target = checkTarget(args); | ||
| if (args.length < 2) { | ||
| return true; | ||
| } |
| // Step 3: default receiver to target when not supplied (args[3]). | ||
| // Note: receiver is intentionally typed as Scriptable, not ScriptableObject, | ||
| // because the caller may pass any object (e.g. a Proxy) as the receiver. | ||
| Scriptable receiver = args.length > 3 ? ScriptableObject.ensureScriptable(args[3]) : target; | ||
|
|
| ScriptableObject target = checkTarget(args); | ||
| if (args.length < 2) { | ||
| return true; | ||
| } |
|
will work on all the sugestions... at least some looking correct ;-) |
| if (!(args[0] instanceof Callable)) { | ||
| throw ScriptRuntime.notFunctionError(args[0]); | ||
| } | ||
| Scriptable callable = (Scriptable) args[0]; |
| Object key = args[1]; | ||
|
|
||
| try { | ||
| if (key instanceof Symbol) { | ||
| return target.defineOwnProperty(cx, key, desc); | ||
| } else { | ||
| String propertyKey = | ||
| ScriptRuntime.toString( | ||
| ScriptRuntime.toPrimitive(key, ScriptRuntime.StringClass)); | ||
| return target.defineOwnProperty(cx, propertyKey, desc); | ||
| } | ||
|
|
||
| String propertyKey = | ||
| ScriptRuntime.toString( | ||
| ScriptRuntime.toPrimitive(key, ScriptRuntime.StringClass)); | ||
| return target.defineOwnProperty(cx, propertyKey, desc); | ||
| } catch (EcmaError e) { | ||
| // Rhino throws where the spec says [[DefineOwnProperty]] should return false. | ||
| // Map those back to the spec-correct false return value. | ||
| return false; | ||
| } |
| ScriptableObject target = checkTarget(args); | ||
| if (args.length < 2) { | ||
| return true; | ||
| } | ||
|
|
| key = ScriptRuntime.toString(args[1]); // coerce to String | ||
| } | ||
|
|
||
| if (receiver != target) { |
| if (receiver instanceof ScriptableObject receiverObj) { | ||
| DescriptorInfo newDesc = new DescriptorInfo(true, true, true, args[2]); | ||
| receiverObj.defineOwnProperty(cx, key, newDesc); | ||
| } | ||
| return true; |
| receiverObj.defineOwnProperty(cx, key, valueDesc); | ||
| return true; |
| receiverObj.defineOwnProperty(cx, key, newDesc); | ||
| return true; |
|
this seems to be a rabbit hole, i have to think about a good way to fix this step by step. Back to Draft... |
Co-authored-by: Lai Quang Duong
This
This does not make so many more 262 test to pass, but i think is at least a small step forward.