The ES6 MOP (Was: New ES6 draft now available)

Allen Wirfs-Brock allen at wirfs-brock.com
Sat Nov 24 10:50:50 PST 2012


On Nov 24, 2012, at 9:54 AM, David Bruant wrote:

> Le 24/11/2012 18:10, Allen Wirfs-Brock a écrit :
>>> * [[Enumerate]], [[Keys]] and [[OwnPropertyKeys]] are very close operations
>>> * So are [[PreventExtensions]]/[[Freeze]]/[[Seal]] on one side and [[IsExtensible]]/[[IsFrozen]]/[[IsSealed]]
>>> 
>>> I'm afraid that making them distinct operations increases footgun-ness. [[HasProperty]] has been removed in favored of [[HasOwnProperty]] (which might be removed in favor of only keeping [[GetOwnProperty]], though the conclusion of the discussion was the keep both IIRC) because the former could be "robustly" composed between 
>> 
>> Yes, I agree.  In particular I think [[Enumerate]], [[Keys]], and [[OwnPropertyKeys]] should be reduced to a single parameterized trap. Multiple traps make it harder to create an internally consistent MOP provider (eg, Proxy handler).
>> 
>> (...)
>> 
>>> 
>>> Here is an idea to uniformize the enumeration story while removing enumeration inconsistency footgun. I'll describe it in proxy trap terms. A unique trap (or internal operation)
>>> keyEnumerate: () -> iterator for {propertyKey, enumerable}
>>> There is this unique operation which returns an iterator not for property keys, but for an object containing a property key and a boolean signifying the enumerable boolean.
>>> Using this, each userland operation would use this iterator, drain it out (unless the iteration is being aborted like by throwing in a for-in loop) and filter out based on the exact userland operation so that:
>>> * All would filter out if propertyKey is ever a private symbol
>>> * Object.getOwnPropertyNames would keep all other property keys regardless of the enumerable value
>>> * Object.keys would filter out properties which are described as non-enumerable
>>> * for-in would filter out non-enumerable and retrieve the different keyEnumerate from the prototype chain objects.
>>> With this unique internal operation, an object is able to communicate its intent regarding what's enumerated and each enumeration userland operation only keeps what it's interested in.
>> 
>> Yes, something like this.  My inclination would be to add a hint parameter indicating one of the currently known variations. I think it is justified as enabling the "handler" to optimize its internal work of collecting the set of keys.
> I'm afraid an hint parameter has the same downside than having several traps: offering the possibility to make different code paths for different userland enumeration operations. This very possibility is the footgun in my opinion.
> I felt satisfied with the [[keyEnumerate]] internal operation, because as a trap, people will have to return an iterator that iterates over everything and the engine (not the trap) makes the     call of what is to be actually enumerated by the different variations.
> Not letting the trap know how the result will be used is the best way to prevent people from writing erroneous traps in my opinion.

I think it is a much smaller footgun than having 3 independent traps that don't have to be provided as a unit.  If you have a single well specified trap, I expect an trap implementor to know or read the trap specification and see that there is a parameter that must be dealt with in a valid implementation.  In practice, they are likely to handle all the variations in a single loop rather than having three separate loops and possibly deviant loop implementations in three separate traps. 

Also, implementations are going to want to optimize and always produce the complete inherited set is going to be observable (via proxy traps as the proto chain is climbed).  Also note that the behavior of these traps for ordinary objects will be fully defined so that means that just getting "own property names" necessarily means that all proto and probably inherited properties must be observably visited and this even applies to code doing Proxy invariant checking that might need to access the "own property names" list.

I think a single operation with a filter "hint" is a good trap off between trying to minimize footgunness and allowing implementations (including via Proxy traps) to apply reasonable optimizations.

> 
> 
>>> 
>>>> • Defined all of the functions in the @Reflect module corresponding to MOP internal operations.
>>> IIRC __proto__ has been agreed to be an data property and there was no Reflect.setPrototypeOf operation in the harmony Reflect module proposal on purpose so that 'delete Object.prototype.__proto__' at any point would be enough to be sure that all [[Prototype]]-setting capabilities are removed.
>>> So I think the Reflect.setPrototypeOf  should be removed.
>> 
>> Do you want to be able to set __proto__ on DOM objects and other exotic objects?
> I personally don't, but for sure others do.

and it current happens....

> I'm happy with the static inheritance mechanism we will soon have and cover the vast majority of current __proto__ use cases.
> Adding Reflect.setPrototypeOf has exactly the same issues than an extractable __proto__ setter. See May meeting notes [1]: 
> "DH: I can predict the security bugs: the implementor just thinks about the normal case, but the attacker takes the accessor out, installs it on an object that inherits from a proxy to an object from another global etc. etc. and something internal breaks
> MM: that's the most compelling argument I've heard. the additional testing surface area is much bigger"
> 
> __proto__ really is in ES6 as a de facto standard. I'm not sure it is a good enough reason to make setting the [[Prototype]] a first citizen of the language.

"in for a penny..."  If it isn't part of the MOP then implementors will just by-pass the the MOP for situations like the DOM and do it anyway. In that case, who knows if they will respect these conventions.   If we are standardizing the feature we might as well understand it.  If we want to make it a disable-able  feature (per realm?? per something else??) we can probably do that.  But just leaving it as an idiom of manipulating Object.prototype (what if O.p is frozen before you the a chance to delete it?) seems like we are still trying to pretend that dynamic prototype mutation isn't a real feature of the language.

> 
> 
>> If so, it needs to be part of the MOP.  "In for a penny, in for a pound". If we are going to make __proto__ part of the language than we have to accommodate just like any other feature.  If you want to be able to disable it make deletion of Reflect.setPrototypeOf be the switch.  Or define a new function for doing it.  But I don't think we should be bending the MOP to accommodate idioms like deleting __proto__..  Remember, the first point above.  [[SetInheritance]] is a generic operation, only some objects implement via a [[Prototype]] internal data property.
> In practice, it's all of them except in platforms with no __proto__, no?

The specifications I wrote for Symbol and Proxy objects doen't have any [[Prototype]] state. Some MOP handler providers (not the current Proxy handlers) might have other kinds of inheritance state.  Also, note that even with the current Proxy semantics, [[GetP]]/[[SetP]] are required to use the target's [[Prototype]] (if it has one ;-) for inheritance lookup.  such proxies can have completely different internal state that they use for their inheritance model.

> Even proxies have no way to override [[SetInheritance]].
> Actually, if Reflect.setPrototypeOf is added, a setPrototypeOf handler trap needs to be added as well. Some previous discussions about __proto__ and proxies occurred at the July meeting [2].

Absolutely, If you need to trap on GetPrototypeOf (even just for notification) you will also need one for SetPrototypeOf (eg, assigning to __proto__).  Same logic applies to both.  Proxy's may impose restrictions on both of them but you still will want the notification.  Non-proxy implementations of the MOP.

I see that [2] call for filtering __proto__ access in Get/Put handlers.   I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation.  Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.


> 
> David
> 
> [1] https://mail.mozilla.org/pipermail/es-discuss/2012-May/022834.html
> [2] http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies#discussed_during_tc39_july_2012_meeting_microsoft_redmond

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121124/82d167fd/attachment.html>


More information about the es-discuss mailing list