[Harmony proxies] "add receiver as a first argument to all prototype-climbing traps"

# David Bruant (14 years ago)

First, I'd like to say that I'm glad proxy-related issues have been discussed during the last TC-39 meeting and that all have found an agreement.

Then I have a something to say on strawman:handler_access_to_proxy Quoting relevant parts: "Andreas: experimenting with DOM wrappers. All prototype-climbing traps require access to the receiver object (which is not necessarily the proxy object), not just the get/set traps. The get and set trap may want access to both the receiver and the proxy. " "Current consensus:

  • add receiver as a first argument to all prototype-climbing traps (get, set, has, getPropertyNames, getPropertyDescriptor traps)"

The intention I understand is that these proto-climbing traps should first be applied to the own layer (or rather, their "own" equivalent) then be applied recursively while climbing the prototype chain. However, I think that with current ES5 internal method definitions and proxies semantics (harmony:proxies_semantics), this doesn't work as the intention goes. For instance, [[Get]] (ES5 8.12.3) uses [[GetProperty]] and calls [[GetProperty]] recursively on the prototype object instead of the [[Get]] internal method. And currently (it was the case before the meeting. I'm just realizing now), semantics of [[GetProperty]] is to return undefined. Similar issues goes with current "has" trap and [[HasProperty]] internal methods which delegates inheritance responsibility to [[GetProperty]] (back to previous problem).

However, getPropertyNames and getPropertyDescriptor traps, recently "promoted" as derived traps are still underspecified. Interestingly, the current implementation (strawman:proxy_derived_traps) isn't recursive, but iterative. This is inconsistent with the current model and should certainly be redefined as a recursive function:

getPropertyDescriptor: function(receiver, name, proxy) { var pd = Object.getOwnPropertyDescriptor(receiver, name, proxy); var proto = Object.getPrototypeOf(proxy); if(proto === null || pd !== undefined) return pd; return Object.getPropertyDescriptor(proto); // recursive version. receiver is passed in some way (?) }

I'd like to remind that if Object.getPropertyDescriptorisn't a native function with a native underlying internal method, but a monkey patch, it will not call a proxy trap (maybe this should be addressed?).

The iterative model differs from the recursive one in a way that the engine is in control of making sure all layers are well-traversed and not "hijacked" by one particular layer. This is actually the idea I called "inheritance-safe proxies" (esdiscuss/2011-March/013366).

I am wondering if by providing both proxy and receiver to all proto-climbing methods, we're not providing too much power to proxies. Let's imagine the following object with prototype chain: o --> a --> b --> null.

a is a proxy and o and b are regular objects (or even proxies, it doesn't really matter with the current model). First, with the current model, since when a call is trapped, inheritance and actual prototype can be faked, a has the power of denying b and messing up with be through Object.getPrototypeOf. Now, if a gets access to 'receiver', it means it can mess up with o. Aren't we providing too much power to a? The more I think about it, the more I question the idea of provinding receiver as an argument to proto-climbing traps. What was the initial rationale behind it? I understand that it is different from proxy, but is it necessary? What are the use cases that require it?

# Tom Van Cutsem (14 years ago)

2011/3/31 David Bruant <david.bruant at labri.fr>

Hi,

First, I'd like to say that I'm glad proxy-related issues have been discussed during the last TC-39 meeting and that all have found an agreement.

Then I have a something to say on strawman:handler_access_to_proxy Quoting relevant parts: "Andreas: experimenting with DOM wrappers. All prototype-climbing traps require access to the receiver object (which is not necessarily the proxy object), not just the get/set traps. The get and set trap may want access to both the receiver and the proxy. " "Current consensus:

  • add receiver as a first argument to all prototype-climbing traps (get, set, has, getPropertyNames, getPropertyDescriptor traps)"

The intention I understand is that these proto-climbing traps should first be applied to the own layer (or rather, their "own" equivalent) then be applied recursively while climbing the prototype chain. However, I think that with current ES5 internal method definitions and proxies semantics ( harmony:proxies_semantics), this doesn't work as the intention goes. For instance, [[Get]] (ES5 8.12.3) uses [[GetProperty]] and calls [[GetProperty]] recursively on the prototype object instead of the [[Get]] internal method. And currently (it was the case before the meeting. I'm just realizing now), semantics of [[GetProperty]] is to return undefined. Similar issues goes with current "has" trap and [[HasProperty]] internal methods which delegates inheritance responsibility to [[GetProperty]] (back to previous problem).

However, getPropertyNames and getPropertyDescriptor traps, recently "promoted" as derived traps are still underspecified. Interestingly, the current implementation ( strawman:proxy_derived_traps) isn't recursive, but iterative. This is inconsistent with the current model and should certainly be redefined as a recursive function:

getPropertyDescriptor: function(receiver, name, proxy) { var pd = Object.getOwnPropertyDescriptor(receiver, name, proxy); var proto = Object.getPrototypeOf(proxy); if(proto === null || pd !== undefined) return pd; return Object.getPropertyDescriptor(proto); // recursive version. receiver is passed in some way (?) }

I think you're right. The spec uses recursion, so a proxy higher up the inheritance chain could terminate the lookup early.

I also don't see how the original object to which Object.getPropertyDescriptor was applied could be passed up the chain. I also don't see the need for it, other than perhaps calling |bind(receiver)| on the found property descriptor's value, get or set attribute?

I'd like to remind that if Object.getPropertyDescriptor isn't a native function with a native underlying internal method, but a monkey patch, it will not call a proxy trap (maybe this should be addressed?).

There's a Harmony proposal that proposes adding Object.getPropertyDescriptor as a new built-in on Object for precisely this reason: < harmony:extended_object_api>

The iterative model differs from the recursive one in a way that the engine is in control of making sure all layers are well-traversed and not "hijacked" by one particular layer. This is actually the idea I called "inheritance-safe proxies" ( esdiscuss/2011-March/013366).

I am wondering if by providing both proxy and receiver to all proto-climbing methods, we're not providing too much power to proxies. Let's imagine the following object with prototype chain: o --> a --> b --> null. a is a proxy and o and b are regular objects (or even proxies, it doesn't really matter with the current model). First, with the current model, since when a call is trapped, inheritance and actual prototype can be faked, a has the power of denying b and messing up with be through Object.getPrototypeOf. Now, if a gets access to 'receiver', it means it can mess up with o. Aren't we providing too much power to a?

Proxies would always be able to "deny" lookup proceeding to their prototype, since you could implement a proxy that pretends to have all possible properties as own properties. But granted: if the handler never gets access to the proxy, then I don't think it can otherwise influence the proxy's prototype.

As for |a| getting access to |o|, it needs this to faithfully emulate accessor properties (see below).

The more I think about it, the more I question the idea of provinding receiver as an argument to proto-climbing traps. What was the initial rationale behind it? I understand that it is different from proxy, but is it necessary? What are the use cases that require it?

For the get and set traps, a proxy needs the original |receiver| as the |this|-binding when calling the get/set functions of accessor properties. Cf. their use in the default implementations of |get| and |set| here: < harmony:proxies#trap_defaults>

I'm not sure why it's needed in the other prototype-climbing traps (getPropertyDescriptor, getPropertyNames, has, enumerate). Perhaps Andreas can explain.

# David Bruant (14 years ago)

Le 04/04/2011 17:08, Tom Van Cutsem a écrit :

Hi,

2011/3/31 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>

Hi,

First, I'd like to say that I'm glad proxy-related issues have
been discussed during the last TC-39 meeting and that all have
found an agreement.

Then I have a something to say on
http://wiki.ecmascript.org/doku.php?id=strawman:handler_access_to_proxy
Quoting relevant parts:
"Andreas: experimenting with DOM wrappers. All prototype-climbing
traps require access to the receiver object (which is not
necessarily the proxy object), not just the get/set traps. The get
and set trap may want access to both the receiver and the proxy. "
"Current consensus:
* add receiver as a first argument to all prototype-climbing traps
(get, set, has, getPropertyNames, getPropertyDescriptor traps)"

The intention I understand is that these proto-climbing traps
should first be applied to the own layer (or rather, their "own"
equivalent) then be applied recursively while climbing the
prototype chain.
However, I think that with current ES5 internal method definitions
and proxies semantics
(http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics), this
doesn't work as the intention goes. For instance, [[Get]] (ES5
8.12.3) uses [[GetProperty]] and calls [[GetProperty]] recursively
on the prototype object instead of the [[Get]] internal method.
And currently (it was the case before the meeting. I'm just
realizing now), semantics of [[GetProperty]] is to return undefined.
Similar issues goes with current "has" trap and [[HasProperty]]
internal methods which delegates inheritance responsibility to
[[GetProperty]] (back to previous problem).

However, getPropertyNames and getPropertyDescriptor traps,
recently "promoted" as derived traps are still underspecified.
Interestingly, the current implementation
(http://wiki.ecmascript.org/doku.php?id=strawman:proxy_derived_traps)
isn't recursive, but iterative. This is inconsistent with the
current model and should certainly be redefined as a recursive
function:
---
getPropertyDescriptor: function(receiver, name, proxy) {
  var pd = Object.getOwnPropertyDescriptor(receiver, name, proxy);
  var proto = Object.getPrototypeOf(proxy);
  if(proto === null || pd !== undefined)
    return pd;
  return Object.getPropertyDescriptor(proto); // recursive
version. receiver is passed in some way (?)
}
---

I think you're right. The spec uses recursion, so a proxy higher up the inheritance chain could terminate the lookup early.

I also don't see how the original object to which Object.getPropertyDescriptor was applied could be passed up the chain. I also don't see the need for it, other than perhaps calling |bind(receiver)| on the found property descriptor's value, get or set attribute?

I don't think it's necessary for the "value".

var p = Proxy.create(h); p.f(); // in the meta-level means: h.get(p, 'f').apply(p);

But the .apply(p) is handled by the engine after the h.get call. So the "get" trap doesn't need the receiver to bind it for the function call. I agree it does need it for set/get anyway.

I'd like to remind that if Object.getPropertyDescriptorisn't a
native function with a native underlying internal method, but a
monkey patch, it will not call a proxy trap (maybe this should be
addressed?).

There's a Harmony proposal that proposes adding Object.getPropertyDescriptor as a new built-in on Object for precisely this reason: harmony:extended_object_api

I agree. The point I wanted to see addressed is when ES engines only partially natively implement the Object introspection API. Currently, Safari 5 doesn't implement preventExtension/seal/freeze. No browser implement getPropertyDescriptor and getPropertyNames (so Object.getPropertyDescriptor wouldn't be trapped if monkey-patched). If all browsers implement proxies now, the same code will reveal inconsistencies if browsers have different stage of Object.* implementation. Same thing if new traps are created afterward. It's very unlikely that all browser will implement the relevant Object.* method at the same time. Maybe we should investigate something like Proxy.trap(o, name, arguments). Example:

if(typeof Object.getPropertyDescriptor != "function"){ Object.getPropertyDescriptor = function(o){ if(Proxy.isProxy(o)){ // There is a need to be able to discriminate return Proxy.trap(o, "getPropertyDescriptor"); // call the trap manually, because the native code cannot do it. /* For seal/freeze, the trap name has nothing to do with the method name and no 1:1 mapping can be done, hence the string argument */ } else{ // code for regular object } }; }

This has downsides and as always, I'm not going to fight for this particular syntax, but I think that there is a need for a mechanism for developers to compensate browsers uneven implementations. In a world where all browsers would all move at the same pace and older browsers market share were dropping as soon as a new version was showing up, we wouldn't need such a mechanism, but I don't think we should assume living in such a world.

The iterative model differs from the recursive one in a way that
the engine is in control of making sure all layers are
well-traversed and not "hijacked" by one particular layer. This is
actually the idea I called "inheritance-safe proxies"
(https://mail.mozilla.org/pipermail/es-discuss/2011-March/013366.html).

I am wondering if by providing both proxy and receiver to all
proto-climbing methods, we're not providing too much power to
proxies. Let's imagine the following object with prototype chain:
o --> a --> b --> null.
a is a proxy and o and b are regular objects (or even proxies, it
doesn't really matter with the current model). First, with the
current model, since when a call is trapped, inheritance and
actual prototype can be faked, a has the power of denying b and
messing up with be through Object.getPrototypeOf. Now, if a gets
access to 'receiver', it means it can mess up with o. Aren't we
providing too much power to a?

Proxies would always be able to "deny" lookup proceeding to their prototype, since you could implement a proxy that pretends to have all possible properties as own properties. But granted: if the handler never gets access to the proxy, then I don't think it can otherwise influence the proxy's prototype.

As for |a| getting access to |o|, it needs this to faithfully emulate accessor properties (see below).

The more I think about it, the more I question the idea of
provinding receiver as an argument to proto-climbing traps. What
was the initial rationale behind it? I understand that it is
different from proxy, but is it necessary? What are the use cases
that require it?

For the get and set traps, a proxy needs the original |receiver| as the |this|-binding when calling the get/set functions of accessor properties. Cf. their use in the default implementations of |get| and |set| here: harmony:proxies#trap_defaults

You are perfectly right. I'm not really happy with the idea of providing receiver as an argument to traps, but I don't see how get and set could be implemented otherwise.

# Tom Van Cutsem (14 years ago)

I'd like to remind that if Object.getPropertyDescriptor isn't a native

function with a native underlying internal method, but a monkey patch, it will not call a proxy trap (maybe this should be addressed?).

There's a Harmony proposal that proposes adding Object.getPropertyDescriptor as a new built-in on Object for precisely this reason: < harmony:extended_object_api>

I agree. The point I wanted to see addressed is when ES engines only partially natively implement the Object introspection API. Currently, Safari 5 doesn't implement preventExtension/seal/freeze. No browser implement getPropertyDescriptor and getPropertyNames (so Object.getPropertyDescriptor wouldn't be trapped if monkey-patched). If all browsers implement proxies now, the same code will reveal inconsistencies if browsers have different stage of Object.* implementation. Same thing if new traps are created afterward. It's very unlikely that all browser will implement the relevant Object.* method at the same time. Maybe we should investigate something like Proxy.trap(o, name, arguments). Example:

if(typeof Object.getPropertyDescriptor != "function"){ Object.getPropertyDescriptor = function(o){ if(Proxy.isProxy(o)){ // There is a need to be able to discriminate return Proxy.trap(o, "getPropertyDescriptor"); // call the trap manually, because the native code cannot do it. /* For seal/freeze, the trap name has nothing to do with the method name and no 1:1 mapping can be done, hence the string argument */ } else{ // code for regular object } }; }

This has downsides and as always, I'm not going to fight for this particular syntax, but I think that there is a need for a mechanism for developers to compensate browsers uneven implementations. In a world where all browsers would all move at the same pace and older browsers market share were dropping as soon as a new version was showing up, we wouldn't need such a mechanism, but I don't think we should assume living in such a world.

Is it really that big an issue?

Considering Object.getPropertyDescriptor, if a shim defines it in terms of a recursive prototype-chain walk + Object.getOwnPropertyDescriptor, I would surmise that this leads to correct behavior if the proxy is well-behaved (i.e. the prototype-chain it emulates is consistent with |Object.getPrototypeOf(proxy)| ).

If a browser does not support Object.freeze, seal or preventExtensions, it likely does not support fixing proxies either. Calling the "fix()" trap explicitly via Proxy.trap would still not lead to the expected behavior.

# David Bruant (14 years ago)

Le 05/04/2011 10:22, Tom Van Cutsem a écrit :

Hi,

    I'd like to remind that if Object.getPropertyDescriptorisn't
    a native function with a native underlying internal method,
    but a monkey patch, it will not call a proxy trap (maybe this
    should be addressed?).


There's a Harmony proposal that proposes adding
Object.getPropertyDescriptor as a new built-in on Object for
precisely this
reason: <http://wiki.ecmascript.org/doku.php?id=harmony:extended_object_api>
I agree.
The point I wanted to see addressed is when ES engines only
partially natively implement the Object introspection API.
Currently, Safari 5 doesn't implement
preventExtension/seal/freeze. No browser implement
getPropertyDescriptor and getPropertyNames (so
Object.getPropertyDescriptor wouldn't be trapped if monkey-patched).
If all browsers implement proxies now, the same code will reveal
inconsistencies if browsers have different stage of Object.*
implementation.
Same thing if new traps are created afterward. It's very unlikely
that all browser will implement the relevant Object.* method at
the same time.
Maybe we should investigate something like Proxy.trap(o, name,
arguments). Example:
----
if(typeof Object.getPropertyDescriptor != "function"){
  Object.getPropertyDescriptor = function(o){
    if(Proxy.isProxy(o)){ // There is a need to be able to
discriminate
      return Proxy.trap(o, "getPropertyDescriptor"); // call the
trap manually, because the native code cannot do it.
      /* For seal/freeze, the trap name has nothing to do with the
method name and no 1:1 mapping can be done, hence the string
argument */
    }
    else{
       // code for regular object
    }
  };
}
----
This has downsides and as always, I'm not going to fight for this
particular syntax, but I think that there is a need for a
mechanism for developers to compensate browsers uneven
implementations.
In a world where all browsers would all move at the same pace and
older browsers market share were dropping as soon as a new version
was showing up, we wouldn't need such a mechanism, but I don't
think we should assume living in such a world.

Is it really that big an issue?

Considering Object.getPropertyDescriptor, if a shim defines it in terms of a recursive prototype-chain walk + Object.getOwnPropertyDescriptor, I would surmise that this leads to correct behavior if the proxy is well-behaved (i.e. the prototype-chain it emulates is consistent with |Object.getPrototypeOf(proxy)| ).

I see two things when trapping: the trap call (with side effects) and the return value. I agree with you on the return value. But if the proxy doesn't trap it also means that there is no side effect (logging, forwarding, visitor/decorator pattern...). That's a bigger issue in my opinion.

If a browser does not support Object.freeze, seal or preventExtensions, it likely does not support fixing proxies either. Calling the "fix()" trap explicitly via Proxy.trap would still not lead to the expected behavior.

You are right. Maybe it could be written somewhere that Proxy should only be implemented in an ES5-compliant environment (+ maybe other dependencies).

# David Bruant (14 years ago)

Le 05/04/2011 10:22, Tom Van Cutsem a écrit :

(...)

If a browser does not support Object.freeze, seal or preventExtensions, it likely does not support fixing proxies either. Calling the "fix()" trap explicitly via Proxy.trap would still not lead to the expected behavior.

Thinking about fix and the special TypeError||become behavior happening after the trap call, I think it would make sense that for the particular "fix" value, Proxy.trap would call the trap and do the TypeError||become behavior. I may be wrong, but I think that if a user calls Proxy.trap("fix"), s/he expects the proxy to be fixed afterward.

# Tom Van Cutsem (14 years ago)

2011/4/5 David Bruant <david.bruant at labri.fr>

Le 05/04/2011 10:22, Tom Van Cutsem a écrit :

(...)

If a browser does not support Object.freeze, seal or preventExtensions, it likely does not support fixing proxies either. Calling the "fix()" trap explicitly via Proxy.trap would still not lead to the expected behavior. Thinking about fix and the special TypeError||become behavior happening after the trap call, I think it would make sense that for the particular "fix" value, Proxy.trap would call the trap and do the TypeError||become behavior. I may be wrong, but I think that if a user calls Proxy.trap("fix"), s/he expects the proxy to be fixed afterward.

Following your design of allowing proxy users to explicitly call all traps, rather than special-casing "fix", an alternative could be to introduce a Proxy.fix(proxy, pdmap) method that expects a property descriptor map and fixes the proxy, so that regular fixing would become expressible as |Proxy.fix(proxy, Proxy.trap(proxy,"fix",[]))|. IIRC, an early version of the tracemonkey Proxy API had a method similar to Proxy.fix just for the sake of testing the fix trap, since it didn't support Object.freeze yet.

But again, I would hope that an implementation that supports both Object.freeze + proxies just supports freezing proxies via the fix() trap as per the draft spec. I don't see the need for such an implementation to additionally provide an explicit mechanism like Proxy.fix.

# Brendan Eich (14 years ago)

On Apr 7, 2011, at 7:49 AM, Tom Van Cutsem wrote:

But again, I would hope that an implementation that supports both Object.freeze + proxies just supports freezing proxies via the fix() trap as per the draft spec. I don't see the need for such an implementation to additionally provide an explicit mechanism like Proxy.fix.

Agreed.

David, no need to overdesign for pathological partial-ES5 + Harmony-Proxies implementations! That's just a non-goal.

# David Bruant (14 years ago)

Le 07/04/2011 20:39, Brendan Eich a écrit :

On Apr 7, 2011, at 7:49 AM, Tom Van Cutsem wrote:

But again, I would hope that an implementation that supports both Object.freeze + proxies just supports freezing proxies via the fix() trap as per the draft spec. I don't see the need for such an implementation to additionally provide an explicit mechanism like Proxy.fix. Agreed.

David, no need to overdesign for pathological partial-ES5 + Harmony-Proxies implementations! That's just a non-goal.

I agree too as I said in my first reply. Nevertheless, partial ES5 is not the only worry. My understanding of the fix trap design is that it is called whenever a language construct sets [[Extensible]] to false. We can expect ES implementations with proxies to implement the current language constructs which do so (Object.preventExtension|seal|freeze), I agree. But no one can predict if an Object.* method which would set [[Extensible]] to false will be added later. If it happens, people will want to monkey-patch this method in implementations that don't support it natively. (Partial ES5 was just one instanciation of this issue). Like today, no worries to monkey-patch the behavior for regular objects, but people will expect this method to have the correct behavior on proxies too. Which means having a mechanism to call the "fix" trap (with a way to distinguish call cases as suggested in esdiscuss/2011-April/013577 ?) and to fix the proxy (TypeError||become). Without the distinction part, Object.preventExtension fits the job. With the distinction part, a Proxy.trap + Proxy.fix (or equivalent mechanisms) may be necessary.

# Brendan Eich (14 years ago)

On Apr 8, 2011, at 2:48 AM, David Bruant wrote:

Like today, no worries to monkey-patch the behavior for regular objects, but people will expect this method to have the correct behavior on proxies too. Which means having a mechanism to call the "fix" trap (with a way to distinguish call cases as suggested in esdiscuss/2011-April/013577 ?) and to fix the proxy (TypeError||become). Without the distinction part, Object.preventExtension fits the job. With the distinction part, a Proxy.trap + Proxy.fix (or equivalent mechanisms) may be necessary.

It's a good point, but my conclusion, or assumption really, was that we wouldn't add any more Object.* methods to clear [[Extensible]]. It doesn't seem worth it, and your point is a good reason to actively avoid such additions.

Any other reason for a way to call fix explicitly?