Invoke trap (was: May 21, 22, 23 TC39 Meeting Notes)

# David Bruant (12 years ago)

Le 02/06/2013 09:46, Rick Waldron a écrit :

4.4 Proxies

Proxy Invoke Trap and wrong |this|-binding on built-in methods

AWB: with current default behavior of “get”, “Caretaker” will break on built-ins such as Date, because the |this| binding is by default set to the proxy, so the Date built-in method will not find the correct private state. ARB: Same issue with binary methods ... STH: We should add invoke trap but not change the object model MM: Pleasant to have. Separate from private state. AWB: used to think this was an issue with proxies, but convinced that it’s an API issue: we need to provide default handlers that do the right thing, and which users can subclass. In particular, want a handler that, on forwarding, rebinds |this| to the target. STH: If you want to proxy a Date method the underlying this needs to be a non wrapped Date object.

Doesn't this break encapsulation?

var d = new Date(); var pd = new Proxy(d, { invoke: function(target, name, thisArg, args){ return target[name].apply(thisArg, args) } })

var pt = pd.getTime(); // calls the invoke trap

// in a mixed-trusted context with access to pd: var ppd = new Proxy(pd, { invoke: function(target, name, thisArg, args){ // does the actual unwrapped date object leak through thisArg? } });

var ppt = ppd.getTime();

In any case, what is the following supposed to do:

Date.prototype.getTime.call( new Proxy(new Date(), handler) )

I imagine we would want this to work too. Is this going through the invoke trap as well? The question stands for all new "class-specific" (Map, Set, etc.) methods that would be dot-called with a proxy as first argument.

If the problem being solved is making Date (and Map, Set, etc) built-in methods works on wrapped objects, I feel the invoke trap is only a partial solution.

I intuit (but have no proof) that for Date.prototype.getTime.call to work on proxies, there is a need for class-specific traps.

  • Object properties have specific traps to intermediate access (has/get/set/defineProperty/keys, etc.),
  • [[Prototype]] has specific traps to intermediate access (get/setPrototypeOf)
  • [[Extensible]] has a specific trap to intermediate access (preventExtensions)
  • [[Call]] has a specific trap to intermediate access (apply)
  • [[Construct]] has a specific trap to intermediate access (construct)
  • Some other things that are somewhat internal (@@iterator, @@class, etc.) are exposed as symbols and are mediated via object properties-related trap

The pattern I see is that a good share of object internal properties (ES5.1 - 8.6.2 - Table 8 and Table 9) have traps dedicated to mediate access.

The case of functions is interesting. There are traps that are only relevant when the target is a function. In that regard, adding traps that are specific to accessing the Date internal [[Time]] value or specific traps to mediate access to [[MapData]] isn't any different than the current specific traps that mediate [[Call]] or [[Construct]]

# Tom Van Cutsem (12 years ago)

2013/6/7 David Bruant <bruant.d at gmail.com>

Le 02/06/2013 09:46, Rick Waldron a écrit :

4.4 Proxies

Proxy Invoke Trap and wrong |this|-binding on built-in methods

AWB: with current default behavior of “get”, “Caretaker” will break on built-ins such as Date, because the |this| binding is by default set to the proxy, so the Date built-in method will not find the correct private state. ARB: Same issue with binary methods ... STH: We should add invoke trap but not change the object model MM: Pleasant to have. Separate from private state. AWB: used to think this was an issue with proxies, but convinced that it’s an API issue: we need to provide default handlers that do the right thing, and which users can subclass. In particular, want a handler that, on forwarding, rebinds |this| to the target. STH: If you want to proxy a Date method the underlying this needs to be a non wrapped Date object.

Doesn't this break encapsulation?

No it doesn't (more below).

var d = new Date(); var pd = new Proxy(d, { invoke: function(target, name, thisArg, args){ return target[name].apply(thisArg, args) } })

var pt = pd.getTime(); // calls the invoke trap

This will call "invoke", which forwards to the real Date.prototype.getTime but with thisArg still bound to pd (the proxy). Hence this method will fail with a "error: not a Date" exception. The proposed solution is to use a ForwardingHandler [1] in such cases.

// in a mixed-trusted context with access to pd: var ppd = new Proxy(pd, { invoke: function(target, name, thisArg, args){ // does the actual unwrapped date object leak through thisArg? } });

var ppt = ppd.getTime();

For this method call, thisArg inside the invoke trap will be bound to ppd, not the the real wrapped Date object.

In any case, what is the following supposed to do:

Date.prototype.getTime.call( new Proxy(new Date(), handler) )

This will still crash with an "error: not a Date" exception. Even when using a ForwardingHandler.

I imagine we would want this to work too. Is this going through the invoke trap as well? The question stands for all new "class-specific" (Map, Set, etc.) methods that would be dot-called with a proxy as first argument.

dot-called methods will not trigger the "invoke" trap.

If the problem being solved is making Date (and Map, Set, etc) built-in methods works on wrapped objects, I feel the invoke trap is only a partial solution.

It is indeed. Combined with the ForwardingHandler, the invoke() trap allows us to transparently forward |pd.getTime()| but not |Date.prototype.getTime.call(pd).|

I intuit (but have no proof) that for Date.prototype.getTime.call to work on proxies, there is a need for class-specific traps.

  • Object properties have specific traps to intermediate access (has/get/set/defineProperty/**keys, etc.),
  • [[Prototype]] has specific traps to intermediate access (get/setPrototypeOf)
  • [[Extensible]] has a specific trap to intermediate access (preventExtensions)
  • [[Call]] has a specific trap to intermediate access (apply)
  • [[Construct]] has a specific trap to intermediate access (construct)
  • Some other things that are somewhat internal (@@iterator, @@class, etc.) are exposed as symbols and are mediated via object properties-related trap

The pattern I see is that a good share of object internal properties (ES5.1 - 8.6.2 - Table 8 and Table 9) have traps dedicated to mediate access.

The case of functions is interesting. There are traps that are only relevant when the target is a function. In that regard, adding traps that are specific to accessing the Date internal [[Time]] value or specific traps to mediate access to [[MapData]] isn't any different than the current specific traps that mediate [[Call]] or [[Construct]]

I think your analysis is mostly right: if we want to make e.g. Date.prototype.getTime.call(proxy) work, then the easiest way to fit that into the current design would be to add Date-specific traps (and Map-specific, Set-specific, Regexp-specific, etc. traps)

The question is whether there's a strong need for intercepting these operations. It implies a pretty strong growth of the handler API. And it's not sufficiently general-purpose to also work for exotics that are defined outside of ES6 (e.g. DOM objects). For full transparency across isolated object graphs, I think membranes are still the way to go.

Cheers, Tom

[1] harmony:virtual_object_api