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

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Dec 18 17:24:47 PST 2012


I think it has to be A, for consistency with [[Call]].  Note that when [[Call]] is directly forwarded to the target, the this value is set to target. It wouldn't be self-consistent if directly forwarded foo.access and foo.method() invocations used different this values.

I assume that by "proxy is the initial receiver", you mean that for a [[SetP]]/[[GetP]] invocation that  SameValue(O,Receiver) is true.  If so, that also seems right to me.  In writjing various [[SetP]]/[[GetP]]/equivalent proxy traps, this test is one I've found that I routine have  to make.  It basically distinguished between the initial application of the operation and  a proto climbing (or other similar delegating) application. 

I'm actually surprised that I didn't notice this wrong receiver issue when I incorporated Proxy exotics into the ES6 spec. as I spend a lot of time thinking about what was the correct this value to forward in various situations.  I'm not blissfully happy with the current forwarding model, but I think it is ok as long as we make sure it is completely self consistent.  The situations I was concerned with are similar for [[Call]] when it is directly forward.  If prox.foo() is forwarded so that the foo method is invoked with the proxy's target as the this value then any this.bar() calls within the foo method dispatches through the target rather than prox.  From an OO delegation perspective this feels wrong.  However, it feels ok if you think of direct forward not as a form of object delegation but rather redirection to a completely oindependent object(the target).  In that case, it is very important that all forwarding consistently use the target as the receiver.

If you really want to [[Call]] a target method with the proxy as the this value or do the equivalent for [[SetP]]//[[GetP]] you can do it by providing a hander that does what you want rather than depending upon direct forwarding.

Allen

 

On Dec 18, 2012, at 1:56 PM, Tom Van Cutsem wrote:

> 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 'If you want to   ddasfdasfasdfafasfasffsff
> 
> 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 _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121218/bb92351c/attachment-0001.html>


More information about the es-discuss mailing list