On Jan 3, 2013, at 12:18 AM, Tom Van Cutsem wrote:
> 2013/1/3 Allen Wirfs-Brock <[email protected]>
>
> 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.
You seem to be assuming that all exotic properties must be an incremental
variation of a data property or an accessor property. However, historically
this hasn't been the case. When accessor attributes were added to ES they
weren't just a incremental extension to the attributes of a data property. If
any new property forms are added to ES in the future, they probably also won't
be simply extended variations of data or accessor properties. I would hope
that proxies could be used to prototype any such future extensions.
As part of a general extension mechanism, I don't think we should unnecessarily
limit how a proxy handler chooses to define the interpretation of its property
descriptors. It's clear to me that such limitations restrict the utility of
proxies. What benefit to we get from those limitations.
>
> 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.
Since we never fully defined the invariants of Object.getOwnPropertyDescriptor
it isn't clear what assumptions existing clients should be making. I think the
most basic assumption that is justified is that the result of gOPD can be used
with Object.defineProperty to define a similar property on a different object
that is the same kind of object as the original.
Sure, a simple forwarding proxy to an ordinary object might break existing code
if it didn't return complete property descriptors. That's a user level bug in
the definition of the Proxy and can be found and corrected using user level
testing and debugging techniques. I don't think we should be adding additional
runtime time overhead to every proxy [[GetOwnProperty]] call to protect against
such bugs. Such checks penalizes correct code and the restrictions they impose
limits the utility of the proxies.
I believe we should only be dynamically validating invariants that are
essential for the low level integrity and robustness or the execution
environment. Everything else should be treated as as a programmer bugs.
>
> 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".
Or for mitigating just this specific integrity hole, as I described below.
>
> 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.
Layers of abstraction...
defineProperty and its internal helpers already have to deal with the fact the
descriptor objects may contain inherited attribute properties or that they may
be accessor properties. So, while we might restrict what is returned by Proxy
[[GetOwnPropety]] we still couldn't restrict what is passed to Proxy
[[DefineOwnProperty]].
While I think it is unnecessary (and restrictive) I could probably live with
alternative 3 that I proposed in my last message even with the addition that
the object produced by the handler must be an ordinary object.
>> 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.
Except that transparency probably isn't appropriate in situations where the
client is highly defensive. Do we currently have a isProxy test? It seems
that it would be essential in such situations.
Does any such code actually exist. Does it depend upon anything other than the
"configurable" property? Does such code, if it exists, work correctly with DOM
objects in all implementations?
>
> 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.
Consider me as representing the programmers whose primarily interest in Proxies
is for extending ES via metaprogramming. From that perspective I think of
O.gOPD (along with Object.defineProperty) as the primary user facing APIs that
allow regular programmers to configure a wide range of exotic objects that I
might invent. I don't want to be restricted to making all my properties look
like some variant of existing data properties or accessor properties. Nor do I
want to arbitrary transformations made to property descriptor I may choose to
define. I'm perfectly happy for place into the hands of security-conscious
coder the tools they need to detect and even reject my exotic objects. If my
uses are incompatible with your high integrity code, I'll just run my code
somewhere else. I want to be sure that you are enabled to create what ever
short of high integrity environment you want but please don't place
restrictions on what I can do when I'm not in your environment
>>
>> 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).
Code that for-ins and switch dispatches over a property descriptor will see
extra attributes.
>
> 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.
I'm concerned about two things:
1) copying is expensive and will almost always be unnecessary
2) You are assuming that you can meaningfully turn my exotic properties into
either data descriptors or accessor descriptors and that when you hand them
back to me I won't care. Here's an example where I would care. I add a kind
of exotic method property whose descriptor has a "method" property. Methods
are never enumerable or writable. I want to throw an error with somebody codes:
Object.defineProperty(pObj, "myMethod", {enumerable: true, writable:
true,method:func, configurable: true}); //this throws because enumerable or
writable methods aren't allowed
Instead they should have said:
Object.defineProperty(pObj, "myMethod", {method:func, configurable: true});
If you normalize as described in the wiki, then this will fail:
Object.defineProperty(pObj2,"myMethod",
Object.getOwnPropertyDescriptor(pObj, "myMethod"));
>
> 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.
Perhaps, but I fine with using objects. For example, some people have made
reasonable use of property descriptor objects that are populated via
inheritance.
>>
>> 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.
see above
>>
>> 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.
I'd guess that there probably isn't much or any such code in the wild. If we
can determine that then perhaps we don't really have a significant issue here.
>
> 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.
Again, I don't see why this is an issue. It is either a case of the Proxy
handler implementor is intentionally redefining what "complete" means for their
exotic properties or it is a run of the milll bug in the handler. I don't see
why such bugs is of much concern to us since missing attributes default to
their "high integrity" state when used to define a new ordinary object property.
>
> 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?
My experience as a language runtime implementor is that all non-essential
validity check at low levels of a runtime are undesirable. You simply never
know which language features are going to be used by someone in some
performance critical situation so it is good practice to push any non-essential
checks to higher levels where they can be avoid.
I wouldn't want to be in the position where some part of the DOM or other
interesting native library can't, in practice, be self hosted because of
performance reasons that trace to unnecessary copying of descriptor objects. I
don't know that it will occur, but my experience suggests that it could.
To wrap up, I think the record-like invariant check would be acceptable as we
are already inspecting the descriptor object to build the corresponding PD
record. I'm strongly \ opposed to any mandated object copying or normalization
of exotic descriptor objects to include missing data/accessor property
attributes.
Allen
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss