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

Tom Van Cutsem tomvc.be at gmail.com
Thu Jan 3 00:18:42 PST 2013


2013/1/3 Allen Wirfs-Brock <allen at wirfs-brock.com>

>
> On Dec 31, 2012, at 3:37 AM, Tom Van Cutsem wrote:
>
> [...]
>
>
> 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.
>

I think it's perfectly obvious what it means for an exotic property to be
complete: that it is at least a complete data or accessor property (i.e. it
either has {value,writable,enumerable,configurable}, or it has
{get,set,enumerable,configurable} properties). It may further specify any
number of additional custom attributes.

More generally, I think it is backwards-compatible to extend property
descriptors with extra attributes, but it's backwards-incompatible to allow
proxies to remove some of the standard attributes from property
descriptors. This breaks the assumptions of existing clients.


> 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.
>

The use of the internal PD record is fine. My problem lies only with
Object.getOwnPropertyDescriptor(proxy,name) exposing a non-normalized
descriptor object.


>  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]].
>

It seems to me that alone would be reason enough to ban "accessor
attributes".


> 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.
>

I'm uncomfortable with this. It's already exceedingly difficult (for me at
least) to think about the correctness of the invariant checks. Having to
take into account that property descriptors may be proxies with arbitrary
behavior introduces further complexity.

> 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.
>

The scenario I'm worried about assumes that the Proxy is actively trying to
confuse a client, so trusting the Proxy to do the right thing is not an
option.


>  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.
>

Two observations:
1) We've been careful to make proxies transparent, and we consider it an
anti-pattern for clients to test whether they're using a Proxy.
2) Yes, the client defending itself by normalizing the descriptor
explicitly would help, but my point is that there is extent ES5.1 code that
does not currently do this because it's currently not necessary.


>  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.
>

Because existing code can assume that "desc.configurable", "desc.writable",
etc. are stable values, and changing these into accessors breaks those
assumptions.

I think of Object.getOwnPropertyDescriptor as the high-integrity,
high-fidelity part of the reflection API. This is the API that
security-conscious code should use when it manipulates untrusted objects.
It can only be a high-integrity reflection API if the return value can be
reliably depended upon.

>
> 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.
>

I don't see how existing ES5.1 code manipulating a property descriptor with
*extra* custom attributes would break, if it only ever touches the standard
attributes (which should be most of the code manipulating descriptors out
there).

In the draft Proxy spec on the wiki, custom attributes are simply copied
onto the normalized descriptor as part of the normalization process.
It's worth noting that they are always copied as data properties. I think
this makes for a good guideline: property descriptors are objects with just
data properties.

If ECMAScript would have had an actual record value type, I'm confident
property descriptors would have been reified as such. Since we only have
objects, descriptors were reified as objects, but that brings with it
features (mutability, behavior, ...) that seem to get in the way here.

>
> 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.
>

If we make it clear that property descriptors are records (i.e. contain
only data properties), and normalization entails:
1) ensure all standard attributes are present and refer to values of the
appropriate type
2) copy any custom properties

Then I think the contract is clear and custom descriptors will round-trip
fine.

>
> 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.
>

I would have agreed to this if it weren't for the fact that there is
probably ES5.1 code out there that already tries to be correct in the
presence of untrusted code, and doesn't know about or use this primitive. I
hope MarkM can point at some relevant sources.


> 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.
>

This still violates the ES5.1 assumption that all descriptors returned by
getOwnPropertyDescriptor are guaranteed to be complete.


> 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.
>

I won't deny there's a performance tax here, either by verifying that the
descriptor is really a "record" (i.e. has only data properties), or by
making an explicit copy. Like all other invariant checks, I agree they will
be wasteful for "benign" proxies.

Then again, let's place things in perspective: when will this become a perf
issue? When querying a large number of proxies for their own property
descriptors. But then any heap containing a large number of proxies
probably already has other perf issues to worry about?

Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130103/08d26e26/attachment-0001.html>


More information about the es-discuss mailing list