Proxies: wrong "receiver" used in default "set" trap

Tom Van Cutsem tomvc.be at gmail.com
Tue Dec 18 13:56:54 PST 2012


Hi,

Someone recently reported an issue [1] while using my harmony-reflect shim
for direct proxies. The fix probably requires a change in the proxy
specification. I'm unsure how to resolve it, so I thought I'd bring it to
the list.

The issue is as follows:

consider a proxy with an empty handler:
var proxy = Proxy(target, {});

now consider assigning a property to the proxy:
proxy.foo = 42

This triggers the proxy's "set" trap, but since the handler does not define
this trap, the default behavior is to forward the assignment to the target.

As currently specified, the intercepted assignment is forwarded as:

Reflect.set(target, 'foo', 42, proxy)

where 'proxy' is used as the "initial receiver" of the property assignment.

This fourth "receiver" argument matters for two reasons:
1) if 'target' defines or inherits 'foo' as an accessor property, inside
that accessor, |this| will point to that receiver argument.
2) if receiver !== target, then Reflect.set will *add* a new data property
to receiver, rather than *update* an existing data property on the target.

Currently, for an assignment as shown above, the proxy itself is passed as
the fourth 'receiver' argument. Thus:
1) inside triggered accessors, |this| will refer to the proxy, not to the
target. This is acceptable.
2) since proxy !== target, Reflect.set will try to *add* data properties to
'proxy', rather than *update* an existing data property on the target. This
is not acceptable and is what causes the issue [1].

I see two solutions, but can't decide on which is better. There may be
better solutions altogether.

Option A:
change the default forwarding behavior of the "set" trap to:

if the proxy is the initial receiver of the property assignment, then
  return Reflect.set(target, name, val, target)
else
  return Reflect.set(target, name, val, receiver)

If the initial receiver is an object that delegates to a proxy, the proxy
won't change the receiver upon forwarding, in order to not interfere with
prototype inheritance.

This solution fixes point 2) since Reflect.set is no longer confused about
the proxy.
Re. point 1), the |this|-binding inside forwarded accessors will now refer
to the target object itself, which I find equally acceptable.

For reasons of symmetry, if we go this route, we probably need to change
the default forwarding behavior of "get" in a similar way. Point 2) does
not come up in this case, but Point 1) does. We probably want the rules for
|this|-binding in forwarded getters to be consistent with setters.

Option B:
Address point 2) directly by changing the test that determines property
addition versus property update inside Reflect.set (i.e. the [[SetP]]
internal method of objects) so that the algorithm no longer tests whether
target === receiver, but rather whether target === receiver || receiver is
a proxy for target.

This solves the issue at hand, although it feels like a more ad hoc
solution.


Another way of looking at things is that the Reflect.set (or [[SetP]])
algorithm currently assumes that the "receiver" argument is either the
target object itself, or otherwise an object that directly or indirectly
inherits from the target object. If this assumption is violated, strange
behavior can ensue. In the above example, the proxy passed in as the
"receiver" argument is neither the target object nor an object that
inherits from it, hence the strange behavior.

Cheers,
Tom

[1] https://github.com/tvcutsem/harmony-reflect/issues/11
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121218/a18b6a37/attachment.html>


More information about the es-discuss mailing list