Do we really need the [[HasOwnProperty]] internal method and hasOwn trap

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Nov 12 09:58:41 PST 2012


On Nov 12, 2012, at 1:52 AM, Tom Van Cutsem wrote:

> 2012/11/12 Allen Wirfs-Brock <allen at wirfs-brock.com>
> It isn't clear to me why the [[HasOwnProperty]] internal method and the hasOwn Proxy trap need to exist as object behavior extension points.
> 
> [...]
> let p = new Proxy({}, {
>    hasOwn(target,key) {return key === 'foo' ? false: Reflect.hasOwn(target,key)}
>    has(target,key) {return key === 'foo' ? true: Reflect.has(target,key)}
> });
> 
> console.log(Reflect.hasOwn(p,"foo"));  //will display: false
> console.log(Reflect.has(p,"foo"));          //will display: true
> 
> I think you intended to swap the outcomes: the above is perfectly consistent with a normal object that inherits a "foo" property. The more awkward behavior would be an object that says it does have a "foo" own property, but no "foo" own or inherited property.

right, I flipped the example... if hasOwn(k) is true, then has(k) should also be true.


>  
> Given,that [[HasOwnProperty]] is not really essential, has good alternatives,  and exposes the potential for such inconsistency I think we should just eliminate it.  Does anyone want to argue that we need to keep it?
> 
> I would argue to keep it, on the general principle that all "derived" traps (like "hasOwn") enable a more "efficient" virtualization (concretely: they avoid unnecessary object allocations). In this particular case, the alternative solution would allocate a property descriptor only to test whether it's not undefined.

Yes, but we are defining the MOP that is necessary to support the ES language, it doesn't have to do much else.  A this point in time, there are no core features of the language that makes independent use of  [[HasOwnProperty]]/hasOwn.  The only situation currently using [[HasOwnProperty]] where replacing it with [[GetOwnProperty]] would result in allocation of a property descriptor is in the implementation of O.p.hasOwnProperty.  A engine's implementation of that built-in could easily short-circut that allocation for  ordinary objects.

Another approach would be to change the shape of the API and provide a single trap that is parameterized to determine whether the query was restricted to own properties:

[[HasProperty]](P,  onlyOwn)
has:    function(target, name, onlyOwn) -> boolean

If onlyOwn is true, return true if P/name is the key of an existing own property.
If onlyOwn is false, return true if P/name is the key of an existing own or inherited property

This sort of API may seem less elegant, but it eliminates the footgun of forgetting to handle has when a proxy has over-ridden hasOwn (or visa versa).

In particular, I'm thinking about this in the context of the Proxy implementation which automatically delegates to the target object if the handler does not  have method for a corresponding trap.   Someone might reasonably expect that handling hasOwn is sufficient because they assume that the default implementation of has will call back to the hasOwn they provide.   However, the target's [[HasProperty]] (assume for this discussion that the target is an ordinary object) will call the target's [[HasOwnProperty]] not the original proxy object's handed hasOwn handler method.  

> We could of course revisit this principle, but that would argue in favor of abandoning all the derived traps, as they can be dealt with similarly (see <http://wiki.ecmascript.org/doku.php?id=harmony:virtual_object_api>).

Yes, I just started this discussion with [[HasProperty]]/[[HasOwnProperty]] because it is the simplest case.  There are actually much worse possible inconsistencies (for example, [[GetOwnProperty]] and [[GetP]] exposing different values for the same own property).

I think the root issue relates to polymorphic dispatch of the internal methods and the transparent forwarding of unhanded traps to the Proxy target object.

Every-time the spec. calls an internal method it is implicitly doing a polymorphic dispatch using that object's "handler"  as the "type". (we can think of ordinary objects as having a intrinsic handler that dispatches to the ordinary implementation of the internal methods).  The specification's implementation of the ordinary internal methods generally assumes that the "this object" that the internal method is operating upon is the object that did the original polymorphic dispatch (remember, no inheritance of internal methods).  This means that within those internal method algorithms it can be assume that a self-calll to an internal method will dispatch to the same ordinary handler.  [[HasProperty]] can self-call [[HasOwnProperty]] safely assuming that it will be using the same two coordinated implementation. 

However, if the ordinary [[HasProperty]] is being evaluated on a Proxy target because it was forwarded by the Proxy [[HasProperty]], its "this object" is the target, not the proxy and so its call to [[HasOwnProperty]] dispatches via the target and not the proxy.  We get an answer that is consistent, relative to the target object but not consistent relative to the proxy-based object that [[HasProperty]] was originally invoked upon. What we have is a situation that is very similar to that which requires the inclusion of a receiver argument in [[GetP]]/[[SetP]] calls, but along a different dimension.  

> 
> Or we could make ad hoc exceptions on the general principle. For instance you could argue that "hasOwn" is a much less common operation than "has" (i.e. the in-operator), such that we don't need a "hasOwn" trap but we do still need a "has" trap. I'm not a fan of this option though.

or, as described above, combine them into a single trap.  It would solve this case but necessarily others.

> 
> (of course, this isn't the only situation where a proxy can be defined that exhibits inconsistent responses for the internal methods.  I'm thinking about some others too, but this one seems fairly straightforward to eliminate).
> 
> This is a general issue with all derived traps. Derived traps avoid allocations, but always open the way to inconsistencies with the fundamental traps from which they are normally derived.
> 
> Note that the "invariant enforcement" technique still doesn't allow a proxy with non-config/non-extensibility invariants to lie about its own properties. There are post-condition assertions on both "getOwnPropertyDescriptor" and "hasOwn" that ensure this. More generally: as long as a proxy p doesn't answer "true" to Object.isFrozen(p), its behavior can be arbitrary. Only when some sensible invariants are established on the proxy's target is the proxy's behavior restrained.

Yes, and the concern is that most Proxy uses will not be in the context of  frozen object so in most cases none of the "invariant  enforcement" will not help with this problem.

I've never been a big fan of dynamic invariant enforcement for proxies and I'm still not.  I am concerned that the current interfaces and factoring of derived/fundamental along with the target forwarding semantics is a footgun that is going to led to difficult to identify bugs. For now, I can spec. the current factoring I think we should continue to look at it from this perspective and see if we can minimize the size of the footgun.  

Allen



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121112/0177f13a/attachment.html>


More information about the es-discuss mailing list