possible excessive proxy invariants for Object.keys/etc??

Allen Wirfs-Brock allen at wirfs-brock.com
Thu Nov 22 09:24:22 PST 2012

On Nov 22, 2012, at 5:36 AM, Tom Van Cutsem wrote:

> 2012/11/21 Allen Wirfs-Brock <allen at wirfs-brock.com>
> On Nov 21, 2012, at 12:09 PM, Tom Van Cutsem wrote:
>> Let's first discuss whether there is an issue with just letting (unique) symbols show up in Object.{keys,getOwnPropertyNames}.
>> It's still a change in return type (from Array[String] to Array[String|Symbol]), but a far less innocuous one than changing Array[String] into "Any".
> Wow, I actually think that the Array[String|Symbol] change is far more likely to have actual compatibility impact.  I can imagine various scenarios where is program or library is doing string processing on the elements returned from keys/getOPN.  That seems far more likely, then that they have dependencies upon the returned collection being Array.isArray rather than just Array-like.
> If we don't do any invariant checks, the trap isn't even obliged to return an array-like. Code that expects an array-like is likely to immediately crash if this happens though, so I'm not sure if that's a real issue.

Just like all the generic array function.  Essentially all objects are array-like, you don't even need to have a "length" property.  Such "array-likes" generally act as if they had a length of 0.

Alternatively, what's the real difference to an application whether it "crashes"  (eg, throws TypeError) within the Proxy implementation of [[Keys]] or crashes when it touches the object returned from Object.keys? 


> A concrete example: say I'm implementing a membrane that wants to allow frozen "records" to pass through the membrane unwrapped.
> By "record", I mean an object with only data properties bound to primitives (strings, numbers, ...). Since individual strings and numbers can pass through unwrapped, it's not entirely unreasonable to allow compound values composed of only primitives to be passed through unwrapped as well.
> To pass such frozen records, the membrane needs to perform a check to see whether the frozen object is indeed a record.
> To do so, it must be able to iterate over all the properties of the object and test whether they are indeed data properties bound to primitives.
> If the frozen record is a proxy that can lie about its own properties, it can not report a particular "foo" property that is bound to a mutable object, thus opening a communications channel piercing the membrane.
> ...

> Hence my above question, is getOPN really one of these basic primitives?
> Without at least 1 operation that reliably lists an object's properties, you can't reliably introspect a frozen object.
>  ...

> It still appears to me that for Sealed/Frozen objects, which I believe are the ones you actually care about, you are enforcing that the list returned is exactly the own properties of the ultimate ordinary target object (perhaps with multiple levels of proxy indirection).  In otherwords, the result cannot be meaningfully changed.  In that case it doesn't need to be trap.
> You're right that the result cannot be meaningfully changed for frozen objects. But it still needs to be a trap for membranes. The issue is again that the membrane proxy needs to be notified of the operation occurring, so that it can update its target object before the operation proceeds. Like Brandon mentioned earlier in this thread, the trap is effectively just a notification callback at that point.

Then I think the thing to do is provide a [[GetNonConfigurablePropertyKeys]] internal method  that, for Proxy objects, only calls the trap as a notification and always returns the [[GetNonConfigurablePropertyKeys]] of the target object (which would eventually bottom out in an ordinary object.  Because your use cases require sealed/frozen objects the list returned is equivalent for those situations to the the own property list.

I'd also eliminate all of the integrity checking from [Keys]]/[[GetOwnPropertyNames]] because for your use cases they simply wouldn't be used.  But they still have utility for people implementing pure open virtual objects.  Eliminating the unnecessary integrity checks greatly simplifies them and should make them more efficient.

Finally, because sealed/frozen is so key to  your use cases, I'd move forward with my suggestion for formalizing the sealed/frozen as object states.  Rather that something that has to be deduced from [[Extensible]] and the [[Configurable]]/[[Writable]] attributes. It  may not be essential (implementation might internally optimize it that way, anyway) but it seems important enough that it would be better for the spec. to be explicit about this rather than leaving in to implementation to figure out that such an optimization is important.  Also, making the state explicit eliminates all the individually observable [[GetOwnProperty]] calls that are otherwise needed to deduce frozen/sealed (and which can't be optimized away because they are observable via traps).  BTW, the ordering of those call is currently unspecified because property name enumeration order is unspecified. 

So one new internal method [[GetNonConfigurablePropertyKeys]]  (although I'd really prefer to combine it, [[Keys]], and [[GetOwnPropertyNames]] into a single parameterized trap).  Also replace [[PreventExtensions]]/[[IsExtensible]] with [[SetIntegrity]](state)//[[GetIntegrity]] where state is one of "non-extensible", "sealed", "frozen" and only lower to higher transitions are allowed. Also, for proxies this would generate a "notify only" trap.  The Object.sealed/freeze/isSealed/isFrozen/preventExtrensions/isExtensible functions could then all be implemented in terms of those two traps.

What do you think?


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121122/5c1859d4/attachment.html>

More information about the es-discuss mailing list