On Dec 31, 2012, at 3:37 AM, Tom Van Cutsem wrote:
> 2012/12/30 Allen Wirfs-Brock <[email protected]>
>
> 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
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss