Begin forwarded message:
> From: Allen Wirfs-Brock <[email protected]> > Date: November 19, 2012 2:11:52 PM PST > To: Tom Van Cutsem <[email protected]> > Cc: "Mark S. Miller" <[email protected]>, Jason Orendorff > <[email protected]> > Subject: Re: possible excessive proxy invariants for Object.keys/etc?? > > > On Nov 19, 2012, at 10:04 AM, Tom Van Cutsem wrote: > >> Hi Allen, >> >> 2012/11/18 Allen Wirfs-Brock <[email protected]> >> The proxy spec.for Object.getOwnPropertyNames/kets/etc. seem to be doing >> quite a bit more than this. They >> >> 1) always copy the array returned from the trap? Why is this necessary? >> Sure the author of a trap should probably always return a fresh object but >> not doing so doesn't violate the integrity of the frozen/sealed invariants? >> In most cases they will provide a fresh object and copying adds unnecessary >> work that is proportional to the number of names to every such call. >> >> The copying is to ensure: >> a) that the result is an Array > > Why is Array-ness essential? It was what ES5 specified, but ES5 had a fixed > implementations of Object.getOwnPropertyNames/keys so that requirement was > simply a reflection of what the spec's algorithms actually did. Most places > in the specification that consume an "array" only require array-like-ness, > not an actual array. Now that we're make the implementation of the > getOPN/keys extensible, it would be natural to relax the requirement that > they produce a [[Class]]==="Array" object. > >> b) that all the elements of the result are Strings > > And presumably Symbols. We have to also accommodate Symbols, at least for > getOwnPropetyNames. Regardless, why is this important? More below... > >> c) to ensure the stability of the result. >> >> You can think of a + b as implementing a type coercion of the trap result to >> "Array of String". This coercion is not too dissimilar from what the >> getOwnPropertyDescriptor has to do (normalization of the returned property >> descriptor by creating a fresh copy). > > Yes, premature type coercion, in my opinion. Also, a classic performance > mistakes made by dynamic language programmers: unnecessary coercion of > values that are never going to be access or redundant coercion checks of > values that are already of the property type. Why is it important to do such > checks on values that are are just passing through these traps. When and if > somebody actually gets arounds to using one of the elements that are returned > as a property key they will be automatically coerced to a valid value. Why > is it important that it happens any sooner than that. > > getOwnPropertyDescriptor is also on my list to talk to you about for similar > reasons. I don't see why the property descriptor needs to be normalized and > regenerated (including converting accessor properties on the descriptor into > data properties and requiring the [[Prototype]] to be object. I have an > alternative for [[GetOwnProperty]]/TrapGetOwnPropertyDescriptor that > preserves the original object returned from the trap (while normalizing to a > descriptor record for any internal calls on ordinary objects). This > preserves any exotic property descriptor object properties as is. This seems > like exactly the right things. Such exotic property descriptor properties > can only be meaningfully used by other proxy traps or application/library > code that knows about them. I don't see any need to normalize them into data > properties or otherwise muck with what the actually implementor of the > getOwnPropertyDescrptor trap choose to return. > > I this alternative implementation is, again, much simpler (and efficient) > with fewer externally observable calls in its algorithm steps. I sure you > will want to review it so I will include it in the spec. draft. > >> >> c) on the other hand is crucial for the >> non-configurability/non-extensibility checks mentioned below. It's no use >> checking some invariants on a data structure if that data structure can >> later still be mutated. > I don't see how an extra copy of the the result array makes any difference in > this regard (also see below). The array returned from > Object.getOwnPropertyNames is still mutable even after you make a copy. It's > only as stable as any other non-frozen object you are likely to encounter. > > >> >> If we don't care about any of a, b and c, then the result array wouldn't >> need to be copied. > yes, I believe that should be the case. > >> >> 2) ensuring that the list of property keys contains no duplicates. Why is >> this essential? Again, I don't see what it has to do with the integrity of >> the frozen/sealed invariants. It is extra and probably unnecessary work >> that is at least proportional to the number of names). >> >> We've been going back and forth over whether or not we wanted to prevent >> duplicates. I remember Andreas Gal being concerned about these kinds of >> issues (that was when he was doing the first Firefox prototype for old >> proxies, in the context of the enumerate() trap, which was called during a >> live for-in loop. IIRC, Firefox already did de-dupe checks, as properties >> already enumerated in a child object should not be re-visited when visiting >> a parent object) >> >> More recently, at the last TC39 meeting in Boston, we decided to change the >> return type of the enumerate() trap from Array[String] to Iterator, and in >> doing so waiving the duplicate properties check. Quoting from the "Open >> issues" section of >> <http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies#open_issues>: >> >> "Enumerate trap signature: consider making the enumerate() trap return an >> iterator rather than an array of strings. To retain the benefits of an >> iterator (no need to store collection in memory), we might need to waive the >> duplicate properties check. Resolution: accepted (duplicate properties check >> is waived in favor of iterator return type)" >> >> I guess if duplicate properties are not crucial for enumeration, they're >> also not crucial for Object.getOwnPropertyNames and Object.keys and can be >> dropped. Mark, can you comment? > > good >> >> 3) Every name in the list returned by the trap code is looked up on the >> target to determine whether or not it exists, even if the target is >> extensible. Each of those lookup is observable (the target might itself be >> a proxy) so, according to the algorithm they all must be performed. >> >> This is where we get into actual checks required to enforce >> non-configurability/non-extensibility. >> >> Granted, the ES5 spec is not clear about the invariants on >> getOwnPropertyNames and keys. The currently specified invariants are a >> common-sense extrapolation of the existing invariants to cover these >> operations. > > Yes, but if they aren't essential then we have to take into account the cost > of doing non-essential invariant checks. These costs are both the actual > runtime cost of doing the checks, the constraints upon implementation imposed > by the need for complete and properly sequenced occurrence of all potentially > externally observable algorithm steps, and finally (and least important) the > added complexity of the specification which makes it harder to actually > achieve interoperable implementations. > >> >> In practice, it determines the degree of confidence that a programmer can >> have in Object.getOwnPropertyNames and friends when dealing with a frozen >> object. If we waive these invariant checks, then the result of those >> operations can never be trusted on to reliably introspect on a frozen >> object's list of property names: >> >> Object.isFrozen(proxy) // true >> Object.getOwnPropertyNames(proxy) // ['foo'] >> Object.getOwnPropertyNames(proxy) // [ ] >> >> Here, the 'foo' property apparently disappeared on a frozen object. > > Yes, but copying the result an extra time doesn't ensure that two separate > calls produce the same result. > >> >> If neither the for-in loop nor Object.getOwnPropertyNames nor Object.keys >> can reliably report an object's own properties, then we've made it >> impossible to reliably traverse and inspect a presumably deep-frozen object >> graph. > > If that is the goal, that maybe what you need is a new operation that cannot > be trapped. Your algorithm is essentially doing all the work, and more, > necessary to generate an accurate report of the actual non-configurable > properties of the target. If that is what is needed for your deep frozen > traversals, add that operation. That seems better than allowing the Proxy > trap to do the work to report anything it wants, but then coming in after the > fact and checking whether what was report matches what you demand. In this > case, it seems preferable to just provide the results you demand without any > handler intervention. > > This seems to harken back to the original proxy design that essentially > turned frozen objects into regular objects with no handlers. If that is what > you need, you should provide access to the results you need for the > situations where you need them. > > But you should not be adding this overhead for Proxy uses that don't have > those requirements. > > >> >> 4) Every own property of the target, is observably looked up (possibly a >> second time) even if the object is extensible and has no non-configurable >> properties. >> >> We may be able to remove the redundancy of two lookups by restructuring the >> algorithm. >> There previously was some redundancy in other checks as well. >> >> It isn't clear to me if any of this work is really necessary to ensure >> integrity. After all, what can you do with any of these names other than >> use them as the property key argument to some other trap/internal method >> such as [[SetP]], [[DefineOwnProperty]], etc. Called on a proxy, those >> fundamental operations are going to enforce the integrity invariants of the >> actual properties involved so the get name checks doesn't really seem to be >> adding anything essential. >> >> Perhaps we can just get rid of all the above checking. It seems like a good >> idea to me. >> >> Alternatively, it suggests that a [[GetNonConfigurablePropertyNames]] >> internal method/trap would be a useful call to have as the integrity >> invariants only care about non-configurable properties. That would >> significantly limit the work in the case where there are none and limit the >> observable trap calls to only the non-configurable properties. >> >> That would be one way to speed things up. >> >> I share your concern that the current spec algorithm may be a bit too >> restrictive for implementations to allow optimizations. It's very >> imperative. We should ask the spec implementors to have a look at the draft >> spec and share their concerns. > > Maybe we should move this discussion to es-discuss. What do you think? > > Allen >> >> Cheers, >> Tom >
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

