Property descriptor normalization (Was: General comments response (was Re: ES6 Rev13 Review: MOP-refactoring, symbols, proxies, Reflect module))

Allen Wirfs-Brock allen at wirfs-brock.com
Wed Jan 2 15:34:51 PST 2013


On Dec 31, 2012, at 3:37 AM, Tom Van Cutsem wrote:

> 2012/12/30 Allen Wirfs-Brock <allen at wirfs-brock.com>
> 
> On Dec 29, 2012, at 2:37 PM, Tom Van Cutsem wrote:
>  > * I'm a bit uncomfortable with the removal of property descriptor normalization in the getOwnPropertyDescriptor/defineProperty traps. Especially for getOwnPropertyDescriptor I think it's a breaking change w.r.t. ES5.1.
> [...]
> This permits things like:
> 
> Object.defineOwnProperty(pObj1,"foo", {method: func});  //define property on a proxy-based object, that have "method" properties
> console.log (Object.getOwnPropertyDescriptor(pObj1,"foo").method);    //we can retrieve the value of the method attribute (if the proxy supports it)
> 
> Object.defineOwnProperty(pObj2,"foo",Object.getOwnProperty(pObj1, "foo"));  //copy a method properry from pObj1 to pObj2
> 
> If descriptor object with extended attributes is applied to an ordinary object, it is always first internally converted to a PD record.  PD records only contain fields for the ordinary attributes, and any operations upon ordinary objects will have no visibility  of  the extended attributes.
> 
> Yes, I agree to all of this and I understand this is the intent of the new [[Origin]] field of internal property descriptors.
> I also agree there's no problem for normal objects, which continue to always cons a fresh property descriptor object.
>  
> The only breaking change (relative to ES 5.1) possibility I see must start with the assumption that  ES5.1 property attributes are the final definition of property descriptor objects and that additional property attributes can never be added to the language (by the spec., not just via proxies) using any of the pre-existing ES5.1 APIs.  That seems quite unreasonable and was certainly not the intent when we introduced the reflection API into ES5. [...]
> 
> This was not the breaking change I had in mind and I agree with you that adding new attributes is both useful and supported by the ES5.1 design.
> 
> Here's the breaking change I had in mind:
> 
> var propDescMap = {};
> var proxy = Proxy({}, {
>   defineProperty(target, name, desc) { propDescMap[name] = desc; return true; },
>   getOwnPropertyDescriptor(target, name) { return propDescMap[name]; }
> };
> 
> // client1 adds:
> var pd1 = {
>   get configurable() { return Math.random() < 0.5; }, // return true or false, randomly
>   custom: true
> };
> Object.defineProperty(proxy, "foo", pd1);
> 
> // client2 queries:
> var pd2 = Object.getOwnPropertyDescriptor(proxy, "foo");
> 
> If I understand correctly, pd2 now refers to exactly the same object as pd1, preserving the custom attribute.

Note that you don't need to start with the defineProperty call using pd1 to get the following effects.  The same observable behavior could occur just by defining the getOwnPropertyDescriptor trap to directly construct and return a similar value for pd2.

> 
> However, this semantics also implies that:
> 1) pd2 is not a completed property descriptor, lacking a writable/enumerable attribute. Only the internal property descriptor was fully completed, not the [[Origin]] object.

We can't say what "complete" means for a property descriptor containing custom attributes.  Just like the complete PD for an ordinary accessor property doesn't contain a "writable" property, perhaps a PD for an exotic "custom" property only have "configurable" and "custom" attributes. This is all up to the designer of the exotic property and something they should define when they describe the contract of their exotic object.

Yes, the internal PD record will be completed but it is only used for a sequence of pseudo-code algorithm steps.  I don't currently see any place where the internal PD record is used in such a way that we have any problems.  Of course, the proxy itself should provide consistent Get/Set/defineProperty/getOwnPropertyDescriptor behavior for such exotic properties. If it doesn't its a ES programmer level bug.

> 2) pd2.configurable is not a data property, but may change randomly

So?  That's the nature of accessor properties although it's not how they are normally used.  Why are these values being unstable any worse than any others?

The one thingI can imagine is that a proxy author could use an "configurable" attribute accessor to sneak past the configurable invariant check in Proxy [[GetOwnProperty]]. I can imagine that we might want to normalize the "configurable" property (only for the situation where their is a corresponding target property??) of the descriptor object, but no other properties.

> 3) since pd1/pd2 refers to a mutable object, changes made by client1 will be visible to client2 and vice-versa.

Again, this is the nature of programming with object references. If the Proxy wants to prevent this, it shouldn't store the descriptor objects -- just like the spec. doesn't store the PD record that is used to create a property.  If client2 is worried about this and doesn't trust the Proxy (in which case it probably shouldn't be using it...)  in can always do its own cloning/normalization.  Regardless, the values of pd2 can't asynchronously change out from under client2.  client2 is in control and presumably should be aware of the possible side-effects of anything it calls.

Basically, I'm saying I don't see why the "hazards" of an accessor are any greater here than in any other random ES code.

> 
> None of these behaviors are possible in ES5.1, so clients of Object.getOwnPropertyDescriptor currently don't (need to) guard against these.

The change is the possible occurrence of exotic properties.  Regardless of any normalization of ES5 ordinary property attributes, existing code is not going to be prepared to handle exotic properties in any specific way. Even for new code where the client knows it could potential be accessing a proxy provided descriptor object, I don't know that it would be worth guarding against these things.  Any object returned from any function may potentially be buggy and violate the assumed contract of the function.  Most code doesn't and should actively defend itself against such potential bugs.

> 
> The issue is that in 8.5.6 Proxy [[GetOwnProperty]], only the "resultDesc" is normalized, not the trapResultObj that is its [[Origin]]. The FromPropertyDescriptor operation blindly returns the [[Origin]], disregarding the normalized descriptor.

Yes, because I don't know what it means to "normalize" an arbitrary exotic property descriptor.  The primary purpose of such exotic descriptors is to convey from the Proxy handler to the client information about an exotic object and its exotic properties.  It is also used to by the client to pass exotic property attributes back to a handler.  Arbitrarily normalizing  an exotic descriptor produced by a getOwnPropertyDescriptor trap might result in turning into something that would be treated as an invalid exotic descriptor when passed back to a corresponding defineProperty trap.

> 
> To rescue the [[Origin]] design, the most straightforward fix I can come up with is that FromPropertyDescriptor first normalizes the [[Origin]] object before returning it (i.e. verifying that it is complete, or making it complete, and ensuring the standard attributes are data properties). Even then so, it's messy that these side-effects are visible to end-user code (the pd1 object of client1 would get mutated-at-a-distance as a result of returning it from a getOwnPropertyDescriptor trap).

Here is a list of alternative, I can envision, ordered (from my perspective) in decreasing desirability:

1) Provide a Reflect.normalizePD(desc) function that produces a new descriptor object via ToPropertyDescriptor(CompletePropertyDescriptor(FromPropertyDesriptor(desc),undefined)).

     Anybody who is paranoid about encountering exotic descriptors, malformed descriptors, or accessor properties can use it.

2) Normalized only the "configurable" property.  Make it an invariant that "configurable" is always present and is a data property.  Either normalize it to that or throw as an invariant violation like for other proxy invariants.

    This seems like the only property attribute whose setting is important for the integrity use cases, so like elsewhere in the proxy design only enforce that single invariant. 

3) Make it an invariant that all own properties of the returned data property are data properties.

    This gets rid of the accessor hazard.  However this puts additional extra checking into the proxy [[GetOwnProperty]] that is almost always going to yield a negative result.  In other words, you a placing a performance tax on the usual case with little usual benefit.  Unless there is real integrity hazard that can't be explicitly mitigated where it matters, we should just treat such situations like misuse of accessors as user level bugs.

Allen

> 
> The only other alternative I see is to create normalized copies, as specified in the draft proxy spec on the wiki.
> 
> Cheers,
> Tom

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130102/7d99292a/attachment-0001.html>


More information about the es-discuss mailing list