[Harmony Proxies] Proposal: Property fixing
Further clarification:
The traps that have a property name parameter are...
getOwnPropertyDescriptor hasOwn defineProperty delete getPropertyDescriptor has get set
The internal methods that these traps are invoked from could delegate to their standard object implementations if the property name they are passed represents a fixed property, and perform proxy logic otherwise.
It would be optimal to ensure that all the other traps ...
getOwnPropertyNames keys getPropertyNames enumerate iterate
... always include any fixed properties, but the associated validation logic would be inefficient, and a solution along the lines of just tacking the fixed properties on to the front is probably too inflexible, so it is probably leave those traps alone with respect to property fixing.
Implementation considerations:
Implementations could store fixed properties in an own properties table just as they would for regular objects. This table would not need to be created unless and until any properties are actually fixed. When a proxy's "fix" trap is invoked, if this table already exists, it just "becomes" the object's own property table, and then the properties in the "fix" trap return value can be added to this table as if via |Object.defineProperties|.
Bug fixes:
Object.defineProperty(x, "foo", {value: "bar", configurable: false, writable: false, enumerable: true};
... should have had a closing paren, and ...
// non-writable, but doesn't throw object.foo = "baz";
... should have been ...
// non-writable, but "set" trap still called x.foo = "baz";
Thanks, Sean Eagan
So in this proposal, once an individual property has been fixed, any access/assignment to it can no longer be intercepted? I'm not sure whether that's always beneficial. We are then taking away some power of proxies to ensure more consistency, but maybe this additional power is still needed in some wrapping scenarios. Consider a hypothetical scenario in which a funky host object ignores the "configurable" attribute. Proxies won't be able to patch it up anymore. (I have no idea how likely such scenarios actually are though)
Also, as you point out, full consistency across all traps is difficult to guarantee, so does it make sense to enforce consistency for some traps but not for others? The overall behavior of the proxy may still be inconsistent.
I do see benefit in this proposal as an aid for proxy writers to develop more well-behaved proxies. But for that purpose it seems that this proposal can be fully written as a Javascript library layered on top of the existing API.
Cheers, Tom
2011/5/5 Sean Eagan <seaneagan1 at gmail.com>
So in this proposal, once an individual property has been fixed, any access/assignment to it can no longer be intercepted? I'm not sure whether that's always beneficial. We are then taking away some power of proxies to ensure more consistency, but maybe this additional power is still needed in some wrapping scenarios. Consider a hypothetical scenario in which a funky host object ignores the "configurable" attribute. Proxies won't be able to patch it up anymore. (I have no idea how likely such scenarios actually are though)
ES5 section 8.6.2 does mention some constraints around host object's [[Configurable]] internal properties, but it doesn't explicitly forbid them from ignoring it from what I can tell.
Also, as you point out, full consistency across all traps is difficult to guarantee, so does it make sense to enforce consistency for some traps but not for others? The overall behavior of the proxy may still be inconsistent.
The only traps for which it would be difficult to enforce consistency are proxy level traps not specific to individual properties, so it might make sense to let these traps remain unconstrained anyways unless and until the proxy as a whole is fixed via the "fix" trap.
I do see benefit in this proposal as an aid for proxy writers to develop more well-behaved proxies. But for that purpose it seems that this proposal can be fully written as a Javascript library layered on top of the existing API.
But couldn't the same be said of the "fix" trap, frozen prototype, and frozen typeof for proxies ?
Also, since proxies are not allowed to advertise their properties as non-configurable, without something like this proposal proxy transparency cannot be achieved. With the current API, one could implement a "Proxy.isProxy" function as follows...
Proxy.isProxy = function(objectOrProxy) { let unlikelyPropertyName = "_ at *&%+!"; Object.defineProperty(objectOrProxy, unlikelyPropertyName, {}); return Object.getOwnPropertyDescriptor(objectOrProxy, unlikelyPropertyName).configurable; }
Thanks, Sean Eagan
I do see benefit in this proposal as an aid for proxy writers to develop more well-behaved proxies. But for that purpose it seems that this proposal can be fully written as a Javascript library layered on top of the existing API.
But couldn't the same be said of the "fix" trap, frozen prototype, and frozen typeof for proxies ?
As for the fix trap: there needs to be some way for a proxy to react to freeze|seal|preventExtensions, even with your proposed extension.
As for frozen typeof: there have been proposals in the past of adding an additional argument to Proxy.create to configure the proxy's |typeof| value (still operating under the restrictions of ES5 11.4.3). If this configurability turns out to be necessary in certain scenarios, I'm sure it will be reconsidered.
As for frozen prototype: despite non-standard proto, the current consensus is to move away from mutable prototypes, not to add standardized features to support it.
Also, since proxies are not allowed to advertise their properties as non-configurable, without something like this proposal proxy transparency cannot be achieved. With the current API, one could implement a "Proxy.isProxy" function as follows...
Proxy.isProxy = function(objectOrProxy) { let unlikelyPropertyName = "_ at *&%+!"; Object.defineProperty(objectOrProxy, unlikelyPropertyName, {}); return Object.getOwnPropertyDescriptor(objectOrProxy, unlikelyPropertyName).configurable; }
Good point. As recently pointed out by Allen, the design point of not allowing proxies to emulate non-configurable properties is still controversial. I have always felt it to be an odd restriction, but I don't have a good alternative that doesn't break |configurable|'s implied invariants. The sweet spot that we seem to be looking for is an API that:
a) allows non-configurable properties to be emulated, b) while still allowing access to these properties to be intercepted (e.g. for logging/tracing), c) and upholding the invariants of non-configurable properties.
The current API doesn't allow a, but upholds c (b is not an issue). Your proposed extension would allow a, but not b, and still uphold c. If one is willing to drop c, it's easy to get a and b by simply removing the check for configurable:true from the current API.
I can think of an API that satisfies all three by e.g. extending your proposal such that access to non-configurable properties still triggers the get trap, but ignores its result, making the proxy more like a chaperone in Racket < docs.racket-lang.org/reference/chaperones.html?q=chaperone#(tech._chaperone)> (chaperones may intercept operations, but are not allowed to influence the result of the operation).
I'm worried though that such additional magic, tucked away in the proxy implementation, can make for a very difficult-to-understand overall API.
On May 9, 2011, at 8:38 AM, Tom Van Cutsem wrote:
Good point. As recently pointed out by Allen, the design point of not allowing proxies to emulate non-configurable properties is still controversial. I have always felt it to be an odd restriction, but I don't have a good alternative that doesn't break |configurable|'s implied invariants. The sweet spot that we seem to be looking for is an API that:
a) allows non-configurable properties to be emulated, b) while still allowing access to these properties to be intercepted (e.g. for logging/tracing), c) and upholding the invariants of non-configurable properties.
The current API doesn't allow a, but upholds c (b is not an issue). Your proposed extension would allow a, but not b, and still uphold c. If one is willing to drop c, it's easy to get a and b by simply removing the check for configurable:true from the current API.
I contend we should drop c. A handler that does not maintain the specified invariants is a buggy handler but, in general, we can't guarantee that programs do not contain bugs. ECMAScript is a language that generally favors expressiveness over early error detection. More generally, dynamic languages typically defer error checking as late as possible in order to avoid false positives. They check what a program actually does rather than what it might do.
Note that there are other explicit or implicit invariants about objects that are not guaranteed. For example, the keys/enumerate traps should only return the names of properties whose [[enumberable]] attribute is false (at the time of the call) and keys should only return the names of "own" properties. Yet we don't attempt to enforce that a handler correctly implements these invariants or disable proxy semantics upon the first occurrence of an action that might exercise buggy handler code.
I can think of an API that satisfies all three by e.g. extending your proposal such that access to non-configurable properties still triggers the get trap, but ignores its result, making the proxy more like a chaperone in Racket docs.racket-lang.org/reference/chaperones.html?q=chaperone#(tech._chaperone) (chaperones may intercept operations, but are not allowed to influence the result of the operation).
I'm worried though that such additional magic, tucked away in the proxy implementation, can make for a very difficult-to-understand overall API.
and would also properly result in a higher overhead (and hence less useful) facility.
I think our first priority should be the expressiveness of Proxies. They need to be able to deal with all the use cases we have discussed including providing an faithful and efficient implementation of the existing built-ins and DOM. After we have that we can try to find situations where we can use the API to enforce various object invariants but even there we need to be selective. After all, a major use case for Proxies is to implement objects that in some way or another violate something about the normal invariants for native ES objects.
On Mon, May 9, 2011 at 10:38 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
I do see benefit in this proposal as an aid for proxy writers to develop more well-behaved proxies. But for that purpose it seems that this proposal can be fully written as a Javascript library layered on top of the existing API.
But couldn't the same be said of the "fix" trap, frozen prototype, and frozen typeof for proxies ?
As for the fix trap: there needs to be some way for a proxy to react to freeze|seal|preventExtensions, even with your proposed extension. As for frozen typeof: there have been proposals in the past of adding an additional argument to Proxy.create to configure the proxy's |typeof| value (still operating under the restrictions of ES5 11.4.3). If this configurability turns out to be necessary in certain scenarios, I'm sure it will be reconsidered. As for frozen prototype: despite non-standard proto, the current consensus is to move away from mutable prototypes, not to add standardized features to support it.
I was not implying that these invariants should not be enforced, but rather that if they are enforced, then non-configurable properties should be enforced as well, as not doing so seems arbitrary and thus confusing to users of the API. As Allen mentioned, there are other important invariants that are not currently enforced, and I would make the same argument there, that either they should be enforced as well, or that nothing should be enforced.
As recently pointed out by Allen, the design point of not allowing proxies to emulate non-configurable properties is still controversial. I have always felt it to be an odd restriction, but I don't have a good alternative that doesn't break |configurable|'s implied invariants. The sweet spot that we seem to be looking for is an API that: a) allows non-configurable properties to be emulated, b) while still allowing access to these properties to be intercepted (e.g. for logging/tracing), c) and upholding the invariants of non-configurable properties. The current API doesn't allow a, but upholds c (b is not an issue).
It only upholds c due to the fact that it does not allow non-configurable properties in the first place.
I can think of an API that satisfies all three by e.g. extending your proposal such that access to non-configurable properties still triggers the get trap, but ignores its result, making the proxy more like a chaperone in Racket docs.racket-lang.org/reference/chaperones.html?q=chaperone#(tech._chaperone) (chaperones may intercept operations, but are not allowed to influence the result of the operation). I'm worried though that such additional magic, tucked away in the proxy implementation, can make for a very difficult-to-understand overall API.
I think the way to get b is actually through AOP (Aspect Oriented Programming) support. I would envision this as an AOP API that is tightly integrated with the intercession API, having the intercession API's "traps" serve as the "join points" "around" which the AOP API's advice could be executed.
With regard to property fixing, if there really are use cases that it restricts (although I don't see any), it could be made optional. To support this, this proposal would merely no longer throw due to a falsey "defineProperty" trap invocation return value.
Thanks, Sean Eagan
On Mon, May 9, 2011 at 1:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
The sweet spot that we seem to be looking for is an API that: a) allows non-configurable properties to be emulated, b) while still allowing access to these properties to be intercepted (e.g. for logging/tracing), c) and upholding the invariants of non-configurable properties. The current API doesn't allow a, but upholds c (b is not an issue). Your proposed extension would allow a, but not b, and still uphold c. If one is willing to drop c, it's easy to get a and b by simply removing the check for configurable:true from the current API.
I contend we should drop c. A handler that does not maintain the specified invariants is a buggy handler but, in general, we can't guarantee that programs do not contain bugs.
But we can guarantee that programs do not contain certain types of bugs, as is done with ES5 strict mode for example, and as long as this does not preclude any reasonable use cases, then why not?
ECMAScript is a language that generally favors expressiveness over early error detection.
I thought early error detection was one of the goals of ES Harmony ?
More generally, dynamic languages typically defer error checking as late as possible in order to avoid false positives. They check what a program actually does rather than what it might do.
I don't believe we have the option to check what a program actually does in this case. How would we validate that a proxy is upholding the invariants of non-configurable properties throughout each property's lifetime?
Note that there are other explicit or implicit invariants about objects that are not guaranteed. For example, the keys/enumerate traps should only return the names of properties whose [[enumberable]] attribute is false (at the time of the call) and keys should only return the names of "own" properties. Yet we don't attempt to enforce that a handler correctly implements these invariants or disable proxy semantics upon the first occurrence of an action that might exercise buggy handler code.
I think while a proxy property is still configurable, semantics related to particular values of property descriptor attributes such as "enumerable" do not need to be enforced, because the value of those properties can still be changed at any time, including within the execution of another trap, such as "keys" or "enumerate". However, once a property is non-configurable, these values can change at any time.
I can think of an API that satisfies all three by e.g. extending your proposal such that access to non-configurable properties still triggers the get trap, but ignores its result, making the proxy more like a chaperone in Racket docs.racket-lang.org/reference/chaperones.html?q=chaperone#(tech._chaperone) (chaperones may intercept operations, but are not allowed to influence the result of the operation). I'm worried though that such additional magic, tucked away in the proxy implementation, can make for a very difficult-to-understand overall API.
and would also properly result in a higher overhead (and hence less useful) facility.
As I commented before, I think AOP support is what is actually needed here, and thus no such magic or overhead is needed for property fixing.
I think our first priority should be the expressiveness of Proxies. They need to be able to deal with all the use cases we have discussed including providing an faithful and efficient implementation of the existing built-ins and DOM.
I believe this proposal for property fixing would get us the non-configurable properties necessary to handle some of the use cases you are referring to.
After we have that we can try to find situations where we can use the API to enforce various object invariants but even there we need to be selective. After all, a major use case for Proxies is to implement objects that in some way or another violate something about the normal invariants for native ES objects.
I think we should take a balanced approach as we have been to this point. Being able to depend on a decent set of invariants I believe will be welcomed by both proxy providers and proxy consumers.
Thanks, Sean Eagan
On May 10, 2011, at 11:18 AM, Sean Eagan wrote:
On Mon, May 9, 2011 at 10:38 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
I do see benefit in this proposal as an aid for proxy writers to develop more well-behaved proxies. But for that purpose it seems that this proposal can be fully written as a Javascript library layered on top of the existing API.
But couldn't the same be said of the "fix" trap, frozen prototype, and frozen typeof for proxies ?
As for the fix trap: there needs to be some way for a proxy to react to freeze|seal|preventExtensions, even with your proposed extension. As for frozen typeof: there have been proposals in the past of adding an additional argument to Proxy.create to configure the proxy's |typeof| value (still operating under the restrictions of ES5 11.4.3). If this configurability turns out to be necessary in certain scenarios, I'm sure it will be reconsidered. As for frozen prototype: despite non-standard proto, the current consensus is to move away from mutable prototypes, not to add standardized features to support it.
I was not implying that these invariants should not be enforced, but rather that if they are enforced, then non-configurable properties should be enforced as well, as not doing so seems arbitrary and thus confusing to users of the API. As Allen mentioned, there are other important invariants that are not currently enforced, and I would make the same argument there, that either they should be enforced as well, or that nothing should be enforced.
I think we are dancing around one of the key differences between static languages and dynamic languages. Static languages make guarantees about a set of potentially complex invariants (for example, subtype conformance). They can do this because the necessary work to detect violations of those invariants is performed ahead of time before the program is allowed to execute. Dynamic languages do most invariant validation as the program runs and hence generally restrict themselves to guaranteeing simple invariants (for example, memory safety) that can be cheaply performed many times as the program runs. Dynamic languages generally avoid expense checking of complex invariants and instead assume that any critical violation of complex invariants will ultimately manifest themselves as violations of the simple invariants that are checked.
A related difference is that a static language generally rejects programs when it proves the set of all possible program inputs produces some states that violate the language's invariants. The program is rejected, even if the input states that produce the invariant violations will never occur in practice. This is a conservative (or pessimistic) approach -- if a program might fail, we assume it will fail. Dynamic languages generally only reject programs (at runtime) when the actual data values used by the program violates the language's invariants. This is a permissive (or optimistic) approach -- if a program might work, we give it the benefit of the doubt and let it run up to the point it begins to misbehave.
The configurability restrictions on Proxies seems to be trying to apply a static language perspective to the very dynamic ES language. They are based upon a complex invariant (what can/cannot be assumed after observing the state of a configurable attribute). Because, there is at best difficult to guarantee that user written proxy handlers will correctly enforce the invariants associated with of configurable:false it is forbidden for a proxy to set configurable to that state. It is pessimistic, it says that because somebody might write a buggy proxy setting configurable we won't let them write any proxy that sets configurable. An alternative that has been proposed is to try to dynamically enforce the configurable invariants. But that is an example, of moving expensive (and probably highly redundant) complex invariants checks into runtime. While it would catch buggy programs, but has the potential of imposing a significant runtime performance penalty on valid programs. The normal dynamic language approach to this sort of problem is to be optimistic about the validity of the program while continuing to guarantee memory safety, and depending upon conventional testing procedure to detect more complex error conditions.
Le 11/05/2011 08:41, Allen Wirfs-Brock a écrit :
I think we are dancing around one of the key differences between static languages and dynamic languages. Static languages make guarantees about a set of potentially complex invariants (for example, subtype conformance). They can do this because the necessary work to detect violations of those invariants is performed ahead of time before the program is allowed to execute. Dynamic languages do most invariant validation as the program runs and hence generally restrict themselves to guaranteeing simple invariants (for example, memory safety) that can be cheaply performed many times as the program runs. Dynamic languages generally avoid expense checking of complex invariants and instead assume that any critical violation of complex invariants will ultimately manifest themselves as violations of the simple invariants that are checked.
A related difference is that a static language generally rejects programs when it proves the set of all possible program inputs produces some states that violate the language's invariants. The program is rejected, even if the input states that produce the invariant violations will never occur in practice. This is a conservative (or pessimistic) approach -- if a program might fail, we assume it will fail. Dynamic languages generally only reject programs (at runtime) when the actual data values used by the program violates the language's invariants. This is a permissive (or optimistic) approach -- if a program might work, we give it the benefit of the doubt and let it run up to the point it begins to misbehave.
The configurability restrictions on Proxies seems to be trying to apply a static language perspective to the very dynamic ES language. They are based upon a complex invariant (what can/cannot be assumed after observing the state of a configurable attribute). Because, there is at best difficult to guarantee that user written proxy handlers will correctly enforce the invariants associated with of configurable:false it is forbidden for a proxy to set configurable to that state. It is pessimistic, it says that because somebody might write a buggy proxy setting configurable we won't let them write any proxy that sets configurable. An alternative that has been proposed is to try to dynamically enforce the configurable invariants. But that is an example, of moving expensive (and probably highly redundant) complex invariants checks into runtime. While it would catch buggy programs, but has the potential of imposing a significant runtime performance penalty on valid programs. The normal dynamic language approach to this sort of problem is to be optimistic about the validity of the program while continuing to guarantee memory safety, and depending upon conventional testing procedure to detect more complex error conditions.
I understand the rationale that leads to the difference you describe in static/dynamic languages design. I understand it and I think these are good reasons. However, I can't help asking you some sort of proof. Has some research been done in the area? Are there dynamic languages that tried to enforce invariants at run-time? What lessons did they learn from that experience? Was the cost on valid program big enough to question these checks? Are there examples of dynamic languages interpreter with static analysis that were able to diminish this cost? Diminishing the cost to make the program "as fast in the long term"? (I quote, because I know that "as fast" and "in the long term" are vague notions)
I thought it might be productive to try and list the relevant "invariants" that proxies may or may not violate. Here is an initial list, which I do not claim is complete. If people think it's useful I can also record them on the wiki.
invariants that are enforced:
-
proxies can't take on the identity of other objects (i.e. '===' can't be intercepted)
-
immutable [[Prototype]]
-
unconfigurable instanceof (Proxy.create(handler, f.prototype) implies proxy instanceof f, function proxies are instanceof Function)
-
unconfigurable typeof (typeof objectproxy === "object", typeof functionproxy === "function")
-
unconfigurable [[Class]] (Object or Function, respectively)
-
once preventExtensions, seal, freeze is called on a proxy, its properties are fixed, so the invariants associated with these operations are maintained (e.g. can't add new properties, can't delete existing properties, …)
controversial invariant:
- proxies can't emulate non-configurable properties. If they would, proxies could still update attributes of non-configurable properties.
invariants that can be broken:
-
general inconsistencies between traps
-
e.g. has('foo') returning true while getPropertyDescriptor('foo') returns undefined
-
e.g. has('foo') returning true while getPropertyNames() doesn't contain it
-
e.g. get('foo') returning 42 while getOwnPropertyDescriptor('foo').value returns 24 (with no assignment operations happening in between)
-
-
traps that return values of the wrong type, e.g. getOwnPropertyNames not returning an array, getOwnPropertyDescriptor not returning a valid property descriptor
-
inheritance: traps are free to ignore the proxy's prototype when it comes to property lookup
-
duplicate property names in the property listing traps (enumerate, get{Own}PropertyNames)
-
the keys/enumerate traps should only return enumerable property names
-
the keys/getOwnPropertyNames traps should only return "own" property names
-
the result of getOwnPropertyNames should be a proper subset of the result of getPropertyNames (same for enumerate/keys)
-
... (I did not try to be exhaustive)
Because the ES5 spec is very implicit about most of these invariants, any distinction between which invariants to uphold and which not will be necessarily vague. However, I can discern some logic in the current distinction:
Most of the enforced properties have to do with classification: instanceof, typeof and === are operators used to classify objects, and classifications typically come with some implied invariants (I'm aware of the fact that instanceof tests can be non-monotonic in JS).
For {freeze|seal|preventExtensions}, one can make the case that defensive programming is one of their main use cases. Allowing proxies to gratuitously break them feels like taking away a lot of the usefulness of these primitives. The use case is not always defensive programming, e.g. frozen objects facilitate caching without cache invalidation.
W.r.t. non-configurable properties: at this point I am convinced that Sean's API is better than the current design of outright rejecting non-configurable properties. Surely there will be cases where proxies will need to emulate non-configurable properties. Also, the fact that the default forwarding handler can't straightforwardly delegate getOwnPropertyDescriptor calls to its target (since it has to change the property's configurability) is a bad smell.
Building on an earlier idea proposed by David ("inheritance-safe proxies"), a compromise could be as follows:
-
allow proxies to emulate/intercept non-configurable properties without checking
-
introduce an "ESObject" abstraction such that if h is a user-defined proxy handler, ESObject(h) creates a "safe" proxy handler that checks conformance of the handler w.r.t. the above ES5 Object semantics. This can be useful for catching bugs, or preventing misbehavior, depending on your POV.
Whether or not ESObject should or could be fully defined in either the engine or in Javascript is an orthogonal issue.
Cheers,
Tom
2011/5/11 David Bruant <david.bruant at labri.fr>
@David: One area of related work is that of Kiczales' metaobject protocols and more generally, "open implementations". Kiczales used the terminology of "rules of behavior" rather than "invariants". All of this work originated in dynamic languages (various flavors of Lisp in particular).
As for the conflicting issues of run-time enforcement and performance, in the MOP literature, a trick that is often applied is to use partial evaluation where possible. The use of partial evaluation makes a distinction between "load-time" and "run-time", allowing expensive conformance checks to be moved to "load-time". While this works great for structural invariants (e.g. ensuring that an inheritance graph is loop-free), it probably is no help at all for enforcing behavioral invariants (such as those associated with non-configurable properties).
(BTW, passing a function proxy's prototype as an argument to Proxy.createFunction ahead of time, and testing at proxy creation time whether it inherits from Function.prototype is one example of this technique. If one would opt for a getPrototypeOf trap, the check would need to be performed time and time again, at "run-time", so to speak).
Cheers, Tom
2011/5/11 David Bruant <david.bruant at labri.fr>
Le 12/05/2011 14:40, Tom Van Cutsem a écrit :
controversial invariant:
- proxies can't emulate non-configurable properties. If they would, proxies could still update attributes of non-configurable properties.
Currently, proxies can emulate configurable properties (they are forced to, but that's not my point). Still, a proxy can ignore Object.defineProperty calls in the case of a re-configuration and also ignore deletions, giving the impression that the property is non-configurable even though Object.getOwnPropertyDescriptor would say that configurable is set to "true". Proxies being able to lie about configurability is the exact mirror of proxies being able to lie about non-configurability. However, one is allowed, the other isn't.
I need to think more about the rest of your message. Good work on the invariant enumeration. It may be incomplete as you point out, but that's a good start anyway.
Among the enforced invariants, maybe that "property names being strings" could be added if that is enforced. If it is, it requries to cast all property names arguments to strings before trap calls. However, enforcing this invariant may require to do the same on the array returned by get{Own}PropertyNames/enumerate/keys. (maybe that it should rather be in the controversial part :-) )
Tom Van Cutsem:
invariants that are enforced: …
- unconfigurable [[Class]] (Object or Function, respectively)
Web IDL requires [[Class]] to take on values other than these two, for example it should be "HTMLDivElement" for an HTMLDivElement object. I have a feeling that the web requires this, as opposed to just having a custom toString function.
google.com/codesearch?q=Object.prototype.toString.call+lang:javascript brings up some libraries that use Object.prototype.toString on DOM objects.
Is it palatable to have proxies control [[Class]] or is there another way we can help proxies over this hurdle?
On Thu, May 12, 2011 at 4:20 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Tom Van Cutsem:
invariants that are enforced: …
- unconfigurable [[Class]] (Object or Function, respectively)
Web IDL requires [[Class]] to take on values other than these two, for example it should be "HTMLDivElement" for an HTMLDivElement object. I have a feeling that the web requires this, as opposed to just having a custom toString function.
google.com/codesearch?q=Object.prototype.toString.call+lang:javascript brings up some libraries that use Object.prototype.toString on DOM objects.
Is it palatable to have proxies control [[Class]] or is there another way we can help proxies over this hurdle?
Tom and I actually proposed this at an earlier EcmaScript meeting, for precisely this reason. Discussion revealed difficulties that led to increasing complexity, due to the use of [[Class]] as an internal nominal type test within the specification machinery. For example, all the Date methods implicitly test that their this.[[Class]] is "Date":
15.9.5:
> a TypeError exception is thrown if the this value is not an
> object for which the value of the [[Class]] internal property is
"Date"
Do we want a proxy to be able to pass this test? If so, do we want a proxy to be able to provide the internal properties that the Date methods access? At the time at least, the proposal for [[Class]] control went down in flames as such questions accumulated. In light of Allen's recent thinking about meta-objects and reflection, we can certainly revisit this, but please not at the May meeting ;).
Le 13/05/2011 01:20, Cameron McCormack a écrit :
Tom Van Cutsem:
invariants that are enforced: …
- unconfigurable [[Class]] (Object or Function, respectively) Web IDL requires [[Class]] to take on values other than these two, for example it should be "HTMLDivElement" for an HTMLDivElement object. I have a feeling that the web requires this, as opposed to just having a custom toString function.
google.com/codesearch?q=Object.prototype.toString.call+lang:javascript brings up some libraries that use Object.prototype.toString on DOM objects.
Is it palatable to have proxies control [[Class]] or is there another way we can help proxies over this hurdle?
DOM objects are defined in ECMAScript as "host objects". As such, they can do whatever they want with [[Class]] (as they always have). There is no need for proxies to change.
Anyway, I think that the idea is to define the DOM as ECMAScript (proxies included). Following this path, maybe that WebIDL could do a willful violation as it's sometimes done in HTML5.
Another idea is that Object.prototype.toString could return different things for DOM objects. This can certainly be implemented in pure ES.next with WeakMaps. It's duck-typing, so a bit ugly, but possible anyway.
David Bruant:
DOM objects are defined in ECMAScript as "host objects". As such, they can do whatever they want with [[Class]] (as they always have). There is no need for proxies to change.
Except of course for the desire to implement the DOM using proxies.
Anyway, I think that the idea is to define the DOM as ECMAScript (proxies included). Following this path, maybe that WebIDL could do a willful violation as it's sometimes done in HTML5.
It could (and probably will have to if there’s no way other way to get Object.prototype.toString to return the right values), but I thought proxies were developed precisely (well, as one of their main goals) so that we didn’t need to do this.
Another idea is that Object.prototype.toString could return different things for DOM objects. This can certainly be implemented in pure ES.next with WeakMaps. It's duck-typing, so a bit ugly, but possible anyway.
True, that would be one way around it.
David,
For clarification, can you give a working example (ie runable in FF nightly) of this:
"Object.prototype.toString could return different things for DOM objects. This can certainly be implemented in pure ES.next with WeakMaps. "
Thanks in advance!
Rick
-- Sent from my Palm Pre On May 12, 2011 8:12 PM, David Bruant <david.bruant at labri.fr> wrote:
Le 13/05/2011 01:20, Cameron McCormack a écrit :
Tom Van Cutsem:
invariants that are enforced: …
- unconfigurable [[Class]] (Object or Function, respectively) Web IDL requires [[Class]] to take on values other than these two, for example it should be "HTMLDivElement" for an HTMLDivElement object. I have a feeling that the web requires this, as opposed to just having a custom toString function.
google.com/codesearch?q=Object.prototype.toString.call+lang:javascript brings up some libraries that use Object.prototype.toString on DOM objects.
Is it palatable to have proxies control [[Class]] or is there another way we can help proxies over this hurdle?
DOM objects are defined in ECMAScript as "host objects". As such, they can do whatever they want with [[Class]] (as they always have). There is no need for proxies to change.
Anyway, I think that the idea is to define the DOM as ECMAScript (proxies included). Following this path, maybe that WebIDL could do a willful violation as it's sometimes done in HTML5.
Another idea is that Object.prototype.toString could return different things for DOM objects. This can certainly be implemented in pure ES.next with WeakMaps. It's duck-typing, so a bit ugly, but possible anyway.
Thanks Tom,
As you point out, the are plenty of other implicit invariants that proxies don't enforce. For example:
- a configurable property can be deleted
- a [[Put]] to a non-writable data property does not change the value observable via [[Get]]
- the attributes of a configurable property can be changed
- obj.hasOwnProperty("foo")===obj.hasOwnProperty("foo") //is true -Object.getOwnPropertyDescriptor(obj,"foo").hasOwnProperty("value) ) && obj.foo===obj.foo //is true, ignoring NaN case or lots of other consistency invariants among sequences of traps
I also wanted to point out that no modern browser fully respects the configurable invariants. Every browser implements RegExp.prototype.compile and the de-facto standard for its behavior blatantly modifies the properties specified in 15.10.7 which are defined to be configurable false and writable: false. The discussions that have occurred concerning this method concluded that it (with that behavior) is essential to the web and that it should be included in the next ES edition. I haven't figured out how we will reconcile this. Probably by turning the affected properties into accessors.
On May 12, 2011, at 5:40 AM, Tom Van Cutsem wrote:
...
Most of the enforced properties have to do with classification: instanceof, typeof and === are operators used to classify objects, and classifications typically come with some implied invariants (I'm aware of the fact that instanceof tests can be non-monotonic in JS).
For {freeze|seal|preventExtensions}, one can make the case that defensive programming is one of their main use cases. Allowing proxies to gratuitously break them feels like taking away a lot of the usefulness of these primitives.
Generally, ES is not a hand holding language. In particular, I don't see why these invariants are more important to enforce than some of those I mention above. A buggy implementation can violate these invariants (and as I mentioned above, all browser implementations are to some degree buggy). A buggy library can forget to freeze even though it is spec'ed do so. I don't see why a buggy proxy is any more of a problem.
My understanding was that a major motivation for these operations was to create objects that can be passed to untrusted code while being safe from tampering. The work around in the presences of proxies would seem to be don't pass any such buggy proxies or to create your own trusted proxy that wrappers the potentially buggy proxy.
The use case is not always defensive programming, e.g. frozen objects facilitate caching without cache invalidation.
You can presumably still do that cache based upon the implied invariant. A failure to respect the invariant within the proxy is a bug that you might want to have a test case for but probably shouldn't cause you to change your design. Just like most of the other object invariants that proxies don't guarantee but your program probably depends upon.
W.r.t. non-configurable properties: at this point I am convinced that Sean's API is better than the current design of outright rejecting non-configurable properties. Surely there will be cases where proxies will need to emulate non-configurable properties. Also, the fact that the default forwarding handler can't straightforwardly delegate getOwnPropertyDescriptor calls to its target (since it has to change the property's configurability) is a bad smell.
I don't like Sean's API because it requires the trapping infrastructure to build and perform validation against a potentially large data structure (per object table of all property names that have been set non-confgurable). That's the sort of complex invariant maintenance that I don't think belongs at the implementation level of a dynamic language. Consider as a use case the ES5 String object. String objects have a "virtual" property foe each character of their underlying string value. Each of these character properties is non-configurable but the String object itself is extensible. A string value potentially has millions of such character properties and they routinely have thousands of them. Using Proxies to implement this style of virtual properties over some fixed data source seems like a great use case. It should be possible and it should burden the underlying runtime with the overhead of validating a complex invariant that is practice would likely only fail during debugging of the Proxy.
Building on an earlier idea proposed by David ("inheritance-safe proxies"), a compromise could be as follows:
- allow proxies to emulate/intercept non-configurable properties without checking
- introduce an "ESObject" abstraction such that if h is a user-defined proxy handler, ESObject(h) creates a "safe" proxy handler that checks conformance of the handler w.r.t. the above ES5 Object semantics. This can be useful for catching bugs, or preventing misbehavior, depending on your POV.
This seems like a reasonable approach.
I've come to view the "native object" semantics defined in ES5 section 8 as simply one of many possible object semantics that can be supported by the core ES language constructs. Most of the ES built-in object types implement some sort of dialect of the "native object" semantics. DOM objects have even greater divergence and arbitrary host objects in some implementation can diverge from "native object" semantics in even more ways. I thing Proxies needed to be view as being closer in power to host objects then they are to "native objects". Given this, it would be great to have a ESObject implementation that programmer can refine for the situations where they intend to make not or only minor variations form the standard semantics.
In terms to the core semantics of Proxies, I think we should restrict ourselves to the semantics that are required to support the individual statements and operators of the core ES language. These are the same semantics that are needed to make those statements and operators operate the current range of native, built-in, host objects.
On May 12, 2011, at 4:20 PM, Cameron McCormack wrote:
Tom Van Cutsem:
invariants that are enforced: …
- unconfigurable [[Class]] (Object or Function, respectively)
Web IDL requires [[Class]] to take on values other than these two, for example it should be "HTMLDivElement" for an HTMLDivElement object. I have a feeling that the web requires this, as opposed to just having a custom toString function.
I'm still hopeful that I can eliminate [[Class]] entirely from the ES.next specification.
What is WebIDL really trying to accomplish when it specifies class values. Parameterizing toString. Trademarking objects? Something else. All the use cases I have seen for [[class]] an be accomplished some other way rather than by over-loading this specification device with additional meanings.
On May 12, 2011, at 6:52 PM, Allen Wirfs-Brock wrote:
I'm still hopeful that I can eliminate [[Class]] entirely from the ES.next specification
See strawman:es5_internal_nominal_typing for a sketch of what I have in mind
thanks for putting these lists together. It will really help clarify why we decided to enforce some invariants and not others. The crucial distinction is that between
- "momentarily invariant between two successive steps, i.e., given no interleaving with other code".
- "eternally invariant for all time" vs Let's take the first two examples from each of your lists:
a) proxies can't take on the identity of other objects (i.e. '===' can't be intercepted) b) has('foo') returning true while getPropertyDescriptor('foo') returns undefined
Let's write a test expressing each of the four combinations. In the following code, assume the bindings of assertTrue, Object.getPropertyDescriptor, and the test functions defined below are stable, as varying them to cause the test to fail does not demonstrate a violation of the invariant being tested.
function test1a(p1, p2) { assertTrue((p1 === p2) === (p1 === p2)); }
In ES5, I claim that there is no way I can call test1a that will cause it to fail, even with conforming host objects. This establishes only that === is stable between consecutive steps, i.e., without any interleaving.
function test2a(p1, p2, interleave) { "use strict"; var sameBefore = (p1 === p2); interleave(); assertTrue(sameBefore === (p1 === p2)); }
In ES5, I claim that there is no way I can call test2a that will cause it to fail, even with conforming host objects. This establishes that the invariant is robust across interleavings with other code. (The "use strict" above is only to suppress a loophole that would actually be besides the point: Otherwise, interleave could modify test2a.arguments[0], causing test2a to fail for reasons other than violation of the invariant we're testing.)
function test1b(p1, name) { "use strict"; name = String(name); // possible interleaving here, which is why we need "use strict" above assertTrue((name in p1) === (Object.getPropertyDescriptor(p1, name) !== undefined)); }
In ES5 augmented with only Object.getPropertyDescriptor, I claim that there is no way I can call test1b that will cause it to fail, even with conforming host objects. This establishes invariant #b in the absence of interleaving.
function test2b(p1, name, interleave) { "use strict"; name = String(name); var hasBefore = (name in p1); interleave(); assertTrue(hasBefore === (Object.getPropertyDescriptor(p1, name) !== undefined)); }
In ES5 augmented with only Object.getPropertyDescriptor, I can cause this test to fail as follows:
var x = {foo: 8}; test2b(x, 'foo', function(){ delete x.foo; });
So #a is eternally invariant while #b is only momentarily invariant.
The most significant weakening that proxies introduce, relative to our ability to reason in ES5, is introducing more potential interleaving points. In ES5, ('foo' in p1) could not cause code elsewhere to run, well, unless p1 is a host object. Since proxies introduce so many new interleaving points, it should not be surprising that proxies can break momentary invariants -- they could anyway by the same means that we broke test2b. The important question is: Which eternal invariants are we willing to lose? Let's go through your list again and see which elements of which list break which kind of invariant:
On Thu, May 12, 2011 at 5:40 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
I thought it might be productive to try and list the relevant "invariants" that proxies may or may not violate. Here is an initial list, which I do not claim is complete. If people think it's useful I can also record them on the wiki.
invariants that are enforced:
- proxies can't take on the identity of other objects (i.e. '===' can't be intercepted)
Eternal.
- immutable [[Prototype]]
Eternal for frozen objects. Not guaranteed eternal for non-frozen objects, but interleavings can only violate this invariant by using non-standard non-portable operations. Where these operations are available, this is a momentary invariant. Elsewhere it is eternal.
- unconfigurable instanceof (Proxy.create(handler, f.prototype) implies proxy instanceof f, function proxies are instanceof Function)
In ES5, (p1 instanceof F) is eternally stable only once p1's [[Prototype]] chain is immutable and F.prototype is frozen. If F.prototype is not frozen, then (p1 instanceof F) is not stable even when p1 is a proxy.
ES5 does specify that Function.prototype is frozen, so if p1 is a function and p1.[[Prototype]] cannot be modified (for whatever reason), then (p1 instanceof Function) is eternally stable.
- unconfigurable typeof (typeof objectproxy === "object", typeof functionproxy === "function")
typeof is eternally stable. The specified constraints on typeof's value are therefore eternal invariants.
- unconfigurable [[Class]] (Object or Function, respectively)
[[Class]] is eternally stable. The specified constraints on [[Class]]'s value are therefore eternal invariants.
- once preventExtensions, seal, freeze is called on a proxy, its properties are fixed, so the invariants associated with these operations are maintained (e.g. can't add new properties, can't delete existing properties, …)
In both cases, given that these operations return successfully rather than throw. Eternal.
controversial invariant:
- proxies can't emulate non-configurable properties. If they would, proxies could still update attributes of non-configurable properties.
The ES5 constraints on updating non configurable properties are eternal invariants.
invariants that can be broken:
general inconsistencies between traps
- e.g. has('foo') returning true while getPropertyDescriptor('foo') returns undefined
Momentary, as established in the intro.
- e.g. has('foo') returning true while getPropertyNames() doesn't contain it
Momentary.
- e.g. get('foo') returning 42 while getOwnPropertyDescriptor('foo').value returns 24 (with no assignment operations happening in between)
The parenthetical acknowledges the momentary nature of the invariant. Since the whole point of proxies is to interleave code anyway, the handler could have simply performed said assignment.
- traps that return values of the wrong type, e.g. getOwnPropertyNames not returning an array, getOwnPropertyDescriptor not returning a valid property descriptor
Eternal. I grant that this breaks the pattern.
- inheritance: traps are free to ignore the proxy's prototype when it comes to property lookup
There are some implied eternal invariants that this does break. We should enumerate them. But the obvious ones are momentary.
- duplicate property names in the property listing traps (enumerate, get{Own}PropertyNames)
Eternal
- the keys/enumerate traps should only return enumerable property names
Momentary
- the keys/getOwnPropertyNames traps should only return "own" property names
Momentary
- the result of getOwnPropertyNames should be a proper subset of the result of getPropertyNames (same for enumerate/keys)
Momentary
- ... (I did not try to be exhaustive)
;)
So I'm rather calm about proxies breaking momentary invariants. We should be very careful when we consider breaking eternal invariants.
Because the ES5 spec is very implicit about most of these invariants, any distinction between which invariants to uphold and which not will be necessarily vague. However, I can discern some logic in the current distinction:
Most of the enforced properties have to do with classification: instanceof, typeof and === are operators used to classify objects, and classifications typically come with some implied invariants (I'm aware of the fact that instanceof tests can be non-monotonic in JS).
For {freeze|seal|preventExtensions}, one can make the case that defensive programming is one of their main use cases. Allowing proxies to gratuitously break them feels like taking away a lot of the usefulness of these primitives. The use case is not always defensive programming, e.g. frozen objects facilitate caching without cache invalidation.
W.r.t. non-configurable properties: at this point I am convinced that Sean's API is better than the current design of outright rejecting non-configurable properties. Surely there will be cases where proxies will need to emulate non-configurable properties. Also, the fact that the default forwarding handler can't straightforwardly delegate getOwnPropertyDescriptor calls to its target (since it has to change the property's configurability) is a bad smell.
Building on an earlier idea proposed by David ("inheritance-safe proxies"), a compromise could be as follows:
allow proxies to emulate/intercept non-configurable properties without checking
introduce an "ESObject" abstraction such that if h is a user-defined proxy handler, ESObject(h) creates a "safe" proxy handler that checks conformance of the handler w.r.t. the above ES5 Object semantics. This can be useful for catching bugs, or preventing misbehavior, depending on your POV.
Whether or not ESObject should or could be fully defined in either the engine or in Javascript is an orthogonal issue.
This still enables the attacker to give to the defender an object that breaks the old eternal invariants that the defender may have been relying on.
Allen Wirfs-Brock:
I'm still hopeful that I can eliminate [[Class]] entirely from the ES.next specification.
What is WebIDL really trying to accomplish when it specifies class values. Parameterizing toString. Trademarking objects? Something else. All the use cases I have seen for [[class]] an be accomplished some other way rather than by over-loading this specification device with additional meanings.
All it is trying to accomplish is making Object.prototype.toString.call(object) return the kinds of values that websites expect. Websites are doing that to check the type of the object they’ve got, yes.
See strawman:es5_internal_nominal_typing for a sketch of what I have in mind
Being able to parameterise Object.prototype.toString is all that’d be needed. I suppose that can’t be done just with a self-contained Proxies spec, but requires changes to the Object.prototype.toString definition.
On Thu, May 12, 2011 at 6:48 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Thanks Tom,
As you point out, the are plenty of other implicit invariants that proxies don't enforce. For example:
- a configurable property can be deleted
In ES5, cannot a conforming host object already break this invariant?
- a [[Put]] to a non-writable data property does not change the value observable via [[Get]]
By the classification of the message I just sent, whether the property is a non-writable data property at the time of [[Put]] is Momentary.
- the attributes of a configurable property can be changed
Conforming host objects can already violate this.
- obj.hasOwnProperty("foo")===obj.hasOwnProperty("foo") //is true
Momentary.
-Object.getOwnPropertyDescriptor(obj,"foo").hasOwnProperty("value) ) && obj.foo===obj.foo //is true, ignoring NaN case or lots of other consistency invariants among sequences of traps
Momentary.
I also wanted to point out that no modern browser fully respects the configurable invariants. Every browser implements RegExp.prototype.compile and the de-facto standard for its behavior blatantly modifies the properties specified in 15.10.7 which are defined to be configurable false and writable: false. The discussions that have occurred concerning this method concluded that it (with that behavior) is essential to the web and that it should be included in the next ES edition. I haven't figured out how we will reconcile this. Probably by turning the affected properties into accessors.
Fortunately, RegExp.prototype.compile is deletable on all major browsers. (I know because initSES.js deletes it.)
On Thu, May 12, 2011 at 6:48 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
My understanding was that a major motivation for these operations was to create objects that can be passed to untrusted code while being safe from tampering. The work around in the presences of proxies would seem to be don't pass any such buggy proxies or to create your own trusted proxy that wrappers the potentially buggy proxy.
The use case is not always defensive programming, e.g. frozen objects facilitate caching without cache invalidation.
You can presumably still do that cache based upon the implied invariant. A failure to respect the invariant within the proxy is a bug that you might want to have a test case for but probably shouldn't cause you to change your design. Just like most of the other object invariants that proxies don't guarantee but your program probably depends upon.
This is equivalent to saying that attacks can choose to be nice to defenders. If attackers aren't nice, then their code is buggy, so problem solved.
On Thu, May 12, 2011 at 7:18 PM, Mark S. Miller <erights at google.com> wrote:
On Thu, May 12, 2011 at 6:48 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
[...]
You can presumably still do that cache based upon the implied invariant. A
failure to respect the invariant within the proxy is a bug that you might want to have a test case for but probably shouldn't cause you to change your design. Just like most of the other object invariants that proxies don't guarantee but your program probably depends upon.
This is equivalent to saying that attacks can choose to be nice to defenders. If attackers aren't nice, then their code is buggy, so problem solved.
s/attacks/attackers/
Mark already commented on these, but i'll just add an additional comment
- hasOwnProperty and getOwnPropertyDescriptor, like all of these meta object functions were not added as immutable properties, so none of them can be trusted and there's no way (pending the modules proposal?) to guarantee that you are getting a real version of the function. So their semantics are basically not trustworthy.
That out of the way, I think there's one thing that I am still not 100% clear on which is what the correct behaviour for host object properties is meant to be in the case of those properties that aren't essentially standard properties.
My recollection of this discussion from a long time ago was that:
- If a property value may change it must be reported as writable, even if it cannot be assigned to
- If a property may disappear it must be configurable, even if it cannot be deleted.
(Totally ignoring the problems of preventExtensions, seal and freeze.)
But I recall seeing something a while back that made me question this behaviour, so I'm curious as to whether someone could clarify.
On Thu, May 12, 2011 at 6:48 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote: [...]
I've come to view the "native object" semantics defined in ES5 section 8 as simply one of many possible object semantics that can be supported by the core ES language constructs. Most of the ES built-in object types implement some sort of dialect of the "native object" semantics. DOM objects have even greater divergence and arbitrary host objects in some implementation can diverge from "native object" semantics in even more ways. I thing Proxies needed to be view as being closer in power to host objects then they are to "native objects".
I think this is the central point of agreement. If conforming host objects can violate it, then it isn't an ES5 invariant. We should be most suspicious of violating eternal invariants that even conforming ES5 host objects must obey.
Given this, it would be great to have a ESObject implementation that programmer can refine for the situations where they intend to make not or only minor variations form the standard semantics.
Further constraining a handler to only be able to emulate a conforming native object is a great use for such a wrapper. This use does not invert the threat model.
On Thu, May 12, 2011 at 7:23 PM, Oliver Hunt <oliver at apple.com> wrote:
Mark already commented on these, but i'll just add an additional comment
- hasOwnProperty and getOwnPropertyDescriptor, like all of these meta object functions were not added as immutable properties, so none of them can be trusted and there's no way (pending the modules proposal?) to guarantee that you are getting a real version of the function. So their semantics are basically not trustworthy.
That out of the way, I think there's one thing that I am still not 100% clear on which is what the correct behaviour for host object properties is meant to be in the case of those properties that aren't essentially standard properties.
My recollection of this discussion from a long time ago was that:
- If a property value may change it must be reported as writable, even if it cannot be assigned to
either writable, configurable, or described as an accessor property, yes. Flipped around is clearer: Once a host object describes a property as a non-writable non-configurable data property, then it must report that property as having a stable value and stable attributes. Thus, caching logic can use this test to reliably cache such values without worrying about becoming confused by buggy or malicious clients. (Those unconcerned about malice should at least ask whether they care about defensiveness in the face of bugs.)
- If a property may disappear it must be configurable, even if it cannot be deleted.
yes
Le 13/05/2011 02:55, Rick Waldron a écrit :
David,
For clarification, can you give a working example (ie runable in FF nightly) of this:
"Object.prototype.toString could return different things for DOM objects. This can certainly be implemented in pure ES.next with WeakMaps. "
Sure. Here is an example of what I was thinking: gist.github.com/970223 I run it directly on the webconsole and it works like a charm (Tested on latest FF nightly on Windows 7 64bits) A couple of things to keep in mind:
- I have no idea which DOM "classes" people expect and what particular values of [[Class]] are set for them.
- I do not create objects respecting the DOM interfaces (it shouldn't make any difference, but tell me if you think it does)
- My sole purpose was to prove feasbility. I am aware that the solution I show is suboptimal in several ways (especially the several redefinitions of Object.prototype.toString)
- The same could have been achieved with arrays instead of weak maps, but would have led to memory leaks.
- I assume the "environment setting" code to run in a fresh conforming ES environment (ES5.1 + current implicit consensus on WeakMaps)
Le 13/05/2011 04:09, Cameron McCormack a écrit :
Allen Wirfs-Brock:
I'm still hopeful that I can eliminate [[Class]] entirely from the ES.next specification.
What is WebIDL really trying to accomplish when it specifies class values. Parameterizing toString. Trademarking objects? Something else. All the use cases I have seen for [[class]] an be accomplished some other way rather than by over-loading this specification device with additional meanings. All it is trying to accomplish is making Object.prototype.toString.call(object) return the kinds of values that websites expect. Websites are doing that to check the type of the object they’ve got, yes.
See strawman:es5_internal_nominal_typing for a sketch of what I have in mind Being able to parameterise Object.prototype.toString is all that’d be needed. I suppose that can’t be done just with a self-contained Proxies spec, but requires changes to the Object.prototype.toString definition.
Yes and no. In gist.github.com/970223 , I indeed redefine Object.prototype.toString, but this change is transparent and un-noticeable from user code (since it only affects objects for constructor the DOM environment controls). From the way I see it, a web browser ES environment works like this (of course, this is a naive view):
- Initialize a fresh ES-compliant environment (built-in defined in the spec (including global object))
- Initialize the DOM environment (all constructors (Node, Element...) and basic objects (window (global object extension), document...))
- Start parsing HTML (appending DOM elements along the way) and run scripts as they arrive (what I call "user code").
So if phase 2 changes any built-in in a way that is not noticeable from user code, I don't think it's a problem. In my code, the only way I see that some code could understand that Object.prototype.toString has been changed is by comparing object identities. But since user code only occur after phase 2 is completed, it cannot get the ES built-in Object.prototype.toString identity to compare afterward.
So, my example changes Object.prototype.toString definition strictly speaking, but not as far as web authors are concerned. Given that my 3 phases model is correct, in my opinion, WebIDL, in its effort to define the DOM in terms of ECMAScript 5 (+ Harmony proxies [1]) can take all liberty to make changes to the definition of built-ins as long as the ES environment looks fresh after phase 2. This would allow duck-typing when necessary. This would allow to define a couple of consistently implemented non-standard methods which belong in web browsers more than ECMAScript (see five last String.prototype.* methods in [2]). Of course, this would be just for spec purposes and implementations would have all liberty to implement differently than what the spec says.
David
[1] WeakMaps aren't necessary since we're dealing with a spec not an actual implementation [2] kangax.github.com/es5-compat-table/non-standard
David,
This is awesome, thank you for taking the time to put this together. If I have any specific questions for you, should I ask them here (perhaps others would benefit) or should I email you directly?
Thanks again
Rick
-- Sent from my Palm Pre On May 13, 2011 5:09 AM, David Bruant <david.bruant at labri.fr> wrote:
Le 13/05/2011 02:55, Rick Waldron a écrit :
David,
For clarification, can you give a working example (ie runable in FF
nightly) of this:
"Object.prototype.toString could return different
things for DOM objects. This can certainly be implemented in pure
ES.next with WeakMaps. "
Sure. Here is an example of what I was thinking:
I run it directly on the webconsole and it works like a charm (Tested on
latest FF nightly on Windows 7 64bits)
A couple of things to keep in mind:
- I have no idea which DOM "classes" people expect and what particular
values of [[Class]] are set for them.
- I do not create objects respecting the DOM interfaces (it shouldn't
make any difference, but tell me if you think it does)
- My sole purpose was to prove feasbility. I am aware that the solution
I show is suboptimal in several ways (especially the several
redefinitions of Object.prototype.toString)
- The same could have been achieved with arrays instead of weak maps,
but would have led to memory leaks.
- I assume the "environment setting" code to run in a fresh conforming
ES environment (ES5.1 + current implicit consensus on WeakMaps)
Le 13/05/2011 14:28, Rick Waldron a écrit :
David,
This is awesome, thank you for taking the time to put this together.
No problem. I think it's important to prove that things work by doing them. And this attitude seems to be widely shared on this list which is a good thing.
If I have any specific questions for you, should I ask them here (perhaps others would benefit) or should I email you directly?
I'm fine with both. I think that any question related to ECMAScript may benefit to the list. Depending on your question, other people may respond in my place if they feel they know the answer or start a debate if they disagree with my answers. I think a debate is good when it's transparent and constructive. However if don't feel comfortable writing on the list, there is no problem to ask me your questions directly.
2011/5/12 David Bruant <david.bruant at labri.fr>
Le 12/05/2011 14:40, Tom Van Cutsem a écrit :
controversial invariant:
- proxies can't emulate non-configurable properties. If they would, proxies could still update attributes of non-configurable properties.
Currently, proxies can emulate configurable properties (they are forced to, but that's not my point). Still, a proxy can ignore Object.defineProperty calls in the case of a re-configuration and also ignore deletions, giving the impression that the property is non-configurable even though Object.getOwnPropertyDescriptor would say that configurable is set to "true". Proxies being able to lie about configurability is the exact mirror of proxies being able to lie about non-configurability. However, one is allowed, the other isn't.
Good point. However, Mark's distinction between eternal and momentary invariants sheds some light on the differences. According to Mark's classification, configurable:false comes with eternal guarantees, while configurable:true does not. In other words: any assumptions that a program makes that are based on the fact that a property is configurable are necessarily momentary, since the property can become non-configurable later. The reverse is not true.
I need to think more about the rest of your message. Good work on the invariant enumeration. It may be incomplete as you point out, but that's a good start anyway.
Among the enforced invariants, maybe that "property names being strings" could be added if that is enforced. If it is, it requries to cast all property names arguments to strings before trap calls. However, enforcing this invariant may require to do the same on the array returned by get{Own}PropertyNames/enumerate/keys. (maybe that it should rather be in the controversial part :-) )
Proxies do coerce all property names to strings, e.g. proxy[obj] will trigger the 'get' trap with 'obj' coerced to a String. This is not actually enforced by the proxy spec, but rather by ES5 (e.g. [[Get]] assumes that its argument P is bound to a property name (a String)). At one point we suggested removing this restriction, so that "proxy[obj]" would give the get trap direct access to obj (which would be particularly useful when intercepting numeric indices on array-like proxies). IIRC, we didn't pursue this option since engines rely on property names being strings in other places, and widening the type of property names to cover arbitrary objects would be problematic.
On May 18, 2011, at 7:28 AM, Tom Van Cutsem wrote:
Proxies do coerce all property names to strings, e.g. proxy[obj] will trigger the 'get' trap with 'obj' coerced to a String. This is not actually enforced by the proxy spec, but rather by ES5 (e.g. [[Get]] assumes that its argument P is bound to a property name (a String)). At one point we suggested removing this restriction, so that "proxy[obj]" would give the get trap direct access to obj (which would be particularly useful when intercepting numeric indices on array-like proxies). IIRC, we didn't pursue this option since engines rely on property names being strings in other places, and widening the type of property names to cover arbitrary objects would be problematic.
SpiderMonkey has a wider internal property name type, which can accomodate at least int and object names. The int case is an optimization, commonly done. The object case is for E4X and perhaps private names.
Oliver wrote in the thread at the time that he thought allowing any value to be used as a property name (in brackets) and passed through uncoerced to proxies was implementable without trouble for JavaScriptCore, IIRC.
On Wed, May 18, 2011 at 7:38 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 18, 2011, at 7:28 AM, Tom Van Cutsem wrote:
Proxies do coerce all property names to strings, e.g. proxy[obj] will trigger the 'get' trap with 'obj' coerced to a String. This is not actually enforced by the proxy spec, but rather by ES5 (e.g. [[Get]] assumes that its argument P is bound to a property name (a String)). At one point we suggested removing this restriction, so that "proxy[obj]" would give the get trap direct access to obj (which would be particularly useful when intercepting numeric indices on array-like proxies). IIRC, we didn't pursue this option since engines rely on property names being strings in other places, and widening the type of property names to cover arbitrary objects would be problematic.
SpiderMonkey has a wider internal property name type, which can accomodate at least int and object names. The int case is an optimization, commonly done. The object case is for E4X and perhaps private names.
Oliver wrote in the thread at the time that he thought allowing any value to be used as a property name (in brackets) and passed through uncoerced to proxies was implementable without trouble for JavaScriptCore, IIRC.
That's the opposite of my memory, but it was a long time ago. Oliver?
On May 18, 2011, at 7:45 AM, Mark S. Miller wrote:
On Wed, May 18, 2011 at 7:38 AM, Brendan Eich <brendan at mozilla.com> wrote: On May 18, 2011, at 7:28 AM, Tom Van Cutsem wrote:
Proxies do coerce all property names to strings, e.g. proxy[obj] will trigger the 'get' trap with 'obj' coerced to a String. This is not actually enforced by the proxy spec, but rather by ES5 (e.g. [[Get]] assumes that its argument P is bound to a property name (a String)). At one point we suggested removing this restriction, so that "proxy[obj]" would give the get trap direct access to obj (which would be particularly useful when intercepting numeric indices on array-like proxies). IIRC, we didn't pursue this option since engines rely on property names being strings in other places, and widening the type of property names to cover arbitrary objects would be problematic.
SpiderMonkey has a wider internal property name type, which can accomodate at least int and object names. The int case is an optimization, commonly done. The object case is for E4X and perhaps private names.
Oliver wrote in the thread at the time that he thought allowing any value to be used as a property name (in brackets) and passed through uncoerced to proxies was implementable without trouble for JavaScriptCore, IIRC.
That's the opposite of my memory, but it was a long time ago.
I was thinking of
www.mail-archive.com/[email protected]/msg05392.html
at least. Probably some IRC convo after that as well, but at least that.
On Wed, May 18, 2011 at 8:50 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 18, 2011, at 7:45 AM, Mark S. Miller wrote:
[...]
Oliver wrote in the thread at the time that he thought allowing any value
to be used as a property name (in brackets) and passed through uncoerced to proxies was implementable without trouble for JavaScriptCore, IIRC.
That's the opposite of my memory, but it was a long time ago.
I was thinking of
www.mail-archive.com/[email protected]/msg05392.html
at least. Probably some IRC convo after that as well, but at least that.
I stand corrected. Thanks for the clarification.
IIRC I was saying that the engine should simply allow non-objects types to pass through uncoerced, rather than taking any particular stance on ease of implementation. I suspect in JSC that we would be able to do it without too much difficulty, but there's a a world of difference between that, and it actually being the case.
To address the outstanding issue of how proxies should deal with non-configurable properties, I picked up on Sean's earlier proposal, adapted it and turned it into a strawman: < strawman:fixed_properties>.
My adaptation is more permissive than the original proposal in that it still allows proxies to intercept access to non-configurable properties if they are willing to expose them as accessor rather than data properties.
This proposal would allow proxies to cover more use cases (because they can now emulate non-configurable properties), at the expense of additional complexity within proxy objects.
As always, discussion and feedback are appreciated.
Cheers, Tom
2011/5/4 Sean Eagan <seaneagan1 at gmail.com>
Doesn't this new proposal still preclude using proxies to create an exact emulation of the built-in Array object type. The "length" property of arrays is specified to be non-configurable yet special behavior must occur each time the value of length is changed. The proposal would allow "length" to be "fixed" as an accessor property whose set behavior did the necessary processing. However, that is also a violation of the Array specification as it requires that "length" exhibit the attributes of a data property.
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
I believe the best way to enable emulation of each builtin type would be to orthogonalize the proxy semantics, such that proxies are no longer a separate language type, but rather an internal capability accessible to all object types. This for example would allow for creation of array proxies whose internal [[DefineOwnProperty]] internal property is 15.4.5.1 just as with regular arrays, where 8.12.9 is updated with "defineProperty" trapping semantics, so the "defineProperty" trap would be invoked for both the "length" property and the array index property on calls to the array proxy's [[DefineOwnProperty]] internal method. It might additionally be useful to allow proxies to override the entirety of 15.4.5.1, in which case there could be a "defineArrayProperty" trap for this purpose. The same thing idea would be used to allow proxies to emulate 10.6, 15.3.5.4, 15.5.5.2. As in esdiscuss/2011-May/014838 these default behaviors would just be the existing prose rather than duplicate ES-defined default trap implementations.
A possible API for such an orthogonalized proxy semantics might be "object descriptors" an object level equivalent to property descriptors, where one of the object level attributes is "handler" ( [[Handler]] ). I am working on a formal proposal which includes all of these concepts, hope to get it out soon.
Would this alleviate some of your concerns with the "fixed properties" proposal?
2011/6/14 Allen Wirfs-Brock <allen at wirfs-brock.com>
Doesn't this new proposal still preclude using proxies to create an exact emulation of the built-in Array object type. The "length" property of arrays is specified to be non-configurable yet special behavior must occur each time the value of length is changed. The proposal would allow "length" to be "fixed" as an accessor property whose set behavior did the necessary processing. However, that is also a violation of the Array specification as it requires that "length" exhibit the attributes of a data property.
It is true that this strawman would not enable a faithful emulation of the Array "length" property at the meta-level (i.e. when looking at an object as a set of property descriptors). I can't yet judge how much of an issue this is: given the novelty of the property descriptor API, I don't know how much code such an imperfect Array proxy would break. At least at the "base-level" (i.e. regular application code), "length" would be updated as expected, and remains accessible as "arrayproxy.length".
(As an aside: re. the emulation of arrays, having a configurable [[Class]] such that |Object.prototype.toString.call(arrayproxy)| can return "[object Array]" seems like a more important imperfection to fix)
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
I'm not sure what you mean by special semantics. If you mean strong guarantees/invariants, then one example would be that a non-writable, non-configurable data property is guaranteed to have a stable value. The proposed strawman allows proxies to define such properties, at the expense of giving up interception of access to such a property.
Le 15/06/2011 13:34, Tom Van Cutsem a écrit :
2011/6/14 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>
Doesn't this new proposal still preclude using proxies to create an exact emulation of the built-in Array object type. The "length" property of arrays is specified to be non-configurable yet special behavior must occur each time the value of length is changed. The proposal would allow "length" to be "fixed" as an accessor property whose set behavior did the necessary processing. However, that is also a violation of the Array specification as it requires that "length" exhibit the attributes of a data property.
It is true that this strawman would not enable a faithful emulation of the Array "length" property at the meta-level (i.e. when looking at an object as a set of property descriptors). I can't yet judge how much of an issue this is: given the novelty of the property descriptor API, I don't know how much code such an imperfect Array proxy would break. At least at the "base-level" (i.e. regular application code), "length" would be updated as expected, and remains accessible as "arrayproxy.length".
(As an aside: re. the emulation of arrays, having a configurable [[Class]] such that |Object.prototype.toString.call(arrayproxy)| can return "[object Array]" seems like a more important imperfection to fix)
I disagree. People can fix this one by themselves: gist.github.com/1026960 (I haven't tested, but it gives the idea). Of course this solution is valid as long as observable side effects of [[Class]] can be redefined. This is not the case for the .length issue.
On Jun 15, 2011, at 5:19 AM, David Bruant wrote:
Le 15/06/2011 13:34, Tom Van Cutsem a écrit :
(As an aside: re. the emulation of arrays, having a configurable [[Class]] such that |Object.prototype.toString.call(arrayproxy)| can return "[object Array]" seems like a more important imperfection to fix) I disagree. People can fix this one by themselves: gist.github.com/1026960 (I haven't tested, but it gives the idea).
That's kind of gross, though. Non-modular. Clearly Tom was talking about modular or proxy-wise customization of [[ClassName]]. IIRC Allen has done work to separate [[Class]] from [[ClassName]] which should allow proxies to intercede for the latter.
Of course this solution is valid as long as observable side effects of [[Class]] can be redefined. This is not the case for the .length issue.
Can you give an example of the length issue? Sorry if I missed it.
Le 15/06/2011 17:10, Brendan Eich a écrit :
On Jun 15, 2011, at 5:19 AM, David Bruant wrote:
Le 15/06/2011 13:34, Tom Van Cutsem a écrit :
(As an aside: re. the emulation of arrays, having a configurable [[Class]] such that |Object.prototype.toString.call(arrayproxy)| can return "[object Array]" seems like a more important imperfection to fix) I disagree. People can fix this one by themselves: gist.github.com/1026960 (I haven't tested, but it gives the idea).
That's kind of gross, though.
I agree. My point was just to prove that it's possible. But it's gross indeed. Moreover, my trick wouldn't work in an environment with several globals (unless redifining all of them which is gross²)
Non-modular. Clearly Tom was talking about modular or proxy-wise customization of [[ClassName]]. IIRC Allen has done work to separate [[Class]] from [[ClassName]] which should allow proxies to intercede for the latter.
I'm looking forward to see it coming.
Of course this solution is valid as long as observable side effects of [[Class]] can be redefined. This is not the case for the .length issue.
Can you give an example of the length issue? Sorry if I missed it.
Currently, one open issue is deciding whether Object.get{Own}PropertyDescriptor should say that every proxy property is configurable (the main pro argument is that it would prevent proxies from lying by saying that properties aren't and change configuration or removing at the same time). Sean has a proposal to have some sort of "separate" records for non-configurable properties on proxies, both guaranteeing non-configurable invariants and the ability for proxies to have non-configurable properties. However, this proposal prevents from implementing arrays which have a non-configurable "length" but have a special behavior (relationship with numeric properties) that wouldn't be emulated by the "separate" records. So basically, if an ES implementation had a bug on arrays .length proxies may not be able to fix them.
My position on the open issue is that proxies should not try to enforce configurable or enumerable since they just cannot.
I'm currently torn between two visions of ECMAScript objects. The former is a property-based vision: an object is a collection of string -> value
associations. The latter is a Meta-Object Programming contract-based (MOP) vision. This second vision is more general and can modelize the former by enforcing invariants and relationship between internal methods (DavidBruant/PropStackObjects was an experiment in that direction). I think it's nothing less than a classic data/computation point of view difference, but considering one or the other changes the vision on proxies. In the former, Sean's proposal makes sense. I'm more doubtful on the latter because (in my opinion) there is no reason to isolate the behavior of some inputs of the MOP contract methods. And non-configurable invariants could be enforced on top of the latter vision, the opposite is not true.
On Jun 15, 2011, at 8:10 AM, Brendan Eich wrote:
On Jun 15, 2011, at 5:19 AM, David Bruant wrote:
Of course this solution is valid as long as observable side effects of [[Class]] can be redefined. This is not the case for the .length issue.
Can you give an example of the length issue? Sorry if I missed it.
From a posting I made earlier in this thread:
Doesn't this new proposal still preclude using proxies to create an exact emulation of the built-in Array object type. The "length" property of arrays is specified to be non-configurable yet special behavior must occur each time the value of length is changed. The proposal would allow "length" to be "fixed" as an accessor property whose set behavior did the necessary processing. However, that is also a violation of the Array specification as it requires that "length" exhibit the attributes of a data property.
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
More generally, for better or worse, Cameron McCormack is right now busily working to publish the last call draft of a Web IDL specification that includes rules for what the attribute settings must be for all DOM properties. If those rules don't match up with what proxies can express then there will be issues with implementing the DOM in ECMAScript.
On Jun 15, 2011, at 8:51 AM, Allen Wirfs-Brock wrote:
From a posting I made earlier in this thread:
Doesn't this new proposal still preclude using proxies to create an exact emulation of the built-in Array object type. The "length" property of arrays is specified to be non-configurable yet special behavior must occur each time the value of length is changed. The proposal would allow "length" to be "fixed" as an accessor property whose set behavior did the necessary processing. However, that is also a violation of the Array specification as it requires that "length" exhibit the attributes of a data property.
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
D'oh, I see.
More generally, for better or worse, Cameron McCormack is right now busily working to publish the last call draft of a Web IDL specification that includes rules for what the attribute settings must be for all DOM properties. If those rules don't match up with what proxies can express then there will be issues with implementing the DOM in ECMAScript.
Paging Dr. Van Cutsem!
To emulate length as non-configurable but magically changing its value as a data property may be possible with proxies. It is after all writable.
On Jun 15, 2011, at 4:34 AM, Tom Van Cutsem wrote:
2011/6/14 Allen Wirfs-Brock <allen at wirfs-brock.com> ...
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
I'm not sure what you mean by special semantics. If you mean strong guarantees/invariants, then one example would be that a non-writable, non-configurable data property is guaranteed to have a stable value. The proposed strawman allows proxies to define such properties, at the expense of giving up interception of access to such a property.
By "special semantics", I mean any semantics for the Object internal methods other than exactly what is specified in section 8.12.
The ES specification states requirements but doesn't make any guarantees. For example, it states a requirement on host objects concerning consistency of value for non-writable, non-configurable data properties. But it can guarantee that an implementation doesn't have bugs or has simply choosen to ignore that requirement. Also, the spec. doesn't say that accessing the value of such non-writable/non-configurable properties may not have any side-effects. As far as I know, nobody has ever actually audited any of the emerging ES5 browser implementations to see if their DOM implementations fully conform to all of the requirements of the ES5 spec. In the short term, I will be surprised if somebody does this and discoveres that the implementations all fully conform.
In general I see invariants violations as bugs that you normally don't explicitly write defensives code to detect. You assume the invariant is true and expect any significant violation of the invariant to manifest itself in some buggy behavior of your program. Any reasonable sized application or framework is gong to have hundreds, if not thousands, of such invariants. I don't see why the non-writable/non-configurable invariant is particularly any more or less important than any other of the invariants that might be violated using proxies or just using regular ES code. In particular, I don't understand why that invariant is so important we would degrade the ability to proxies to support some of their primary uses cases.
It may be that for some security scenarios it really is important to have truly frozen data only objects. I have no problem with supporting that need via the fix trap or something like it. What I object to is using that use case as a justification for not supporting other important use cases such as accurate emulation of built-in objects or the DOM.
typo in 3rd sentence of 2nd paragraph: "can guarantee" --> "can't guarantee"
On Wed, Jun 15, 2011 at 9:26 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Jun 15, 2011, at 4:34 AM, Tom Van Cutsem wrote:
2011/6/14 Allen Wirfs-Brock <allen at wirfs-brock.com>
...
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
I'm not sure what you mean by special semantics. If you mean strong guarantees/invariants, then one example would be that a non-writable, non-configurable data property is guaranteed to have a stable value. The proposed strawman allows proxies to define such properties, at the expense of giving up interception of access to such a property.
By "special semantics", I mean any semantics for the Object internal methods other than exactly what is specified in section 8.12.
The ES specification states requirements but doesn't make any guarantees. For example, it states a requirement on host objects concerning consistency of value for non-writable, non-configurable data properties. But it can guarantee that an implementation doesn't have bugs or has simply choosen to ignore that requirement. Also, the spec. doesn't say that accessing the value of such non-writable/non-configurable properties may not have any side-effects. As far as I know, nobody has ever actually audited any of the emerging ES5 browser implementations to see if their DOM implementations fully conform to all of the requirements of the ES5 spec. In the short term, I will be surprised if somebody does this and discoveres that the implementations all fully conform.
I don't get your point. Host objects aside, I would be surprised if any JavaScript fully conforms to the ES5 spec in any timeframe that can be considered short term. In both cases, when spec violations are discovered, we hope that they get fixed. Otherwise, why have a spec at all?
On Jun 15, 2011, at 9:40 AM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 9:26 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Jun 15, 2011, at 4:34 AM, Tom Van Cutsem wrote:
2011/6/14 Allen Wirfs-Brock <allen at wirfs-brock.com> ...
I emphasize Array when I look at this simply because it is a fairly simple example of the sort of thing that a host object might currently do and the most important use case of proxies for me is the replacement/emulation of host objects. I really don't know where this idea that non-configurable implies no special semantics comes from. That isn't the case in the ES5 specification (10.6,15.3.5.4,15.4.5.1+15.4.5.2, 15.5.5.2) for such properties.
I'm not sure what you mean by special semantics. If you mean strong guarantees/invariants, then one example would be that a non-writable, non-configurable data property is guaranteed to have a stable value. The proposed strawman allows proxies to define such properties, at the expense of giving up interception of access to such a property.
By "special semantics", I mean any semantics for the Object internal methods other than exactly what is specified in section 8.12.
The ES specification states requirements but doesn't make any guarantees. For example, it states a requirement on host objects concerning consistency of value for non-writable, non-configurable data properties. But it can guarantee that an implementation doesn't have bugs or has simply choosen to ignore that requirement. Also, the spec. doesn't say that accessing the value of such non-writable/non-configurable properties may not have any side-effects. As far as I know, nobody has ever actually audited any of the emerging ES5 browser implementations to see if their DOM implementations fully conform to all of the requirements of the ES5 spec. In the short term, I will be surprised if somebody does this and discoveres that the implementations all fully conform.
I don't get your point. Host objects aside, I would be surprised if any JavaScript fully conforms to the ES5 spec in any timeframe that can be considered short term. In both cases, when spec violations are discovered, we hope that they get fixed. Otherwise, why have a spec at all?
Reason for restricting the configurable attribute of properties of Proxy objects seems to be trading off an attempt to guarantee an resonable invariant but at the cost of limiting the utility of proxies. My point is that such guarantees are never more than requirements that may or may not be met by actual implementations. If the requirement is there for security reasons then it probably isn't enough for you to blindly depend upon it. Given that, I don't see why proxies should be partially crippled in a attempt to turn a requirement into a guarantee. I'm fine with stating a requirement for proxy implementors just like we do for host object implementors. I'm not fine with it being a lynchpin of enforced proxy semantics.
2011/6/15 Brendan Eich <brendan at mozilla.com>
On Jun 15, 2011, at 8:51 AM, Allen Wirfs-Brock wrote:
More generally, for better or worse, Cameron McCormack is right now busily working to publish the last call draft of a Web IDL specification that includes rules for what the attribute settings must be for all DOM properties. If those rules don't match up with what proxies can express then there will be issues with implementing the DOM in ECMAScript.
Aside from the configurable attribute (which we are now discussing), proxies are able to emulate all other attribute values, so I can't immediately think of any other use cases that proxies currently cannot deal with.
Paging Dr. Van Cutsem!
To emulate length as non-configurable but magically changing its value as a data property may be possible with proxies. It is after all writable.
Ah, good suggestion. The fixed properties strawman would indeed allow this. First, one defines "length" as a non-configurable property on a fresh arrayProxy, thereby fixing the property:
Object.defineProperty(arrayProxy, "length", { value: 0, writable: true, enumerable: false, configurable: false });
Given that we provide all traps with a reference to the proxy they're currently servicing (as per one of the pending strawmen), the handler for arrayProxy can then update the fixed "length" property within a trap by performing:
Object.defineProperty(arrayProxy, "length", { value: newValue });
This will modify the fixed property, which is legal since it is writable. It may not be very elegant, but at least it's doable.
To be clear: what the strawman still doesn't allow is that a proxy would be able to emulate a non-writable, non-configurable data property whose value can still change.
On Jun 15, 2011, at 1:38 PM, Tom Van Cutsem wrote:
Object.defineProperty(arrayProxy, "length", { value: newValue });
This will modify the fixed property, which is legal since it is writable. It may not be very elegant, but at least it's doable.
Great. When in doubt, use brute force.
To be clear: what the strawman still doesn't allow is that a proxy would be able to emulate a non-writable, non-configurable data property whose value can still change.
Right, and I claim that is DOM crazyland host object behavior to leave behind in browser host object implementations.
We're redoing Gecko's DOM to be self-hosted in certain ways (nodelists via proxies) and experimenting with a fully self-hosted DOM. WebIDL is evolving to match.
Who needs more crazy? Not me.
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
Perhaps the strawman should be refined such that the defineProperty trap is triggered even on redefinition of fixed properties. The trap's returned descriptor would again act as the "real" descriptor that is used to redefine the actual fixed property inside of the proxy using the built-in [[DefineOwnProperty]] algorithm.
Cheers, Tom
Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
In a way, the fixed properties proposal make proxies bicephal. For some inputs (property names), they plug their handler-provided MOP-brain and for some others, they plug a native object MOP-brain (ES5 - 8.12). These brains cannot communicate. This is why changing .length in the "native object brain" has no effect in the other brain (which handles numeric properties(...unless some of these are non-configurable)). And I think it has been said before, but there would be no logging possible for non-configurable properties in the context of the fixed properties strawman since native MOP-brain doesn't allow that.
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr> wrote:
Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ? There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
In a way, the fixed properties proposal make proxies bicephal. For some inputs (property names), they plug their handler-provided MOP-brain and for some others, they plug a native object MOP-brain (ES5 - 8.12). These brains cannot communicate. This is why changing .length in the "native object brain" has no effect in the other brain (which handles numeric properties(...unless some of these are non-configurable)). And I think it has been said before, but there would be no logging possible for non-configurable properties in the context of the fixed properties strawman since native MOP-brain doesn't allow that.
Cute metaphor. But as Tom's code showed, the proxy can create fixed (non-configurable) accessor properties whose getters and setters form a corpus callosum ;).
On Jun 15, 2011, at 3:53 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr> wrote: Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ? There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
ES5 code can. Isn't that enough of an objection? People are filing bugs over such observable (with ES5's meta-object API) differences among engines, especially in the DOM where this is driving the WebIDL spec.
On Jun 15, 2011, at 3:53 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr> wrote: Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ?
Because the ES5 spec. say the length property has attributes: writable: true, enumerable: false, configurable: false. any of the above would be a minor variation, but still a variation from the specification. If you feel comfortable with accepting that variation what other variations would you also be comfortable with accepting? What variations would others be comfortable with accepting and what if their areas of comfort are different from yours? How about a variation that ignores the restrictions on non-configurable Proxy properties.
There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
The issue isn't only about Array length or compatibility with existing user code. It is about whether or not Proxies have the power to implement the sort of objects that have been created in the past by host objects and implementation extensions and even the ESstandard. What if somebody said, I have this great new system implementation language but it has an limitation that prevents it from implementing some features of the ES standard. Is it ok, to just get the implementation as close as I can. That's what they would likely do regardless of what you say, but would you be happy about that?
On Wed, Jun 15, 2011 at 4:01 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 15, 2011, at 3:53 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr>wrote:
Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ? There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
ES5 code can. Isn't that enough of an objection? People are filing bugs over such observable (with ES5's meta-object API) differences among engines, especially in the DOM where this is driving the WebIDL spec.
Objection to what? This is what I'm confused about. You're saying that there is ES5 code which actually cares about these assurances, and it's breaking because we don't enable proxies to issue these assurances falsely. Either the ES5 code cares or it doesn't. We need to ask, why does it care? If you enable proxies to lie, so that such ES5 code then proceeds under false assurances, is this a more or less severe breakage of that ES5 code? It depends on why the ES5 code cares about the answer.
On Wed, Jun 15, 2011 at 4:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Because the ES5 spec. say the length property has attributes: writable: true, enumerable: false, configurable: false. any of the above would be a minor variation, but still a variation from the specification. If you feel comfortable with accepting that variation what other variations would you also be comfortable with accepting? What variations would others be comfortable with accepting and what if their areas of comfort are different from yours? How about a variation that ignores the restrictions on non-configurable Proxy properties.
I really don't understand what we're talking about. On the one hand, I'm talking about an inability of a proxy to perfectly emulate an ES5 array within the ES5 spec. On the other hand, you're talking about relaxing the ES5 spec so that a proxy can appear to perfectly emulate an ES5 array while not doing so. These do not seem comparable. If these are comparable, what am I missing? Really, I'm not arguing yet, I'm asking for clarification, because I do not understand this conversation.
There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
The issue isn't only about Array length or compatibility with existing user code. It is about whether or not Proxies have the power to implement the sort of objects that have been created in the past by host objects and implementation extensions and even the ESstandard. What if somebody said, I have this great new system implementation language but it has an limitation that prevents it from implementing some features of the ES standard. Is it ok, to just get the implementation as close as I can. That's what they would likely do regardless of what you say, but would you be happy about that?
Arrays aren't host objects. And the legacy host object behavior that WebIDL must be compat with predates ES5. To take a simple position that may be clarifying, why not have WebIDL's JS binding specify that all properties of host objects must be configurable? Really? This would allow host objects to do what they want within the spec, would allow proxies to emulate host objects perfectly within the spec, and would all be perfectly compat with all legacy host object behavior. I would have no objection to such a WebIDL JS-binding specification.
On Jun 15, 2011, at 4:25 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 4:01 PM, Brendan Eich <brendan at mozilla.com> wrote: On Jun 15, 2011, at 3:53 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr> wrote: Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ? There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
ES5 code can. Isn't that enough of an objection? People are filing bugs over such observable (with ES5's meta-object API) differences among engines, especially in the DOM where this is driving the WebIDL spec.
Objection to what? This is what I'm confused about. You're saying that there is ES5 code which actually cares about these assurances, and it's breaking because we don't enable proxies to issue these assurances falsely. Either the ES5 code cares or it doesn't. We need to ask, why does it care? If you enable proxies to lie, so that such ES5 code then proceeds under false assurances, is this a more or less severe breakage of that ES5 code? It depends on why the ES5 code cares about the answer.
This line of argument is remarkably similar to discussion have had on a number of occasions with JavaScript implementor who were looking for license to ignore or modify part of the ES5 specified behavior. "nobody depends upon this, something will be better if we don't have to do that, it was stupid for the spec. to say that so I think I can just ignore it..."
On Wed, Jun 15, 2011 at 4:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Because the ES5 spec. say the length property has attributes: writable: true, enumerable: false, configurable: false. any of the above would be a minor variation, but still a variation from the specification. If you feel comfortable with accepting that variation what other variations would you also be comfortable with accepting? What variations would others be comfortable with accepting and what if their areas of comfort are different from yours? How about a variation that ignores the restrictions on non-configurable Proxy properties.
I really don't understand what we're talking about. On the one hand, I'm talking about an inability of a proxy to perfectly emulate an ES5 array within the ES5 spec. On the other hand, you're talking about relaxing the ES5 spec so that a proxy can appear to perfectly emulate an ES5 array while not doing so. These do not seem comparable. If these are comparable, what am I missing? Really, I'm not arguing yet, I'm asking for clarification, because I do not understand this conversation.
No, I'm not talking about relaxing the ES5 spec. I'm talking about what is necessary to enable ES.next to be used as an implementation language for the ES5 spec. Proxy are a metalevel facility that supports intercession. They are being added to enable ES programs to do things that they cannot directly do without dropping down to such a metalevel. What are some of these things that we want to enable. They are things that modify the operation of the "internal methods" that define the semantics of objects. What sort of modification to these operations are we willing to support? Perhaps, this is an area where we have never adequately reached consensus on. My personal stake in the ground is that I want to be able to implement all the ES5 built-in objects in full conformance to the ES5 specification. I also want to be able to implement all of the web app APIs including the DOM that are now implemented outside of JavaScript using engine specific host object foreign function interfaces. As a stake in the ground, I don't really case how these objects might be changed to more easily fit the Proxy specification. What I care about is that there are things that some of these objects do that cannot be accomplished using proxies. To me that says that proxies are not meeting my full expectations of what they should enable.
There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
The issue isn't only about Array length or compatibility with existing user code. It is about whether or not Proxies have the power to implement the sort of objects that have been created in the past by host objects and implementation extensions and even the ESstandard. What if somebody said, I have this great new system implementation language but it has an limitation that prevents it from implementing some features of the ES standard. Is it ok, to just get the implementation as close as I can. That's what they would likely do regardless of what you say, but would you be happy about that?
Arrays aren't host objects. And the legacy host object behavior that WebIDL must be compat with predates ES5. To take a simple position that may be clarifying, why not have WebIDL's JS binding specify that all properties of host objects must be configurable? Really? This would allow host objects to do what they want within the spec, would allow proxies to emulate host objects perfectly within the spec, and would all be perfectly compat with all legacy host object behavior. I would have no objection to such a WebIDL JS-binding specification.
This isn't about how WebIDL might be modified to make it more compatible with ES, that's actually what Cameroon is trying to do right now I see implementing ES5 built-in and implementing web app APIs are two important use cases for proxies but I'm currently bringing those up only as specific test cases for the overall power of proxies as currently specified. Both the built-ins and current host objects exist because of loopholes that allow them to escape from the confines of normal ES native object semantics. We want to allow metalevel programming in ES to accomplish similar things. What capabilities do we need to actually support at that metalevel? I'm not sure. But in the built-ins and existing browser host objects (which actually had quite wide variation across implementations) we have a fairly large corpus of what implementation have actually done at this level. I'm arguing that if we don't have the ability to replicate that corpus then we don't yet have an minimally adequate set of metalevel capabilities.
On Wed, Jun 15, 2011 at 5:13 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Jun 15, 2011, at 4:25 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 4:01 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 15, 2011, at 3:53 PM, Mark S. Miller wrote:
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr>wrote:
Le 15/06/2011 23:01, Tom Van Cutsem a écrit :
Just realized: even though an arrayProxy could update its fixed "length" property, it would not be able to intercept updates "from the outside" (i.e. updates to "length" by objects other than the handler). I guess that capability is also needed to be able to "shrink" an array if its "length" is decreased.
There's something I don't understand about this whole conversation. Why does our emulated array need to claim that its length property is a non-configurable data property, as opposed to
- a non-configurable accessor property
- a configurable data property
- a configurable accessor property ? There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
ES5 code can. Isn't that enough of an objection? People are filing bugs over such observable (with ES5's meta-object API) differences among engines, especially in the DOM where this is driving the WebIDL spec.
Objection to what? This is what I'm confused about. You're saying that there is ES5 code which actually cares about these assurances, and it's breaking because we don't enable proxies to issue these assurances falsely. Either the ES5 code cares or it doesn't. We need to ask, why does it care? If you enable proxies to lie, so that such ES5 code then proceeds under false assurances, is this a more or less severe breakage of that ES5 code? It depends on why the ES5 code cares about the answer.
This line of argument is remarkably similar to discussion have had on a number of occasions with JavaScript implementor who were looking for license to ignore or modify part of the ES5 specified behavior. "nobody depends upon this, something will be better if we don't have to do that, it was stupid for the spec. to say that so I think I can just ignore it..."
At time point, I still do not understand you. The lines you think are remarkably similar seem diametrically opposed. The "nobody..." line you think is similar to my line I find to instead be similar to yours. However, I think I understand what you say below. If, from my response, it seems that we're understanding each other below, perhaps we can skip our inability to communicate above?
Alternatively, if anyone here thinks they understand both what Allen is trying to say and what I am trying to say, perhaps you can help explain us to each other?
On Wed, Jun 15, 2011 at 4:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Because the ES5 spec. say the length property has attributes: writable: true, enumerable: false, configurable: false. any of the above would be a minor variation, but still a variation from the specification. If you feel comfortable with accepting that variation what other variations would you also be comfortable with accepting? What variations would others be comfortable with accepting and what if their areas of comfort are different from yours? How about a variation that ignores the restrictions on non-configurable Proxy properties.
I really don't understand what we're talking about. On the one hand, I'm talking about an inability of a proxy to perfectly emulate an ES5 array within the ES5 spec. On the other hand, you're talking about relaxing the ES5 spec so that a proxy can appear to perfectly emulate an ES5 array while not doing so. These do not seem comparable. If these are comparable, what am I missing? Really, I'm not arguing yet, I'm asking for clarification, because I do not understand this conversation.
No, I'm not talking about relaxing the ES5 spec. I'm talking about what is necessary to enable ES.next to be used as an implementation language for the ES5 spec. Proxy are a metalevel facility that supports intercession. They are being added to enable ES programs to do things that they cannot directly do without dropping down to such a metalevel. What are some of these things that we want to enable. They are things that modify the operation of the "internal methods" that define the semantics of objects. What sort of modification to these operations are we willing to support? Perhaps, this is an area where we have never adequately reached consensus on. My personal stake in the ground is that I want to be able to implement all the ES5 built-in objects in full conformance to the ES5 specification. I also want to be able to implement all of the web app APIs including the DOM that are now implemented outside of JavaScript using engine specific host object foreign function interfaces. As a stake in the ground, I don't really case how these objects might be changed to more easily fit the Proxy specification. What I care about is that there are things that some of these objects do that cannot be accomplished using proxies. To me that says that proxies are not meeting my full expectations of what they should enable.
Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
There can't be any ES3 code that would be broken by any of these other choices, since ES3 code can't ask about these attributes.
The issue isn't only about Array length or compatibility with existing user code. It is about whether or not Proxies have the power to implement the sort of objects that have been created in the past by host objects and implementation extensions and even the ESstandard. What if somebody said, I have this great new system implementation language but it has an limitation that prevents it from implementing some features of the ES standard. Is it ok, to just get the implementation as close as I can. That's what they would likely do regardless of what you say, but would you be happy about that?
Arrays aren't host objects. And the legacy host object behavior that WebIDL must be compat with predates ES5. To take a simple position that may be clarifying, why not have WebIDL's JS binding specify that all properties of host objects must be configurable? Really? This would allow host objects to do what they want within the spec, would allow proxies to emulate host objects perfectly within the spec, and would all be perfectly compat with all legacy host object behavior. I would have no objection to such a WebIDL JS-binding specification.
This isn't about how WebIDL might be modified to make it more compatible with ES, that's actually what Cameroon is trying to do right now I see implementing ES5 built-in and implementing web app APIs are two important use cases for proxies but I'm currently bringing those up only as specific test cases for the overall power of proxies as currently specified. Both the built-ins and current host objects exist because of loopholes that allow them to escape from the confines of normal ES native object semantics. We want to allow metalevel programming in ES to accomplish similar things. What capabilities do we need to actually support at that metalevel? I'm not sure. But in the built-ins and existing browser host objects (which actually had quite wide variation across implementations) we have a fairly large corpus of what implementation have actually done at this level. I'm arguing that if we don't have the ability to replicate that corpus then we don't yet have an minimally adequate set of metalevel capabilities.
So if all properties of host objects were configurable, would they no longer demonstrate that we don't have minimal adequacy? If we fix array 'length' as above, what remaining problem cases do we have for achieving this minimal adequacy? Can we fix these remaining problem cases in this same way?
On Jun 15, 2011, at 4:25 PM, Mark S. Miller wrote:
ES5 code can. Isn't that enough of an objection? People are filing bugs over such observable (with ES5's meta-object API) differences among engines, especially in the DOM where this is driving the WebIDL spec.
Objection to what? This is what I'm confused about.
Much of it is testers wanting conformance. Nerds love pro-forma scoring, and with some justice: interoperation bugs hide in bug habitat created by spec-cheating on the part of implementors.
Le 16/06/2011 00:53, Mark S. Miller a écrit :
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
In a way, the fixed properties proposal make proxies bicephal. For some inputs (property names), they plug their handler-provided MOP-brain and for some others, they plug a native object MOP-brain (ES5 - 8.12). These brains cannot communicate. This is why changing .length in the "native object brain" has no effect in the other brain (which handles numeric properties(...unless some of these are non-configurable)). And I think it has been said before, but there would be no logging possible for non-configurable properties in the context of the fixed properties strawman since native MOP-brain doesn't allow that.
Cute metaphor. But as Tom's code showed, the proxy can create fixed (non-configurable) accessor properties whose getters and setters form a corpus callosum ;).
Interactions with getter/setter is already a good thing, but I think it's not enough. ES5 offers a very fine-grained API to study objects. If we pretend to be able to emulate arrays based on proxies, we should be able to emulate everything including answering correctly when it comes to "is it a data or an accessor property descriptor?". For instance, for a data property descriptor, even if non-configurable, "writable" can be changed from true to false (then it cannot be changed afterward). This behavior is not possible when dealing with getter/setters. With fixed properties Tom's code, it is not possible to change an array's length from writable to not writable. Trying to do so would throw an error (because the property is non-configurable and any attempt to switch from accessor to data property descriptor throw an error (or just reject?)).
I'd like to insist on the ability for proxies to be able to emulate native arrays (and new ES5 Object.* API interaction in particular) especially because currently, SpiderMonkey has a problem with redefining ".length" on arrays. See bugzilla.mozilla.org/show_bug.cgi?id=598996 I wish proxies to be able to compensate if there is such a bug (in any ES implementation) and I need this level of spec conformance. This discredits the current fixed properties proposal (especially the getter/setter compensation code), I think.
How about optional property fixing as a compromise? We could replace the configurability check of the "defineProperty" trap return value with a check of whether the return value is an object, in which case it would be treated as a property descriptor to fix the property to, otherwise the return value would just be ignored.
Sorry, I meant remove the configurability check of the "getOwnPropertyDescriptor" and "getPropertyDescriptor" trap return values, and add a check as to whether the "defineProperty" trap return value is an object to fix the property to.
I remember Allen brought up some concerns before about the strictness of the "fix" trap as well. The same idea could be applied there, such that a proxy author could return a non-object value if they want to still be able to intercess, and they could explicitly throw a TypeError if they want to indicate that the object cannot be made non-extensible / sealed / frozen.
Le 16/06/2011 15:15, Sean Eagan a écrit :
Sorry, I meant remove the configurability check of the "getOwnPropertyDescriptor" and "getPropertyDescriptor" trap return values, and add a check as to whether the "defineProperty" trap return value is an object to fix the property to.
On Thu, Jun 16, 2011 at 8:12 AM, Sean Eagan<seaneagan1 at gmail.com> wrote:
How about optional property fixing as a compromise? We could replace the configurability check of the "defineProperty" trap return value with a check of whether the return value is an object, in which case it would be treated as a property descriptor to fix the property to, otherwise the return value would just be ignored.
If you replace the compulsory configurability check with an opt-in (by choosing a particular return value to defineProperty trap), then "optional property fixing" means "no property fixing", because the optional part could be implemented as a library on top of proxies without configurability check.
Le 16/06/2011 00:53, Mark S. Miller a écrit :
On Wed, Jun 15, 2011 at 2:10 PM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
In a way, the fixed properties proposal make proxies bicephal. For some inputs (property names), they plug their handler-provided MOP-brain and for some others, they plug a native object MOP-brain (ES5 - 8.12). These brains cannot communicate. This is why changing .length in the "native object brain" has no effect in the other brain (which handles numeric properties(...unless some of these are non-configurable)). And I think it has been said before, but there would be no logging possible for non-configurable properties in the context of the fixed properties strawman since native MOP-brain doesn't allow that.
Cute metaphor. But as Tom's code showed, the proxy can create fixed (non-configurable) accessor properties whose getters and setters form a corpus callosum ;).
I think I see also a potential security issue. In Tom's code, getters and setters of the non-configurable properties trigger code of what was in the handler. This is useful as a user to keep triggering the get and set traps, but it also leaks a reference to these functions (after a call to Object.getOwnPropertyDescriptor). In the current proposal, before a property becomes non-configurable, there is no access to any trap (unless having access to the object which implies having indirect access to all traps or the handler itself). After becoming a non-configurable accessor property, the get and set trap applied to the non-configurable properties become available as independant functions that can be passed around. Currently, one can revoke the access to an object with a membrane/wrapper, but with the leak, I think that all getter/setters will have to be wrapped as well (their call trap in particular)? Ok, maybe there is no security issue, but it adds a little bit more work.
I was first enthousiastic by the general idea of keeping get/set traps through getter/setters (came up first with the fix trap, I think), but since there is an API allowing to extract these functions independently, I'm not really sure it's a good idea (in general) anymore.
Le 16/06/2011 15:26, David Bruant a écrit :
Le 16/06/2011 15:15, Sean Eagan a écrit :
Sorry, I meant remove the configurability check of the "getOwnPropertyDescriptor" and "getPropertyDescriptor" trap return values, and add a check as to whether the "defineProperty" trap return value is an object to fix the property to.
On Thu, Jun 16, 2011 at 8:12 AM, Sean Eagan<seaneagan1 at gmail.com>
wrote:How about optional property fixing as a compromise? We could replace the configurability check of the "defineProperty" trap return value with a check of whether the return value is an object, in which case it would be treated as a property descriptor to fix the property to, otherwise the return value would just be ignored. If you replace the compulsory configurability check with an opt-in (by choosing a particular return value to defineProperty trap), then "optional property fixing" means "no property fixing", because the optional part could be implemented as a library on top of proxies without configurability check. ... And it doesn't solve the bicephality issue. One thing that proxies allow is inter-property intelligence. That's why they allow emulation of the relationship between "length" and numeric properties in arrays. Arrays have a unity (what I called "one brain") at the object scale. Any attempt we make to separate properties to a different brain will break this unity. We can partially recover (by putting pieces of the first brain at the property scale of the second one), but it's at the cost of imposing non-configurable properties to be accessor properties (which, this time, is not an invariant imposed by ES5).
The fixed property proposal (even if optional) currently imposes to choose between no inter-property intelligence (non-configurable accessor properties) and non-configurable property being accessors (we loose some inter-property intelligence in that case anyway).
2011/6/16 Mark S. Miller <erights at google.com>
Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|").
Note that my idiom of turning non-configurable data props into accessor props was just that: an idiom. It's not in any way enforced by the strawman.
If one is dealing with e.g. a non-configurable data property whose [[Value]] is bound to a function, then one can imagine a handler returning:
{ value: function(...args) { return handler.get(name)(...args); }, writable: desc.writable, enumerable: desc.enumerable, configurable: false }
Or sometimes the handler may not even need to continue to intercept access to a property, and may just return the original descriptor.
(Also: I just updated the strawman such that the defineProperty trap would still be triggered on fixed properties, which should allow proxies + this strawman to faithfully emulate Array "length")
Cheers, Tom
2011/6/16 David Bruant <david.bruant at labri.fr>
Le 16/06/2011 16:50, Tom Van Cutsem a écrit :
2011/6/16 Mark S. Miller <erights at google.com <mailto:erights at google.com>>
Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|").
In arrays, "length" affect numerical properties, but the opposite is also true. Should all numerical properties be considered as accessors then? (there is a little bit of bad faith here, because a valid implementation is possible with just "length" being an accessor. See [1]).
Considering "length" as a data or accessor property is a secondary question in my opinion. The "magic" behavior is not at the property level but at the object level (even though it can be emulated at the property level). The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?". If they are not, then, the notion of "data descriptor" does not make sense in the context of proxies because anytime a proxy could trigger any code. Should data property descriptor be entirely banned from proxies (and host objects)?
There is a cursor to put between data and accessor properties. My opinion is that an accessor property should be used when it has a side effect outside the object scope. For instance, document.cookie should be an accessor, because it affects something else than the document object (namely other tabs if these send requests to the same domain). Same for element.innerHTML which triggers a reflow. However, array.length doesn't have any observable effect outside of the object it's being used on. I am fully aware that the first flaw of this definition is that there is no way to enforce it in a program (especially not in getOwnPropertyDescriptor proxy traps return value). However, this separation may be a guideline for the spec purpose.
What are your thoughts on this separation?
David
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr> wrote:
** Le 16/06/2011 16:50, Tom Van Cutsem a écrit :
2011/6/16 Mark S. Miller <erights at google.com>
Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|").
In arrays, "length" affect numerical properties, but the opposite is also true. Should all numerical properties be considered as accessors then? (there is a little bit of bad faith here, because a valid implementation is possible with just "length" being an accessor. See [1]).
Considering "length" as a data or accessor property is a secondary question in my opinion. The "magic" behavior is not at the property level but at the object level (even though it can be emulated at the property level). The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner.
But as pointed out by an earlier message, my original suggestion, that array length be a non-configurable accessor, is wrong, as it would prevent it from becoming a non-configurable data property when freezing the array. So, while it's magical, either configurable data or configurable accessor would work. Once an array is frozen (or even length by itself frozen), its length no longer needs to be magical.
In summary, all arrays, Object.defineProperty(array, 'length', {writable: true, configurable: false}) should always be rejected. Likewise for any similarly problematic properties we encounter.
Le 16/06/2011 17:46, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
Le 16/06/2011 16:50, Tom Van Cutsem a écrit :
2011/6/16 Mark S. Miller <erights at google.com <mailto:erights at google.com>> Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully. This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|").
In arrays, "length" affect numerical properties, but the opposite is also true. Should all numerical properties be considered as accessors then? (there is a little bit of bad faith here, because a valid implementation is possible with just "length" being an accessor. See [1]). Considering "length" as a data or accessor property is a secondary question in my opinion. The "magic" behavior is not at the property level but at the object level (even though it can be emulated at the property level). The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner.
Currently, the "configurable" attributes has two meanings. At the same time it tells who whether or not you can delete the property and whether or not you can reconfigure (call to Object.defineProperty) your property. If I understand well, you would like it also to tell whether the property is magical or not.
If we are at a point where we'd break Object.defineProperty return values, shouldn't we add new keywords rather than adding semantics to current attribute keywords?
On Jun 16, 2011, at 9:09 AM, David Bruant wrote:
Le 16/06/2011 17:46, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr> wrote:
The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner. Currently, the "configurable" attributes has two meanings. At the same time it tells who whether or not you can delete the property and whether or not you can reconfigure (call to Object.defineProperty) your property. If I understand well, you would like it also to tell whether the property is magical or not.
Implementations need the no-delete aspect. We should not change that for Array length.
Freezing length is another use-case not to break, of course. No one disagrees there (we just have a SpiderMonkey bug to fix).
If we are at a point where we'd break Object.defineProperty return values, shouldn't we add new keywords rather than adding semantics to current attribute keywords?
We should have no more attribute "keywords" in property descriptors than we need to model the semantics we wish to reflect on via Object.* APIs and intercede in via proxies. Do we really want to split "DontDelete" back out of "configurable"?
On Thu, Jun 16, 2011 at 9:09 AM, David Bruant <david.bruant at labri.fr> wrote:
** Le 16/06/2011 17:46, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr>wrote:
Le 16/06/2011 16:50, Tom Van Cutsem a écrit :
2011/6/16 Mark S. Miller <erights at google.com>
Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|").
In arrays, "length" affect numerical properties, but the opposite is also true. Should all numerical properties be considered as accessors then? (there is a little bit of bad faith here, because a valid implementation is possible with just "length" being an accessor. See [1]).
Considering "length" as a data or accessor property is a secondary question in my opinion. The "magic" behavior is not at the property level but at the object level (even though it can be emulated at the property level). The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner.
Currently, the "configurable" attributes has two meanings. At the same time it tells who whether or not you can delete the property and whether or not you can reconfigure (call to Object.defineProperty) your property. If I understand well, you would like it also to tell whether the property is magical or not.
If we are at a point where we'd break Object.defineProperty return values, shouldn't we add new keywords rather than adding semantics to current attribute keywords?
I do not believe so. The host object contract for configurable shows that the only meaning it ever had in ES5 that one could count on is "this is not guaranteed not to be magical". The proxy spec already allows the same violations of the first two meanings you suggest:
- a proxy and a compliant ES5 host object may refuse to delete a configurable property, and
- a proxy and a compliant ES5 host object may refuse an attempt to reconfigure a configurable property.
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those.
On Thu, Jun 16, 2011 at 9:15 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 9:09 AM, David Bruant wrote:
Le 16/06/2011 17:46, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr>wrote:
The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner.
Currently, the "configurable" attributes has two meanings. At the same time it tells who whether or not you can delete the property and whether or not you can reconfigure (call to Object.defineProperty) your property. If I understand well, you would like it also to tell whether the property is magical or not.
Implementations need the no-delete aspect. We should not change that for Array length.
Freezing length is another use-case not to break, of course. No one disagrees there (we just have a SpiderMonkey bug to fix).
If we are at a point where we'd break Object.defineProperty return values, shouldn't we add new keywords rather than adding semantics to current attribute keywords?
We should have no more attribute "keywords" in property descriptors than we need to model the semantics we wish to reflect on via Object.* APIs and intercede in via proxies. Do we really want to split "DontDelete" back out of "configurable"?
We do not. We need merely specify that (a non-frozen) array.length is configurable but that arrays refuse to delete them. This is just part of the magical length behavior provided by arrays, with the virtues that it can be faithfully emulated by proxies.
On Jun 16, 2011, at 9:18 AM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 9:15 AM, Brendan Eich <brendan at mozilla.com> wrote: We should have no more attribute "keywords" in property descriptors than we need to model the semantics we wish to reflect on via Object.* APIs and intercede in via proxies. Do we really want to split "DontDelete" back out of "configurable"?
We do not. We need merely specify that (a non-frozen) array.length is configurable but that arrays refuse to delete them. This is just part of the magical length behavior provided by arrays, with the virtues that it can be faithfully emulated by proxies.
Clever. To rephrase your previous reply to avoid double-negatives ("this is not guaranteed not to be magical"), "configurable properties may behave magically". I agree it's the non-configurable magic that we should address.
Bonus, I think this will simplify things in our implementation of Array length vs. the ES5 Object.* APIs.
On Thu, Jun 16, 2011 at 9:22 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 9:18 AM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 9:15 AM, Brendan Eich <brendan at mozilla.com> wrote:
We should have no more attribute "keywords" in property descriptors than we need to model the semantics we wish to reflect on via Object.* APIs and intercede in via proxies. Do we really want to split "DontDelete" back out of "configurable"?
We do not. We need merely specify that (a non-frozen) array.length is configurable but that arrays refuse to delete them. This is just part of the magical length behavior provided by arrays, with the virtues that it can be faithfully emulated by proxies.
Clever. To rephrase your previous reply to avoid double-negatives ("this is not guaranteed not to be magical"), "configurable properties may behave magically".
In retrospect, perhaps we should have named this attribute "magical" rather that "configurable"? And it is shorter. (Just kidding ;).)
I agree it's the non-configurable magic that we should address.
Bonus, I think this will simplify things in our implementation of Array length vs. the ES5 Object.* APIs.
Cool! Shrinking a case explosion is often a good sign.
Le 16/06/2011 18:15, Mark S. Miller a écrit :
I do not believe so. The host object contract for configurable shows that the only meaning it ever had in ES5 that one could count on is "this is not guaranteed not to be magical". The proxy spec already allows the same violations of the first two meanings you suggest:
- a proxy and a compliant ES5 host object may refuse to delete a configurable property, and
- a proxy and a compliant ES5 host object may refuse an attempt to reconfigure a configurable property.
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those.
Ok, with this defintion, it makes sense to not let proxies lie on property configurability. So does it even make sense to want non-configurable (fixed) properties on proxies? Back to Sean's initial e-mail on this thread, why would we want individual non-configurable properties on proxies?
As a side note, if all properties are described as configurable, then, a forwarding proxy will not properly forward when it comes to returning a property descriptor if the target has a non-configurable property.
On Jun 16, 2011, at 9:15 AM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 9:09 AM, David Bruant <david.bruant at labri.fr> wrote: Le 16/06/2011 17:46, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 8:29 AM, David Bruant <david.bruant at labri.fr> wrote: Le 16/06/2011 16:50, Tom Van Cutsem a écrit :
2011/6/16 Mark S. Miller <erights at google.com> Ok good, I'll take you up on that. I propose that ES-next arrays differ from ES5 arrays in that their 'length' property appears to be a non-configurable accessor property. Clearly, that better reflects the way Array's 'length' works anyway. Alternatively, if there's some problem with that, I propose that array 'length' appears to be a configurable data property that arrays nevertheless refuse to delete. Either way, proxies would then be able to emulate them faithfully.
This is also my feeling: part of the awkwardness in emulating array "length" is definitely that we're trying to mimic the behavior of an accessor property using a mutable data property. Would Mark's suggestion be too radical a change? (note: I'm not arguing for this change on the grounds of "it's too awkward to emulate using proxies". I'm arguing on the grounds of "ES5 accessor properties seem to better describe the behavior of a dynamic property such as array |length|"). In arrays, "length" affect numerical properties, but the opposite is also true. Should all numerical properties be considered as accessors then? (there is a little bit of bad faith here, because a valid implementation is possible with just "length" being an accessor. See [1]).
Considering "length" as a data or accessor property is a secondary question in my opinion. The "magic" behavior is not at the property level but at the object level (even though it can be emulated at the property level). The question raised by Mark is: "should objects with noticeable custom internal method (array, host objects, proxies...) be allowed to prentend having data property even if some logic is triggered under the hood?".
Almost, and thanks for trying to summarize. My question is
"Should ... be allowed to pretend having a non-configurable data property ...?"
A perfectly fine answer to the array.length issue is to have length be a configurable data property so long as it needs to operate in a magical manner. For all such problematic magical behavior, we should likewise report the property as configurable so long as it needs to operate in a magical manner. Currently, the "configurable" attributes has two meanings. At the same time it tells who whether or not you can delete the property and whether or not you can reconfigure (call to Object.defineProperty) your property. If I understand well, you would like it also to tell whether the property is magical or not.
If we are at a point where we'd break Object.defineProperty return values, shouldn't we add new keywords rather than adding semantics to current attribute keywords?
I do not believe so. The host object contract for configurable shows that the only meaning it ever had in ES5 that one could count on is "this is not guaranteed not to be magical". The proxy spec already allows the same violations of the first two meanings you suggest:
- a proxy and a compliant ES5 host object may refuse to delete a configurable property, and
- a proxy and a compliant ES5 host object may refuse an attempt to reconfigure a configurable property.
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those.
I don't agree with this conclusion at all. There is a guarantee, it is just hard to know when it is applicable.
There are three layers of the system invoked here:
-
The normal native object semantics as manifest in the basic native operations of the language: property put, property get, property delete, implicitly or declarative property create, property enumerate, etc. This is the level that most application code operates at.
-
The Object.* introspection semantics that provide information (attribute values) on and limited control of how individual properties will actually respond to the basic native object operations.
-
An intercession semantics that allows complete redefinition of the semantics of the basic object operations. This is the "magic" level that allows creation of things that aren't normal native objects. This level includes the mechanisms of Proxy, host objects, and any built-in object semantics that deviate from normal object semantics.
For normal native objects there is a complete specification of the relationship between layer 1 and layer 2. For example, if you inspect a property's configurable attribute (a level to reflection operation) and see that its value is true you know that applying the delete operator to that property (a level 1 operation will succeed in deleting the property). Similarly if you use a reflective operation to set the configurable attribute of a property you know that applying a delete operation will fail.
The root question here seems to be if and how level 3 is allowed to modify the standard semantic linkage between levels 1 and 2. Given that level 3 is permitted to make pretty much arbitrary changes to the level 1 semantics, I'm not sure that this is even a very meaningful question as the level 1/level 2 normal native object semantic coupling is dependent upon the normal level 1 semantics. ES5 tried to express some restrictions upon what host objects are allowed WRT the level 1-2 semantic linkage. Those restriction had no teeth and it isn't clear that they have had any impact. There were no such restrictions on additional implementation provided built-in objects. We are now talking about what restriction are imposed (and enforced) on Proxy based objects.
Given the above view, it seems pointless to argument about placing level 3 constraints upon the value of the configurable attribute so that level 1 (or level 2) code can condition its behavior based upon its setting. What such code really want to know is whether an object is a native object and hence conforms to the standard level 1 and level 2 semantics or whether this is a magic level 3 defined object. In the latter case, no logic based upon the native object semantics will necessarily be valid. It seems that what such code really needs is the ability to test whether or not an object is a magic object. If not, it can depend upon the native object semantics for both level 1 and level 2. If it is magic, the client code might refuse to deal with the object or it might attempt to further identify the specific spell that the object is under in order to understand how it will behave.
On Thu, Jun 16, 2011 at 10:13 AM, David Bruant <david.bruant at labri.fr>wrote:
Le 16/06/2011 18:15, Mark S. Miller a écrit :
I do not believe so. The host object contract for configurable shows that the only meaning it ever had in ES5 that one could count on is "this is not guaranteed not to be magical". The proxy spec already allows the same violations of the first two meanings you suggest:
- a proxy and a compliant ES5 host object may refuse to delete a configurable property, and
- a proxy and a compliant ES5 host object may refuse an attempt to reconfigure a configurable property.
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those. Ok, with this defintion, it makes sense to not let proxies lie on property configurability. So does it even make sense to want non-configurable (fixed) properties on proxies? Back to Sean's initial e-mail on this thread, why would we want individual non-configurable properties on proxies?
As a side note, if all properties are described as configurable, then, a forwarding proxy will not properly forward when it comes to returning a property descriptor if the target has a non-configurable property.
As far are I can tell, the only reason anyone is asking for non-configurable properties on trapping proxies is the issue raised by your side note. But for this issue, I see no need to allow fixing of individual properties on trapping proxies.
On Jun 16, 2011, at 11:26 AM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 10:13 AM, David Bruant <david.bruant at labri.fr> wrote: Le 16/06/2011 18:15, Mark S. Miller a écrit :
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those. Ok, with this defintion, it makes sense to not let proxies lie on property configurability. So does it even make sense to want non-configurable (fixed) properties on proxies? Back to Sean's initial e-mail on this thread, why would we want individual non-configurable properties on proxies?
As a side note, if all properties are described as configurable, then, a forwarding proxy will not properly forward when it comes to returning a property descriptor if the target has a non-configurable property.
As far are I can tell, the only reason anyone is asking for non-configurable properties on trapping proxies is the issue raised by your side note. But for this issue, I see no need to allow fixing of individual properties on trapping proxies.
There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 11:26 AM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 10:13 AM, David Bruant <david.bruant at labri.fr>wrote:
Le 16/06/2011 18:15, Mark S. Miller a écrit :
In summary, "configurable" was never a guarantee of anything. "non-configurable" was the only state that came with guarantees. Let's not weaken those. Ok, with this defintion, it makes sense to not let proxies lie on property configurability. So does it even make sense to want non-configurable (fixed) properties on proxies? Back to Sean's initial e-mail on this thread, why would we want individual non-configurable properties on proxies?
As a side note, if all properties are described as configurable, then, a forwarding proxy will not properly forward when it comes to returning a property descriptor if the target has a non-configurable property.
As far are I can tell, the only reason anyone is asking for non-configurable properties on trapping proxies is the issue raised by your side note. But for this issue, I see no need to allow fixing of individual properties on trapping proxies.
There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
On Jun 16, 2011, at 4:14 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com> wrote: There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
It's in Last Call, so time is short. Also, implementations do matter, and non-configurable is valued by implementations that want to optimize by assuming the slot in the object won't go away. If we make Array length, NodeList length, etc. be configurable, we implementors will need some other hidden attribute.
On Thu, Jun 16, 2011 at 4:23 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 4:14 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com> wrote:
There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
It's in Last Call, so time is short. Also, implementations do matter, and non-configurable is valued by implementations that want to optimize by assuming the slot in the object won't go away. If we make Array length, NodeList length, etc. be configurable, we implementors will need some other hidden attribute.
There is already internal special case handling for every property that needs to be magical. So having non-delete-ability be part of this special case handling would seem natural. From your
Bonus, I think this will simplify things in our implementation of Array
length vs. the ES5 Object.* APIs.
it might even be net more natural than the current situation.
On Jun 16, 2011, at 4:27 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:23 PM, Brendan Eich <brendan at mozilla.com> wrote: On Jun 16, 2011, at 4:14 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com> wrote: There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
It's in Last Call, so time is short. Also, implementations do matter, and non-configurable is valued by implementations that want to optimize by assuming the slot in the object won't go away. If we make Array length, NodeList length, etc. be configurable, we implementors will need some other hidden attribute.
There is already internal special case handling for every property that needs to be magical. So having non-delete-ability be part of this special case handling would seem natural. From your
Bonus, I think this will simplify things in our implementation of Array length vs. the ES5 Object.* APIs.
it might even be net more natural than the current situation.
My "Bonus" remark was only about Array length. For a great number of non-configurable properties (e.g. global vars in ES5 implementations, not created by eval), we JIT authors need a general attribute to consult. We aren't hardcoding, as we do for Array length (all the fast VMs optimize a.length expressions in the dense array, or any array, case).
On Thu, Jun 16, 2011 at 4:30 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 4:27 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:23 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 16, 2011, at 4:14 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com>wrote:
There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
It's in Last Call, so time is short. Also, implementations do matter, and non-configurable is valued by implementations that want to optimize by assuming the slot in the object won't go away. If we make Array length, NodeList length, etc. be configurable, we implementors will need some other hidden attribute.
There is already internal special case handling for every property that needs to be magical. So having non-delete-ability be part of this special case handling would seem natural. From your
Bonus, I think this will simplify things in our implementation of Array length vs. the ES5 Object.* APIs.
it might even be net more natural than the current situation.
My "Bonus" remark was only about Array length. For a great number of non-configurable properties (e.g. global vars in ES5 implementations, not created by eval), we JIT authors need a general attribute to consult. We aren't hardcoding, as we do for Array length (all the fast VMs optimize a.length expressions in the dense array, or any array, case).
Can we gather a list of all magical host properties that WebIDL says are currently non-configurable but that proxies can't emulate unless they are made configurable?
On Jun 16, 2011, at 4:32 PM, Mark S. Miller wrote:
My "Bonus" remark was only about Array length. For a great number of non-configurable properties (e.g. global vars in ES5 implementations, not created by eval), we JIT authors need a general attribute to consult. We aren't hardcoding, as we do for Array length (all the fast VMs optimize a.length expressions in the dense array, or any array, case).
Can we gather a list of all magical host properties that WebIDL says are currently non-configurable but that proxies can't emulate unless they are made configurable?
See the recent public-script-coord threads -- but I'm not sure enumerating cases will help. We do not want to hardcode a list, even if short. Array length, we hardcode for competitive reasons; that cost is mostly sunk (and there's no sunk cost fallacy that I can see, just details about how one optimizes arrays).
For any more general set of non-configurable properties, we want one bit to condition judgment about how sealed or frozen the property is. We want this from JIT code, and also for deciding whether it is safe to share deeply frozen objects across threads via workers.
Le 17/06/2011 01:23, Brendan Eich a écrit :
On Jun 16, 2011, at 4:14 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 4:05 PM, Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>> wrote:
There is a second reason, mentioned recently: we are implementing the DOM on top of proxies, and the current WebIDL spec has non-configurable properties induced in its normative ES bindings from the IDL syntax. We want to match the spec.
Perhaps the WebIDL spec should be revised in exactly the same we we're currently talking about revised arrays?
It's in Last Call, so time is short. Also, implementations do matter, and non-configurable is valued by implementations that want to optimize by assuming the slot in the object won't go away. If we make Array length, NodeList length, etc. be configurable, we implementors will need some other hidden attribute.
I think that the deeper question we have to deal with now is the exact semantics of configurability when it comes to "non-natural objects" (array, host objects, proxies. Is there an official name for them in the spec?). My understanding is that Allen seems to question the applicability/application/ground of the spec on that ("Those restriction had no teeth and it isn't clear that they have had any impact.").
Currently, ES5 - 8.6.2 says: "The [[GetOwnProperty]] internal method of a host object must conform to the following invariants for each property of the host object: (...five bullets points...)" I'd like to ask a few questions on this part of the spec: Why are we expecting anything from host (all if including arrays?) objects at all? (I know this one is naive and a bit provocative, but I actually wonder) Why are we expecting host (all if including arrays?) objects to respect these particular invariants (this is actually 5 questions) and no other?
Mark S. Miller:
Can we gather a list of all magical host properties that WebIDL says are currently non-configurable but that proxies can't emulate unless they are made configurable?
Web IDL requires various properties to be non-configurable, but the ones we care about here are just the ones that need to exist on objects that will have to be implemented using proxies, right? I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
Constants map to non-configurable properties, but they exist only on interface objects and interface prototype objects, which are “normal” objects that wouldn’t need to be implemented using proxies.
I will soon commit (probably today) a change to make indexed/named properties be exposed using custom [[GetOwnProperty]] and [[DefineOwnProperty]] handlers, which will mean objects that support indexed/named properties need to be proxies. The relevant issue with these is that these objects must also expose non-configurable properties that users set on them. For example:
<!DOCTYPE html> <form name=a></form> <script> var forms = document.getElementsByTagName("form"); Object.defineOwnProperty(forms, "z", { value: 1 }); </script>
forms needs to expose two properties, "0" and "a", whose values are the HTMLFormElement. In my upcoming change, [[GetOwnProperty]] will be specified to return this property descriptor for property "0":
{ [[Writable]]:false, [[Enumerable]]:true, [[Configurable]]:true, [[Value]]:theHTMLFormElement }
but to refuse to allow the property to be reconfigured.
It would be nice if the z property could be allowed to be defined and exposed as a non-configurable property, but this can’t be done without the proxy proposal changes being discussed here. But I don’t think it’s a huge loss if we just disallow non-configurable properties from being set on objects that support indexed/named properties. My plan was to silently make [[DefineOwnProperty]] for such objects assume [[Configurable]] was true on the descriptor it receives. Throwing would also be acceptable.
We could do the same thing for the array host objects mentioend at the top of the email, too: have the length and individual array index properties be reported as configurable, but to have [[DefineOwnProperty]] refuse to reconfigure them.
(BTW, Web IDL is not in Last Call right now, but it is close.)
One question I forgot to ask, which is just my ignorance about the details of the proxy proposal. Say you have a prototype chain like this:
[object a] → [object b] → [Object prototype object] → null
a is a proxy object and b is a normal object. If you define a non- configurable property on b, and you look up that property on a, can it still be reported as non-configurable?
On Thu, Jun 16, 2011 at 4:54 PM, David Bruant <david.bruant at labri.fr> wrote:
** I think that the deeper question we have to deal with now is the exact semantics of configurability when it comes to "non-natural objects" (array, host objects, proxies. Is there an official name for them in the spec?).
Allen and I just had a long phone conversation about these issues. We ended up speaking in terms of a three part distinction:
-
normal native objects, i.e., those native objects or functions whose internal methods operate in the normal non-magical way. Normal native functions includes both built-in functions (e.g. Object.prototype.valueOf) and functions written in JavaScript (e.g., what "function(){}" evaluates to).
-
abnormal native objects, like arrays, whose internal methods are still fully specified by the ES5 spec, but whose behavior is peculiar / magical.
-
non-native objects, i.e., "host objects". We started our conversation with a long digression into spec legalese about whether an object could be neither native nor host. I still take the position that there cannot be by definition. I don't know whether Allen ended up agreeing. But we agreed that there are not currently any such taxonomy breaking objects, and so for our purposes we could use "non-native" and "host" as synonyms. "non-native" is less confusing since it avoids tempting one into inventing a fourth category.
In ES6, proxies create an open ended set of abnormal native objects whose behavior is defined by JavaScript code. To ES5 code in an ES6 environment, such proxies are non-native, in that their ES5-level behavior is not fully specified by the ES5 spec.
My understanding is that Allen seems to question the applicability/application/ground of the spec on that ("Those restriction had no teeth and it isn't clear that they have had any impact.").
NOTE: I offer the following only as a concrete example of a more general point, rather than to single out Safari. There are plenty of irritating spec violations across all the major browsers. But the following is a particularly clear example.
Currently, on Safari WebKit Nightly Version 5.0.5 (5533.21.1, r88920) and on Chrome 13.0.782.24 dev
(1,{}.valueOf)()
evaluates to the global object, in violation of the ES5 spec. (This is fixed as of Chrome 14.0.794.0 dev.) Although I reported < bugs.webkit.org/show_bug.cgi?id=51097> in December 2010, WebKit
still lists this bug as UNCONFIRMED.
Possible conclusion from this: the ES5 spec has no teeth and it isn't clear that it has any impact. My conclusion instead is that we're still in early days at rolling specs into tests that pressure implementors to conform. But when implementors violate clear spec language, we should report these violations, ensure that these violations show up as violations in standard test suites, and pressure implementors to eventually conform.
Currently, ES5 - 8.6.2 says: "The [[GetOwnProperty]] internal method of a host object must conform to the following invariants for each property of the host object: (...five bullets points...)" I'd like to ask a few questions on this part of the spec: Why are we expecting anything from host (all if including arrays?) objects at all? (I know this one is naive and a bit provocative, but I actually wonder)
Because it's in the spec, and we should all ensure that any violations of the spec stand out like a sore thumb in standard test suites.
Why are we expecting host (all if including arrays?) objects to respect these particular invariants (this is actually 5 questions) and no other?
Because these invariants are in the spec.
Put another way, do you expect Safari to eventually fix the December "valueOf" bug they haven't yet even confirmed? Why?
Should we ever stop expecting anyone to pay attention to the spec, I suggest that TC39 disband.
Another useful line of question is: Why are these invariants in the spec? This is useful to discuss, but is a separate matter.
On Thu, Jun 16, 2011 at 5:11 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Hi Mark.
Mark S. Miller:
Can we gather a list of all magical host properties that WebIDL says are currently non-configurable but that proxies can't emulate unless they are made configurable?
Web IDL requires various properties to be non-configurable, but the ones we care about here are just the ones that need to exist on objects that will have to be implemented using proxies, right?
Yes. And of those, only the ones that need to have magical behavior that violates the invariants associated with non-configurability. IIUC, this exempts WebIDL constants, as they never violate those invariants, no matter how weird their host object. Is this right?
I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
By "array host objects", do you mean, for example, NodeList? Is there a general definition of "array host object"?
I expect that "length" on array host objects should be treated however we treat "length" on regular arrays. Does this seem reasonable?
Why do array index own properties of array host objects need to be non-configurable? What would be lost if these were non-configurable but array host objects could refuse to delete them?
Constants map to non-configurable properties, but they exist only on interface objects and interface prototype objects, which are “normal” objects that wouldn’t need to be implemented using proxies.
Great! But as I mention above, I don't think these would be problematic in any case.
I will soon commit (probably today) a change to make indexed/named properties be exposed using custom [[GetOwnProperty]] and [[DefineOwnProperty]] handlers, which will mean objects that support indexed/named properties need to be proxies. The relevant issue with these is that these objects must also expose non-configurable properties that users set on them. For example:
<!DOCTYPE html> <form name=a></form> <script> var forms = document.getElementsByTagName("form"); Object.defineOwnProperty(forms, "z", { value: 1 }); </script>
forms needs to expose two properties, "0" and "a", whose values are the HTMLFormElement. In my upcoming change, [[GetOwnProperty]] will be specified to return this property descriptor for property "0":
{ [[Writable]]:false, [[Enumerable]]:true, [[Configurable]]:true, [[Value]]:theHTMLFormElement }
but to refuse to allow the property to be reconfigured.
Great! So long as this property claims to be configurable, it raises none of these issues.
It would be nice if the z property could be allowed to be defined and exposed as a non-configurable property, but this can’t be done without the proxy proposal changes being discussed here.
I'm curious why you think this would be nice?
But I don’t think it’s a huge loss if we just disallow non-configurable properties from being set on objects that support indexed/named properties. My plan was to silently make [[DefineOwnProperty]] for such objects assume [[Configurable]] was true on the descriptor it receives. Throwing would also be acceptable.
This is great! It looks like our hard issues are rapidly dissolving away.
We could do the same thing for the array host objects mentio[ne]d at the top of the email, too: have the length and individual array index properties be reported as configurable, but to have [[DefineOwnProperty]] refuse to reconfigure them.
This is really awesome and much appreciated. Thanks for the clarifications!
On Thu, Jun 16, 2011 at 5:17 PM, Cameron McCormack <cam at mcc.id.au> wrote:
(BTW, Web IDL is not in Last Call right now, but it is close.)
One question I forgot to ask, which is just my ignorance about the details of the proxy proposal. Say you have a prototype chain like this:
[object a] → [object b] → [Object prototype object] → null
a is a proxy object and b is a normal object. If you define a non- configurable property on b, and you look up that property on a, can it still be reported as non-configurable?
With the proxy proposal as it stands, no. I agree this is unfortunate. With proxies as augmented by < strawman:fixed_properties>, the
answer is yes, by fixing it on proxy a. This is differently unfortunate, in that it is now an own property on proxy a. This is indeed an interestingly problematic case. In fact, it's probably the most problematic case identified so far -- congrats.
Practically, do we know of a concrete case where it is a problem if proxy *a
- reports that the property in question is configurable? If not, I suggest that the lesser evil is to mis-report it as a configurable inherited property rather than a non-configurable own property. YMMV.
If this is a real problem, then we need to revisit < strawman:fixed_properties> in that
light. I can imagine introducing the idea of fixing an absence of a shadow, but I'd rather not.
On Jun 16, 2011, at 6:52 PM, Mark S. Miller wrote:
[object a] → [object b] → [Object prototype object] → null
a is a proxy object and b is a normal object. If you define a non- configurable property on b, and you look up that property on a, can it still be reported as non-configurable?
Perhaps I'm missing something here, but isn't the only way to actually inspect (report?) the attributes of a property is via Object.getOwnPropertyDescriptor so you can't use a to inspect the attributes of a property of b, regardless or whether or not b is a Proxy. I believe that the only operations on a that are sensitive to the value of the configurable attribute only apply to own properties.
On Thu, Jun 16, 2011 at 7:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Jun 16, 2011, at 6:52 PM, Mark S. Miller wrote:
[object a] → [object b] → [Object prototype object] → null
a is a proxy object and b is a normal object. If you define a non- configurable property on b, and you look up that property on a, can it still be reported as non-configurable?
Perhaps I'm missing something here, but isn't the only way to actually inspect (report?) the attributes of a property is via Object.getOwnPropertyDescriptor so you can't use a to inspect the attributes of a property of b, regardless or whether or not b is a Proxy. I believe that the only operations on a that are sensitive to the value of the configurable attribute only apply to own properties.
Was true until < harmony:extended_object_api>. Perhaps
we should simply withdraw the getPropertyDescriptor trap and avoid the whole problem? When in doubt, leave it out.
Cameron McCormack:
Web IDL requires various properties to be non-configurable, but the ones we care about here are just the ones that need to exist on objects that will have to be implemented using proxies, right?
Mark S. Miller:
Yes. And of those, only the ones that need to have magical behavior that violates the invariants associated with non-configurability. IIUC, this exempts WebIDL constants, as they never violate those invariants, no matter how weird their host object. Is this right?
That’s right, they really are constant, and they live on objects with no magical behaviour.
I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
By "array host objects", do you mean, for example, NodeList? Is there a general definition of "array host object"?
No, NodeList is a regular IDL interface which has getters/setters defined on it.
Array host objects are ones that feel a bit like native Arrays but are under host object control and which are represented by T[] types in IDL. They’re actually pretty much like a shorthand for defining an interface like NodeList, except that they also have the Array prototype object in their prototype chain.
(When we met late last year there was a hope to remove them from the spec due to them not being used anyway, but it turned out I was wrong, and they were being used.)
The T[] types: dev.w3.org/2006/webapi/WebIDL/#idl-array
How values of these types are represented using array host objects in ES: dev.w3.org/2006/webapi/WebIDL/#es-array
Maybe in the future when real subclassing of Array can be done, we can change the T[] type to do that instead of having completely separate objects.
I expect that "length" on array host objects should be treated however we treat "length" on regular arrays. Does this seem reasonable?
Yes.
Why do array index own properties of array host objects need to be non-configurable? What would be lost if these were non-configurable but array host objects could refuse to delete them?
Not much, except for author confusion if they see they are configurable but the object refuses to let them be reconfigured. The alternative, having them be non-configurable, non-writable (for read only arrays) data properties whose value changes also has an element of confusion about them, though. I think on balance that leaving them be configurable and refusing to reconfigure them is the lesser of the two confusions.
I agree with other parts of this thread about having length on Array instances be a non-configurable accessor property – that’s much closer to how I’d expect length properties to be defined if we were doing it from scratch, less magical. (So in fact we could do that for array host objects; it’s just written the way it is at the moment to mirror the native Array length property.)
In my upcoming change, [[GetOwnProperty]] will be specified to return this property descriptor for property "0":
{ [[Writable]]:false, [[Enumerable]]:true, [[Configurable]]:true, [[Value]]:theHTMLFormElement }
but to refuse to allow the property to be reconfigured.
Great! So long as this property claims to be configurable, it raises none of these issues.
Excellent.
It would be nice if the z property could be allowed to be defined and exposed as a non-configurable property, but this can’t be done without the proxy proposal changes being discussed here.
I'm curious why you think this would be nice?
Because it’s what the author requested. Presumably they have some reason for wanting to define a non-configurable property.
This wouldn’t work for objects with named properties, though, since if z is first exposed as a non-configurable property, and then an item is added to the collection later with the name "z", it will appear to have changed.
Allen Wirfs-Brock:
Perhaps I'm missing something here, but isn't the only way to actually inspect (report?) the attributes of a property is via Object.getOwnPropertyDescriptor so you can't use a to inspect the attributes of a property of b, regardless or whether or not b is a Proxy. I believe that the only operations on a that are sensitive to the value of the configurable attribute only apply to own properties.
Ah, that’s the point I overlooked.
On Thu, Jun 16, 2011 at 7:24 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Cameron McCormack:
Web IDL requires various properties to be non-configurable, but the ones
we care about here are just the ones that need to exist on objects that will have to be implemented using proxies, right?
Mark S. Miller:
Yes. And of those, only the ones that need to have magical behavior that violates the invariants associated with non-configurability. IIUC, this exempts WebIDL constants, as they never violate those invariants, no matter how weird their host object. Is this right?
That’s right, they really are constant, and they live on objects with no magical behaviour.
I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
By "array host objects", do you mean, for example, NodeList? Is there a general definition of "array host object"?
No, NodeList is a regular IDL interface which has getters/setters defined on it.
Array host objects are ones that feel a bit like native Arrays but are under host object control and which are represented by T[] types in IDL. They’re actually pretty much like a shorthand for defining an interface like NodeList, except that they also have the Array prototype object in their prototype chain.
(When we met late last year there was a hope to remove them from the spec due to them not being used anyway, but it turned out I was wrong, and they were being used.)
The T[] types: dev.w3.org/2006/webapi/WebIDL/#idl-array
How values of these types are represented using array host objects in ES: dev.w3.org/2006/webapi/WebIDL/#es-array
Maybe in the future when real subclassing of Array can be done, we can change the T[] type to do that instead of having completely separate objects.
Although strawman:proto_operator
still appears in "strawman", I believe we accepted its functionality (with concrete syntax still TBD) into ES-next "proposal" status, so it will become standard at the same time proxies will. Of course, the current WebIDL JS binding is targeting ES5 and so cannot directly rely on either proxies or "<|". But perhaps we should choose an ES5 host behavior for array host objects that anticipates reimplementing them using "<|" rather than proxies in the ES-next timeframe? Perhaps we should choose an ES5 behavior that leaves both future choices open? It sounds like we're already either there or almost there.
On Thu, Jun 16, 2011 at 7:24 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Cameron McCormack:
Web IDL requires various properties to be non-configurable, but the ones
we care about here are just the ones that need to exist on objects that will have to be implemented using proxies, right?
Mark S. Miller:
Yes. And of those, only the ones that need to have magical behavior that violates the invariants associated with non-configurability. IIUC, this exempts WebIDL constants, as they never violate those invariants, no matter how weird their host object. Is this right?
That’s right, they really are constant, and they live on objects with no magical behaviour.
I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
By "array host objects", do you mean, for example, NodeList? Is there a general definition of "array host object"?
No, NodeList is a regular IDL interface which has getters/setters defined on it.
Array host objects are ones that feel a bit like native Arrays but are under host object control and which are represented by T[] types in IDL. They’re actually pretty much like a shorthand for defining an interface like NodeList, except that they also have the Array prototype object in their prototype chain.
(When we met late last year there was a hope to remove them from the spec due to them not being used anyway, but it turned out I was wrong, and they were being used.)
The T[] types: dev.w3.org/2006/webapi/WebIDL/#idl-array
How values of these types are represented using array host objects in ES: dev.w3.org/2006/webapi/WebIDL/#es-array
Why does each array host object have its own toString method rather than using one inherited from Array.prototype? ES5 already says that Array.prototype.toString is "intentionally generic; it does not require that its this value be an Array object." So that should work fine.
On Thu, Jun 16, 2011 at 8:13 PM, Mark S. Miller <erights at google.com> wrote:
On Thu, Jun 16, 2011 at 7:24 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Cameron McCormack:
Web IDL requires various properties to be non-configurable, but the ones
we care about here are just the ones that need to exist on objects that
will have to be implemented using proxies, right?
Mark S. Miller:
Yes. And of those, only the ones that need to have magical behavior that violates the invariants associated with non-configurability. IIUC, this exempts WebIDL constants, as they never violate those invariants, no matter how weird their host object. Is this right?
That’s right, they really are constant, and they live on objects with no magical behaviour.
I believe that list is only:
- the length own property on array host objects
- each array index own property on an array host object
By "array host objects", do you mean, for example, NodeList? Is there a general definition of "array host object"?
No, NodeList is a regular IDL interface which has getters/setters defined on it.
Array host objects are ones that feel a bit like native Arrays but are under host object control and which are represented by T[] types in IDL. They’re actually pretty much like a shorthand for defining an interface like NodeList, except that they also have the Array prototype object in their prototype chain.
(When we met late last year there was a hope to remove them from the spec due to them not being used anyway, but it turned out I was wrong, and they were being used.)
The T[] types: dev.w3.org/2006/webapi/WebIDL/#idl-array
How values of these types are represented using array host objects in ES: dev.w3.org/2006/webapi/WebIDL/#es-array
Why does each array host object have its own toString method rather than using one inherited from Array.prototype? ES5 already says that Array.prototype.toString is "intentionally generic; it does not require that its this value be an Array object." So that should work fine.
Unfortunately, that same paragraph does go on to say that "Whether the toString function can be applied successfully to a host object is implementation-dependent." If this clause actually prevents using Array.prototype.toString here, which would be a shame, the how about having array host object inherit a common toString method from the array host object prototype object?
On Jun 16, 2011, at 7:44 PM, Mark S. Miller wrote:
Although strawman:proto_operator still appears in "strawman", I believe we accepted its functionality (with concrete syntax still TBD) into ES-next "proposal" status, so it will become standard at the same time proxies will.
I replaced the strawman's content with "See [harmony:object_literals#set_literal_prototype_operator]."
Mark S. Miller:
Although strawman:proto_operator still appears in "strawman", I believe we accepted its functionality (with concrete syntax still TBD) into ES-next "proposal" status, so it will become standard at the same time proxies will. Of course, the current WebIDL JS binding is targeting ES5 and so cannot directly rely on either proxies or "<|". But perhaps we should choose an ES5 host behavior for array host objects that anticipates reimplementing them using "<|" rather than proxies in the ES-next timeframe? Perhaps we should choose an ES5 behavior that leaves both future choices open? It sounds like we're already either there or almost there.
Maybe I was too eager: I guess subclassing array objects in this way wouldn’t let us define arrays that are read only, or fixed length, or whose values could be changed by the implementation at any time. Is that right? If so, maybe array subclassing isn’t what we want here, and using proxies with the Array prototype object in the prototype chain is the way to go.
On Jun 16, 2011, at 8:58 PM, Cameron McCormack wrote:
Mark S. Miller:
Although strawman:proto_operator still appears in "strawman", I believe we accepted its functionality (with concrete syntax still TBD) into ES-next "proposal" status, so it will become standard at the same time proxies will. Of course, the current WebIDL JS binding is targeting ES5 and so cannot directly rely on either proxies or "<|". But perhaps we should choose an ES5 host behavior for array host objects that anticipates reimplementing them using "<|" rather than proxies in the ES-next timeframe? Perhaps we should choose an ES5 behavior that leaves both future choices open? It sounds like we're already either there or almost there.
Maybe I was too eager: I guess subclassing array objects in this way wouldn’t let us define arrays that are read only, or fixed length, or whose values could be changed by the implementation at any time. Is that right? If so, maybe array subclassing isn’t what we want here, and using proxies with the Array prototype object in the prototype chain is the way to go.
Read-only and fixed length array subclassing should not require using a proxy to implement the subclass.
The value changing at any time should be ok as long as length is writable. Non-writable length value should not change (assuming non-configurable -- if configurable then reconfiguring to writable would allow new values to be put).
Mark S. Miller:
Why does each array host object have its own toString method rather than using one inherited from Array.prototype? ES5 already says that Array.prototype.toString is "intentionally generic; it does not require that its this value be an Array object." So that should work fine.
Good question! I’m not sure why I have that. It’s also gratuitously different from Array.prototype.string (nulls and undefineds would get stringified to "null"s and "undefined"s with my version).
Unfortunately, that same paragraph does go on to say that "Whether the toString function can be applied successfully to a host object is implementation-dependent." If this clause actually prevents using Array.prototype.toString here, which would be a shame, the how about having array host object inherit a common toString method from the array host object prototype object?
Having it on the array host host object prototype object would be better than as an own property on the array host object itself, you are right. But let’s just assume that the inheriting Array.prototype.toString just works (i.e., let’s have Web IDL require that it works). :)
Brendan Eich:
Read-only and fixed length array subclassing should not require using a proxy to implement the subclass.
I forgot a requirement: that the array be dense, too.
The value changing at any time should be ok as long as length is writable. Non-writable length value should not change (assuming non-configurable -- if configurable then reconfiguring to writable would allow new values to be put).
How about an array whose values might change, and whose length might change, but which must not be allowed to be changed by user script: a read only but not fixed length array host object?
This is essentially the same behaviour as a NodeList, without the additional methods on it. So the value of the length property must be able to be changed by the implementation. Same for the individual array element properties. I don’t think we can get this with regular data properties on a native Array object (or a subclass of one).
On Thu, Jun 16, 2011 at 9:29 PM, Cameron McCormack <cam at mcc.id.au> wrote:
Brendan Eich:
Read-only and fixed length array subclassing should not require using a proxy to implement the subclass.
I forgot a requirement: that the array be dense, too.
The value changing at any time should be ok as long as length is writable. Non-writable length value should not change (assuming non-configurable -- if configurable then reconfiguring to writable would allow new values to be put).
How about an array whose values might change, and whose length might change, but which must not be allowed to be changed by user script: a read only but not fixed length array host object?
This is essentially the same behaviour as a NodeList, without the additional methods on it. So the value of the length property must be able to be changed by the implementation. Same for the individual array element properties. I don’t think we can get this with regular data properties on a native Array object (or a subclass of one).
I agree. Sounds like a proxy. Glad we worked out the configurability issues.
On Jun 16, 2011, at 10:12 PM, Mark S. Miller wrote:
On Thu, Jun 16, 2011 at 9:29 PM, Cameron McCormack <cam at mcc.id.au> wrote: Brendan Eich:
Read-only and fixed length array subclassing should not require using a proxy to implement the subclass.
I forgot a requirement: that the array be dense, too.
The value changing at any time should be ok as long as length is writable. Non-writable length value should not change (assuming non-configurable -- if configurable then reconfiguring to writable would allow new values to be put).
How about an array whose values might change, and whose length might change, but which must not be allowed to be changed by user script: a read only but not fixed length array host object?
This is essentially the same behaviour as a NodeList, without the additional methods on it. So the value of the length property must be able to be changed by the implementation. Same for the individual array element properties. I don’t think we can get this with regular data properties on a native Array object (or a subclass of one).
Yes, NodeList requires Proxy.
I agree. Sounds like a proxy. Glad we worked out the configurability issues.
Assuming Tom's fixed properties proposal (the latest) is approvied -- right?
Le 17/06/2011 02:38, Mark S. Miller a écrit :
On Thu, Jun 16, 2011 at 4:54 PM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
I think that the deeper question we have to deal with now is the exact semantics of configurability when it comes to "non-natural objects" (array, host objects, proxies. Is there an official name for them in the spec?).
Allen and I just had a long phone conversation about these issues. We ended up speaking in terms of a three part distinction:
normal native objects, i.e., those native objects or functions whose internal methods operate in the normal non-magical way. Normal native functions includes both built-in functions (e.g. Object.prototype.valueOf) and functions written in JavaScript (e.g., what "function(){}" evaluates to).
abnormal native objects, like arrays, whose internal methods are still fully specified by the ES5 spec, but whose behavior is peculiar / magical.
non-native objects, i.e., "host objects". We started our conversation with a long digression into spec legalese about whether an object could be neither native nor host. I still take the position that there cannot be by definition. I don't know whether Allen ended up agreeing. But we agreed that there are not currently any such taxonomy breaking objects, and so for our purposes we could use "non-native" and "host" as synonyms. "non-native" is less confusing since it avoids tempting one into inventing a fourth category.
In ES6, proxies create an open ended set of abnormal native objects whose behavior is defined by JavaScript code. To ES5 code in an ES6 environment, such proxies are non-native, in that their ES5-level behavior is not fully specified by the ES5 spec.
Noted.
Currently, ES5 - 8.6.2 says: "The [[GetOwnProperty]] internal method of a host object must conform to the following invariants for each property of the host object: (...five bullets points...)" I'd like to ask a few questions on this part of the spec: Why are we expecting anything from host (all if including arrays?) objects at all? (I know this one is naive and a bit provocative, but I actually wonder)
Because it's in the spec, and we should all ensure that any violations of the spec stand out like a sore thumb in standard test suites.
Why are we expecting host (all if including arrays?) objects to respect these particular invariants (this is actually 5 questions) and no other?
Because these invariants are in the spec.
Put another way, do you expect Safari to eventually fix the December "valueOf" bug they haven't yet even confirmed? Why?
Should we ever stop expecting anyone to pay attention to the spec, I suggest that TC39 disband.
Another useful line of question is: Why are these invariants in the spec? This is useful to discuss, but is a separate matter.
This is what I was asking, sorry for the confusion. Be sure that if I was ok with implementations not caring about the spec, I wouldn't have joined es-discuss and I wouldn't file bugs on test262 :-p If things are in the spec, they should be followed by implementations (and having implementors in the spec body certainly helps in that process even if it's apparently not entirely an easy battle).
However, my questions were the rationals of the spec. Why each of these 5 bullets points is in the spec? What is the rational behind each of these? What discussion/arguments led to these particular invariants? Why no other invariant?
(I understand that the confusion may have come from the formulation of my questions. I asked "Why are /we/...". By "we", I meant "people who discuss the spec", and you apparently interpreted "implementors")
Sorry again for the confusion.
Le 17/06/2011 08:31, Brendan Eich a écrit :
2011/6/16 David Bruant <david.bruant at labri.fr>
I think I see also a potential security issue. In Tom's code, getters and setters of the non-configurable properties trigger code of what was in the handler. This is useful as a user to keep triggering the get and set traps, but it also leaks a reference to these functions (after a call to Object.getOwnPropertyDescriptor). In the current proposal, before a property becomes non-configurable, there is no access to any trap (unless having access to the object which implies having indirect access to all traps or the handler itself). After becoming a non-configurable accessor property, the get and set trap applied to the non-configurable properties become available as independant functions that can be passed around. Currently, one can revoke the access to an object with a membrane/wrapper, but with the leak, I think that all getter/setters will have to be wrapped as well (their call trap in particular)? Ok, maybe there is no security issue, but it adds a little bit more work.
I don't think there is a potential "leak" in the case of membranes: the non-configurable property's get/set traps forward to handler.get/set, which, if |handler| refers to a revoked proxy, will throw and not penetrate the membrane, as expected.
2011/6/17 David Bruant <david.bruant at labri.fr>
Le 17/06/2011 08:31, Brendan Eich a écrit :
On Jun 16, 2011, at 10:12 PM, Mark S. Miller wrote:
I agree. Sounds like a proxy. Glad we worked out the configurability issues.
Assuming Tom's fixed properties proposal (the latest) is approvied -- right?
I think that there are still pending issues. One is figuring out why the ES5 invariants on abnormal+non-native object properties configurability are in the spec. If we assume they're legitimate, then we're pretty much done (not 100%, see below) As far as I'm concerned, I haven't read arguments enforcing a belief that these invariants on abnormal+non-native objects really have a value and why all implementations should follow them (Once again, they are in the spec, so they should be respected by implementors. But should they be in the spec? What is the added value of these 5 invariants? Why no other invariant?)
For the moment, apparently, two cases are problematic:
- A forwarding proxy cannot fully forward, because it cannot honestly tell whether a property in the target object is configurable or not (it will always pretend it's configurable). Since the forwarding proxy pattern is probably the most important, this seems to be a major issue.
I agree there doesn't seem to be a generic solution. However, strawman:fixed_properties does make it possible for forwarding proxies to deal with certain cases:
- if target has a non-configurable accessor property, the proxy can create a fixed non-configurable accessor whose get/set attributes invoke handler.get/set, as shown on the strawman page.
- if target has a non-writable, non-configurable data property (typically a property just acting as a constant), the proxy can create its own fixed copy of that property. IINM, non-writable non-configurable properties can't change, so the copies cannot grow out of sync.
- if target has a writable, non-configurable data property, the proxy could create a fixed copy. In its defineProperty trap, it has to make sure to forward the defineProperty operation to the target, in case its writable property is switched from true to false. The one thing the proxy cannot intercept are changes made to the original property on its |target|. I.e. the proxy may still advertise its fixed property as {writable:true, configurable:false} while the target's property may have been changed to {writable:false,configurable:false} by some other code.
Also, trying to create a non-configurable property on the target will not be
possible (unless using custom property descriptor attribute? :-s), because with the fixed property proposal, such a property get stuck at the proxy level. No trap is called, no forwarding.
My latest addition to the proposal would allow proxy handlers to still intercept defineProperty, even on fixed properties, so they could propagate changes to fixed properties to their target.
- Issue with a proxy saying that an inherited property is a non-configurable property (getPropertyDescriptor trap). Basically, getPropertyDescriptor may say that a property is configurable while getPrototypeOf+**getOwnPropertyDescriptor(on a native object which allows non-configurable property) may say that the property is not-configurable. They would both talk about the same property and be inconsistent.
Yes, this is a thorny issue. We could drop the getPropertyDescriptor operation & trap. What we lose by that is that proxies would be unable to fully emulate inherited properties. That is: while a proxy's get/set traps may still emulate "foo" as an inherited property (i.e. proxy.foo returns a value, but Object.getOwnPropertyDescriptor(proxy,'foo') returns undefined), the illusion would be broken once the proxy's inheritance chain is inspected (i.e. code may find out that there's actually no "foo" property on the proxy's prototype chain). Actually, one could argue that even without getPropertyDescriptor proxies already cannot emulate prototype inheritance, since they can't virtualize Object.getPrototypeOf.
Cheers, Tom
A branch of this thread took for granted the current spec invariants on
Le 17/06/2011 16:35, Tom Van Cutsem a écrit :
2011/6/16 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>
I think I see also a potential security issue. In Tom's code, getters and setters of the non-configurable properties trigger code of what was in the handler. This is useful as a user to keep triggering the get and set traps, but it also leaks a reference to these functions (after a call to Object.getOwnPropertyDescriptor). In the current proposal, before a property becomes non-configurable, there is no access to any trap (unless having access to the object which implies having indirect access to all traps or the handler itself). After becoming a non-configurable accessor property, the get and set trap applied to the non-configurable properties become available as independant functions that can be passed around. Currently, one can revoke the access to an object with a membrane/wrapper, but with the leak, I think that all getter/setters will have to be wrapped as well (their call trap in particular)? Ok, maybe there is no security issue, but it adds a little bit more work.
I don't think there is a potential "leak" in the case of membranes: the non-configurable property's get/set traps forward to handler.get/set, which, if |handler| refers to a revoked proxy, will throw and not penetrate the membrane, as expected.
Yep, you're perfectly right. My mistake. It still does leak for the non-membrane case though, but that's a smaller problem, I think.
On Fri, Jun 17, 2011 at 4:53 AM, David Bruant <david.bruant at labri.fr> wrote:
** Le 17/06/2011 02:38, Mark S. Miller a écrit :
Another useful line of question is: Why are these invariants in the spec? This is useful to discuss, but is a separate matter.
This is what I was asking, sorry for the confusion. [...] Why each of these 5 bullets points is in the spec?
Hi David, thanks for asking. I think of them as seven points, divided into the first four and the last three. The last two have no bullets on them in the text but are the two paragraphs following the bulleted list at the end of 8.6.2. Renumbering, so that we can refer back to them:
- Universal property constraints:
a) If a property is described as a data property and it may return different values over time, then either or both of the [[Writable]] and [[Configurable]] attributes must be true even if no mechanism to change the value is exposed via the other internal methods.
b) If a property is described as a data property and its [[Writable]] and [[Configurable]] are both false, then the SameValue (according to 9.12) must be returned for the [[Value]] attribute of the property on all calls to [[GetOwnProperty]].
c) If the attributes other than [[Writable]] may change over time or if the property might disappear, then the [[Configurable]] attribute must be true. [The "disappear" clause here is also partially a universal object constraint, and so is relevant to the next list. Alternatively, we could consider non-existence to be a state of a property.]
d) If the [[Writable]] attribute may change from false to true, then the [[Configurable]] attribute must be true.
- Universal object constraints:
a) If the value of the host object‘s [[Extensible]] internal property has been observed by ECMAScript code to be false, then if a call to [[GetOwnProperty]] describes a property as non-existent all subsequent calls must also describe that property as non-existent.
b) The [[DefineOwnProperty]] internal method [...] must not permit the addition of a new property to a [...] object if the [[Extensible]] internal property of that [...] object has been observed by ECMAScript code to be false.
c) If the [[Extensible]] internal property of that [...] object has been observed by ECMAScript code to be false then it must not subsequently become true.
["host" replaced with [...] above for the following reasons]
Although the ES5.1 text describes these only as constraints on non-native (i.e., "host") objects, I label these as "universal" above, since the detailed behavior that ES5.1 specifies for all native objects, both normal and abnormal, all imply these constraints. Put another way, if any object O on system X violates these constraints, then system X is not a conformant ES5.1 system. Either O is native, in which case it violates some specific specified native behavior spec, or O is non-native, in which case it directly violates the above text from 8.6.2.
What is the rational behind each of these?
One of JavaScript's great virtues is that it is a highly reflective language, leading programmers and framework builders to engage in a wide range of meta-programming patterns. One of JavaScript's great flaws is that its property state space is complex, leading such meta-programmers into a case explosion. Prior to ES5, there were so few guarantees of any stability properties over this state space, especially for non-native ("host") objects, that robust programming was almost impossible.
For example, Caja on ES3, to uphold its own invariants, must handle non-native objects very carefully. Given the unspecified nature of native objects, we cannot both handle these objects and maintain our security, while depending only on specified behavior. To figure out how to handle them safely enough, we rely on lore and testing as well, relying on observed regularities not guaranteed by any spec. To diminish the cases we need to worry about, Caja pays substantial costs to ensure that untrusted code never gets a direct reference to any non-native object, not even "alert". This ice is too thin.
How does ES5.1 help make robust meta-programming practical? The following text from Chapter 4 of erights.org/talks/thesis seems relevant:
In the human world, when you plan for yourself, you make assumptions about
future situations in which your plan will unfold. Occasionally, someone else’s plan may interfere with yours, invalidating the assumptions on which your plan is based. To plan successfully, you need some sense of which assumptions are usually safe from such disruption. But you do not need to anticipate every possible contingency. If someone does something you did not expect, you will probably be better able to figure out how to cope at that time anyway.
When programmers write programs, they express plans for machines to execute. To formulate such plans, programmers must also make assumptions. When separately formulated plans are composed, conflicting assumptions can cause the run-time situation to become inconsistent with a given plan’s assumptions, corrupting its continued execution. Such corrupted executions likely violate assumptions on which other programs depend, potentially spreading corruption throughout a system. To program successfully, programmers use abstraction and modularity mechanisms to limit (usually implicitly) which assumptions must be made, and to structure these assumptions so they are more likely to mesh without conflict. Beyond these assumptions, correct programs must handle all remaining relevant contingencies. *The case analysis burden this requires must be kept reasonable, or robust programming becomes impractical. *
[emphasis added]
In ES5.1, across normal native, abnormal native, and non-native, we reliably contain the property state space to < es3.1:attribute_states>. Only the
transition labels in this diagram are specific to normal native. If we erase only these transition labels, keeping the state labels and everything else, then this diagram is universal and directly reflects the "Universal property constraints" from 8.6.2. Open problem: What are the non-equivalences if any between this diagram and the universal property constraints? How are these constraints either stronger or weaker than the diagram? If there are substantial non-equivalences, then we should revisit this.
Once we erase the transition labels, then we can without further loss of info also erase the blue transition arc currently labeled "put" and keep only the two transitions currently labeled "defineProperty". Even this state space is distressingly large and hard to think about. But we've learned to deal with it. Let's not make it any worse.
Le 17/06/2011 18:45, Mark S. Miller a écrit :
What is the rational behind each of these?
One of JavaScript's great virtues is that it is a highly reflective language, leading programmers and framework builders to engage in a wide range of meta-programming patterns. One of JavaScript's great flaws is that its property state space is complex, leading such meta-programmers into a case explosion. Prior to ES5, there were so few guarantees of any stability properties over this state space, especially for non-native ("host") objects, that robust programming was almost impossible.
For example, Caja on ES3, to uphold its own invariants, must handle non-native objects very carefully. (...). To diminish the cases we need to worry about, Caja pays substantial costs to ensure that untrusted code never gets a direct reference to any non-native object, not even "alert". This ice is too thin.
It may be a naive question, but why should untrusted code be prevented from getting a reference to a non-native object? Do you have a concrete example of threat this protects from? ... well... if untrusted code has access to "alert", it can do "while(1) alert();" and prevent trusted code from running. But all other non-native objects?
How did each of the 4 "Universal property constraints" made Caja's work easier?
For NodeLists proxy emulation, the "length" property cannot be changed manually (as far as I know), so I'd say that its writable has to be false (tell me if I'm wrong to think that). The "length" value can however change due to DOM tree transformations. For this reason, configurable has to be true as per Universal property constraints 1) a). However you cannot really configure it in the sense that you can't neither delete it nor re-configure it. "configurable" does not really mean that the property is configurable (not quote) on host objects.
I'm just puzzled by the wording I think. "configurable:true" on normal native objects means "this property is configurable (delete + Object.defineProperty with any property descriptor)". However, for abnormal+non-native objects, the semantics of "configurable" is constrainted to the Universal property constraints, but does not give any insight on what you can do with the property. Exact semantics (what you can do with the property) is left at the discretion of the object implementor. The confusing part for me is probably the "-able" suffix which sounds like giving me an information on what I can do with the property (like "enumerable" do), but this is actually not the case at all for abnormal+non-native objects. It's not that big of a deal for abnormal native objects since I can read the spec to know what to expect from these.
On a last note, array.length already do respect all Universal property constraints since writable is true. Or am I missing something?
Thanks for your explanations,
Le 17/06/2011 16:51, Tom Van Cutsem a écrit :
2011/6/17 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>
Le 17/06/2011 08:31, Brendan Eich a écrit : On Jun 16, 2011, at 10:12 PM, Mark S. Miller wrote: I agree. Sounds like a proxy. Glad we worked out the configurability issues. Assuming Tom's fixed properties proposal (the latest) is approvied -- right? I think that there are still pending issues. One is figuring out why the ES5 invariants on abnormal+non-native object properties configurability are in the spec. If we assume they're legitimate, then we're pretty much done (not 100%, see below) As far as I'm concerned, I haven't read arguments enforcing a belief that these invariants on abnormal+non-native objects really have a value and why all implementations should follow them (Once again, they are in the spec, so they should be respected by implementors. But should they be in the spec? What is the added value of these 5 invariants? Why no other invariant?) For the moment, apparently, two cases are problematic: - A forwarding proxy cannot fully forward, because it cannot honestly tell whether a property in the target object is configurable or not (it will always pretend it's configurable). Since the forwarding proxy pattern is probably the most important, this seems to be a major issue.
I agree there doesn't seem to be a generic solution. However, strawman:fixed_properties does make it possible for forwarding proxies to deal with certain cases:
- if target has a non-configurable accessor property, the proxy can create a fixed non-configurable accessor whose get/set attributes invoke handler.get/set, as shown on the strawman page.
Actually, for this very case, you could take the getter and setter of the target property and use them for the fixed proxy property (since they won't change on the target). It would solve the asymetry that comes from calling handler.get/set when the non-configurable proxy property is an accessor and not calling these traps when it's a data property.
- if target has a non-writable, non-configurable data property (typically a property just acting as a constant), the proxy can create its own fixed copy of that property. IINM, non-writable non-configurable properties can't change, so the copies cannot grow out of sync.
No issue with constants, indeed.
- if target has a writable, non-configurable data property, the proxy could create a fixed copy. In its defineProperty trap, it has to make sure to forward the defineProperty operation to the target, in case its writable property is switched from true to false. The one thing the proxy cannot intercept are changes made to the original property on its |target|. I.e. the proxy may still advertise its fixed property as {writable:true, configurable:false} while the target's property may have been changed to {writable:false,configurable:false} by some other code.
Symetrically to the forwarding of defineProperty, maybe that getOwnPropertyDescriptor could be forwarded too (requires to call the getOwnPropertyDescriptor trap for non-configurable properties) and the return value could be used to redefine writable on the proxy fixed property if necessary.
var t = Object.create(null, {a:{value:1, configurable:false, writable:true}}); var p = ForwardingProxy(t); // p has a fixed property 'a'. Object.getOwnPropertyDescriptor(p, 'a'); // {value:1, configurable:false, writable:true, enumerable:false}
Object.defineProperty(t, 'a', {value:1, configurable:false, writable:false});
Object.getOwnPropertyDescriptor(p, 'a'); // Currently unchanged as you said: {value:1, configurable:false, writable:true, enumerable:false} // because the trap is not called. // My suggestion is that the getOwnPropertyDescriptor trap should be called // In that case, it would forward to the target and return the correct target property descriptor // {value:1, configurable:false, writable:false, enumerable:false} // Since the pd cannot be returned right away without risking to violate some invariants, // the engine should check whether the returned pd can replace the current pd // without violating an invariant. // In that case, changing writable from false to true of a non-configurable property is legitimate. // The engine changes the fixed property descriptor with the returned one (reject if the wanted // change violates an invariant) and actually return to the program this invariant.
Also, trying to create a non-configurable property on the target will not be possible (unless using custom property descriptor attribute? :-s), because with the fixed property proposal, such a property get stuck at the proxy level. No trap is called, no forwarding.
My latest addition to the proposal would allow proxy handlers to still intercept defineProperty, even on fixed properties, so they could propagate changes to fixed properties to their target.
That's what I was missing. Propagating getOwnPropertyDescriptor is the symetric idea.
- Issue with a proxy saying that an inherited property is a non-configurable property (getPropertyDescriptor trap). Basically, getPropertyDescriptor may say that a property is configurable while getPrototypeOf+getOwnPropertyDescriptor(on a native object which allows non-configurable property) may say that the property is not-configurable. They would both talk about the same property and be inconsistent.
Yes, this is a thorny issue. We could drop the getPropertyDescriptor operation & trap. What we lose by that is that proxies would be unable to fully emulate inherited properties. That is: while a proxy's get/set traps may still emulate "foo" as an inherited property (i.e. proxy.foo returns a value, but Object.getOwnPropertyDescriptor(proxy,'foo') returns undefined), the illusion would be broken once the proxy's inheritance chain is inspected (i.e. code may find out that there's actually no "foo" property on the proxy's prototype chain). Actually, one could argue that even without getPropertyDescriptor proxies already cannot emulate prototype inheritance, since they can't virtualize Object.getPrototypeOf.
For the getPropertyDescriptor trap, if the returned descriptor has "configurable:false", the engine could climb the prototype chain to check that "configurable:false" is actually legitimate (only configurability). May be costly?
To summurize fixed propeties property-specific trap behaviors, one could say that:
- defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way)
- delete rejects
- All other property-specific traps have the default derived trap behavior (all the remaining property-specific traps are derived).
Does that sounds right?
Le 17/06/2011 21:41, David Bruant a écrit :
To summurize fixed propeties property-specific trap behaviors, one could say that:
- defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way)
- delete rejects ...wait a minute. There is no invariant imposing this. There is an invariant asking to a property that might /disappear/ to show "configurable:true" but it does not prevent from manually deleting a property with "configurable:false", does it? I agree that this is the expectation we have from a native object, but I am not sure this is enforced by the spec on abnormal native or non-native (host) objects.
Regardless of the very delete trap issue, the more I think about it, the more I realize that we're slightly moving to a position where we put invariant enforcement code: the getOwnPropertyDescriptor trap imposes "configurable:true", unless it has been noticed that this very property has been noticed (or set) with "configurable:false" before, in which case, check against previously returned/set descriptor, etc. Would it cost that much more to just call traps and put the invariant enforcement code at the relevant places instead of the bicephal property fixing proposal?
Also, I think that there might be missing invariants like:
- if for a property, [[Configurable]] has been set to false or observed as false, it must stay like so (otherwise, all other invariants relying on "configurable:false" are just pointless)
- The first observed value of [[Prototype]] of an object must remain throughout the program lifetime
- etc.
On Fri, Jun 17, 2011 at 1:50 PM, David Bruant <david.bruant at labri.fr> wrote:
** Le 17/06/2011 21:41, David Bruant a écrit :
To summurize fixed propeties property-specific trap behaviors, one could say that:
- defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way)
- delete rejects
...wait a minute. There is no invariant imposing this. There is an invariant asking to a property that might /disappear/ to show "configurable:true" but it does not prevent from manually deleting a property with "configurable:false", does it?
If the delete succeeds, then the property disappears, so yes, this would be prohibited.
I agree that this is the expectation we have from a native object, but I am not sure this is enforced by the spec on abnormal native or non-native (host) objects.
I'm not sure that the delete operation must throw, though I hope it must. But it cannot result in a non-configurable property disappearing.
Regardless of the very delete trap issue, the more I think about it, the more I realize that we're slightly moving to a position where we put invariant enforcement code: the getOwnPropertyDescriptor trap imposes "configurable:true", unless it has been noticed that this very property has been noticed (or set) with "configurable:false" before, in which case, check against previously returned/set descriptor, etc. Would it cost that much more to just call traps and put the invariant enforcement code at the relevant places instead of the bicephal property fixing proposal?
I think the loophole idea suggests this as well.
Also, I think that there might be missing invariants like:
- if for a property, [[Configurable]] has been set to false or observed as false, it must stay like so (otherwise, all other invariants relying on "configurable:false" are just pointless)
Oops. You're right, this is missing. It is a spec bug and needs to be added to the errata.
- The first observed value of [[Prototype]] of an object must remain throughout the program lifetime
For ES5.1, we explicitly do not prohibit changes to the [[Prototype]] on extensible objects. Neither do we provide any way to change it, but implementations that allow such changes do not thereby violate ES5.1. I believe we should prohibit this in ES-next, but I cannot say we yet have consensus on that.
We do prohibit changes to [[Prototype]] on frozen objects. I think we prohibit such changes on all non-extensible objects, but no longer remember for sure.
Mark, I don't want to argue about the specific invarients you listed. They generally make sense. However I don't believe that they are as firmly embedded in the fabric of ES as your believe.
Prior, to ES5 attribute value were not reified and there were no way for ES code to specify attribute values for properties. I believe (I'm where where I can't check) that [[DefineOwnProperty]] didn't exist prior to ES5. Argurably Host objects were not required to even have property attributes. They simply needed to provide [[PUT/Get]] behaviors and there were minimal constraints on what they implemented. Some widely used host objest (DOM NodeList) have behaviors that are tough to match to the normal interpretation of the attributes.
In ES5 we added [[DefineOwnProperty]] as an internal method and incorporated into it the attribute transtion rules you quoted. However as an internal method it can have other "native" implementations that implement other rules. I don't believe there is any contrary spec. language. We did put in the host object invarients but I don't believe we actually talked about expanding there applicability to all objects.
I'm not saying it is necessarily a bad idea. I just don't think it is implicit in ES5.
On Fri, Jun 17, 2011 at 4:52 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Mark, I don't want to argue about the specific invarients you listed. They generally make sense. However I don't believe that they are as firmly embedded in the fabric of ES as your believe.
Prior, to ES5 [...bunch of stuff about ES3...]
Hi Allen,
It sounds like you think I'm making a point about ES that spans at least ES3 and ES5. I'm not. I'm making a point only about ES5. ES3 was a mess and we did an extraordinary job, starting under the ES3.1 banner and continuing under the ES5 banner, of cleaning up that mess. So if we wish to speak historically, my point spans ES3.1 to ES5.1.
In ES5 we added [[DefineOwnProperty]] as an internal method and incorporated into it the attribute transtion rules you quoted. However as an internal method it can have other "native" implementations that implement other rules. I don't believe there is any contrary spec. language.
ES5.1 states the specification of all normal and abnormal native objects. I didn't feel we needed spec language to state the invariants that all these specific native specifications upheld, since they did uphold them.
We did put in the host object invarients but I don't believe we actually talked about expanding there applicability to all objects.
I'm not saying it is necessarily a bad idea. I just don't think it is implicit in ES5.
It is because I was careful to ensure, starting with our ES3.1 conversations, that all these specific native specs upheld these invariants. I certainly admit that what these invariants were, and why they were important, are all more clear to me now than they were then. I was discovering the invariants that mattered by intuition while we were doing the ES3.1 design.
We actually did better than I expected. In July 2009 when we wrote "Support a statically verifiable, object-capability secure subset." into the Harmony Goals at harmony:harmony#goals, we
had already achieved this goal in ES5 and I didn't know it. I had been groping towards this goal during the ES5 design hoping to make incremental progress, but I didn't know we had crossed the finish line until December 2009 when I wrote the first SES prototype at < code.google.com/p/es-lab/source/detail?r=3&path=/trunk/src/ses/initSES.js>.
Current development has moved to < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses
.
(David, the following should also serve as a partial answer to the question of your's that I postponed)
As an example where I'm already exploiting these invariants: At < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#614>
you will find
/**
- Read the current value of base[name], and freeze that property as
- a data property to ensure that all further reads of that same
- property from that base produce the same value.
- <p>The algorithms in startSES traverse the graph of primordials
- multiple times. These algorithms rely on all these traversals
- seeing the same graph. By freezing these as data properties the
- first time they are read, we ensure that all traversals see the
- same graph.
- <p>The frozen property should preserve the enumerability of the
- original property. */ function read(base, name) { var desc = Object.getOwnPropertyDescriptor(base, name); if (desc && 'value' in desc && !desc.writable && !desc.configurable) { return desc.value; }
var result = base[name];
try {
Object.defineProperty(base, name, {
value: result, writable: false, configurable: false
});
} catch (ex) {
cantNeuter.push({base: base, name: name, err: ex});
}
return result;
}
[again with better formatting of the code. The previous one also looked good when I sent it. If this one arrives looking crappy, please just follow the link to the repository site. Email mangling of html befuddles me.]
On Fri, Jun 17, 2011 at 6:03 PM, Mark S. Miller <erights at google.com> wrote:
[again with better formatting of the code. The previous one also looked good when I sent it. If this one arrives looking crappy, please just follow the link to the repository site. Email mangling of html befuddles me.]
Crap. Please look at < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/startSES.js#614
An even better example where Tom and I used these invariants, even if only for an illustrative example, is < harmony:proxies#an_eventual_reference_proxy>.
The old text at the bottom of this old code says:
"Note: localFarReferenceMaker should work even if obj is a host
object."
Were these invariants relaxed, abstractions such as this would either be incorrect or be much less precise and therefore less useful.
Le 18/06/2011 05:35, Mark S. Miller a écrit :
An even better example where Tom and I used these invariants, even if only for an illustrative example, is harmony:proxies#an_eventual_reference_proxy. The old text at the bottom of this old code says:
"Note: |localFarReferenceMaker| should work even if |obj| is a
host object."
A couple of things on this code:
-
var pds = Object.getProperties(obj); => strawman:extended_object_api suggests the name "Object.getPropertyDescriptors"
-
var nonConfigurableOwnProperties = Object.freeze(nonConfigurableProperties.filter(function(name) { return !!({}).getOwnPropertyDescriptor.call(obj, name); }); => I think the filter should be ({}).hasOwnProperty.call(obj, name); (no
need for !! since hasOwnProperty already returns a boolean)
-
isEnumerableOwn => This is not a trap (anymore?)
-
In "enumerate" and "keys" traps, you do not take "enumerable" into account, is it on purpose?
-
This abstraction does not take into account whether an property of obj has later been set to "configurable:false". (But I don't know purpose of an eventual reference, so it might be on purpose)
-
There are no defineProperty, getPropertyNames or getOwnPropertyDescriptor traps (which are fundamental traps). Is it on purpose?
Were these invariants relaxed, abstractions such as this would either be incorrect or be much less precise and therefore less useful.
What is the purpose of this abstration? How is it used?
On a slightly unrelated note, you use setTimeout(f,0) several times. I'd like to point out that Malte Ulb very recently showed (jsperf.com/postmessage) that setTimeout(f,0) is not very good performance-wise and that posting a message to oneself (window.postmessage) is far more efficient. This is neither part of ECMAScript, nor supported by all browsers (should be by IE8, but the test doesn't work for a reason I have tried to find and given up on), but in relevant contexts, i'll stop using setTimeout(f,0).
yes, that code is just a stale illustrative example. This abstraction itself is not being used, and the code predates many changes to the proxy API that render it invalid. And even then, it was sloppy in ways you point out, e.g, regarding enumerability.
However, it is a good example of the kinds of abstraction that one could write using the modern proxy API, if we don't weaken the invariants it relies on. And again, the "read" function I pointed to is a production example which also relies on these invariants.
Le 19/06/2011 00:30, Mark S. Miller a écrit :
Hi David, yes, that code is just a stale illustrative example. This abstraction itself is not being used, and the code predates many changes to the proxy API that render it invalid. And even then, it was sloppy in ways you point out, e.g, regarding enumerability.
Sorry if i was a little harsh on my e-mail. It wasn't my intention. The critiques on the code were just meant to keep examples consistent to not puzzle newcomers to the wiki.
However, it is a good example of the kinds of abstraction that one could write using the modern proxy API, if we don't weaken the invariants it relies on. And again, the "read" function I pointed to is a production example which also relies on these invariants.
But the very question I am asking is: do we need this kind of abstractions? the "read" function you showed uses some invariants in production for SES. So these invariants should be kept (unless equivalent robustness can be guaranteed with weaker invariants). However, do all invariants have such use? I am sorry to ask this question this way, but this is all very new to me and I'm blind when it comes to justifying that such or such invariant is required to build such or such things which is used (or should be or may be) in such and such context.
By the way, I am a bit puzzled by the read function. The comment on top says "The frozen property should preserve the enumerability of the original property.". However, l.636-638, the call to defineProperty doesn't set "enumerable" (implicitly set to 'false'). Shouldn't it be enumerable:desc.enumerable, or am I missing something?
Le 17/06/2011 23:21, Mark S. Miller a écrit :
On Fri, Jun 17, 2011 at 1:50 PM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
Le 17/06/2011 21:41, David Bruant a écrit :
To summurize fixed propeties property-specific trap behaviors, one could say that: - defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way) - delete rejects
...wait a minute. There is no invariant imposing this. There is an invariant asking to a property that might /disappear/ to show "configurable:true" but it does not prevent from manually deleting a property with "configurable:false", does it?
If the delete succeeds, then the property disappears, so yes, this would be prohibited.
Ok. "Disappear" seemed to imply "not manually" to me, but I'm not a native English speaker.
I agree that this is the expectation we have from a native object, but I am not sure this is enforced by the spec on abnormal native or non-native (host) objects.
I'm not sure that the delete operation must throw, though I hope it must. But it cannot result in a non-configurable property disappearing.
Actually, the spec is not very precise on this point. ES5.1 11.4.1 the delete operator "In addition, if a delete operator occurs within strict mode code and the property to be deleted has the attribute { [[Configurable]]: false }, a TypeError exception is thrown." An exception is thrown, however, nothing is said on the destiny of the property. Has it been deleted anyways? One can assume not, but the spec leaves it implicit.
- The first observed value of [[Prototype]] of an object must remain throughout the program lifetime
For ES5.1, we explicitly do not prohibit changes to the [[Prototype]] on extensible objects. Neither do we provide any way to change it, but implementations that allow such changes do not thereby violate ES5.1. I believe we should prohibit this in ES-next, but I cannot say we yet have consensus on that.
We do prohibit changes to [[Prototype]] on frozen objects. I think we prohibit such changes on all non-extensible objects, but no longer remember for sure.
The spec words are "if [[Extensible]] is false the value of the [[Class]] and [[Prototype]] internal properties of the object may not be modified."..."Implementation specific extensions that modify [[Class]], [[Prototype]] or [[Extensible]] must not violate the invariants defined in the preceding paragraph." Based on that a decision has to be made between:
- changes to [[Prototype]] even on extensible objects should be prohibited
- getPrototypeOf should be a proxy trap.
And I am also going to ask the same question as usual: is this invariant on extensible objects and prototype actually used (in SES for instance)?
2011/6/17 David Bruant <david.bruant at labri.fr>
** Symetrically to the forwarding of defineProperty, maybe that getOwnPropertyDescriptor could be forwarded too (requires to call the getOwnPropertyDescriptor trap for non-configurable properties) and the return value could be used to redefine writable on the proxy fixed property if necessary.
Yep, trapping getOwnPropertyDescriptor on fixed properties seems to close the loop.
However, I find it strange that the return value of getOwnPropertyDescriptor would actually implicitly side-effect the fixed property of the proxy. I naturally think of defineProperty as a side-effecting trap, not so for getOwnPropertyDescriptor. Perhaps it should only check the return value for consistency with the fixed descriptor, without side-effects.
One can still achieve 'synchrony' between a target's descriptor and the proxy's fixed descriptor by explicitly setting the property in the getOwnPropertyDescriptor trap (at least then the side-effect is explicit):
// assume 'name' already refers to a fixed property within 'proxy' function getOwnPropertyDescriptor(name, proxy) { var desc = Object.getOwnPropertyDescriptor(this.target, name); if (desc && !desc.configurable) { // desc is non-configurable, redefine it on the proxy to keep it in sync Object.defineProperty(proxy, name, desc); // can only change writable:true into writable:false } return desc; // proxy will test whether 'desc' is consistent with the fixed descriptor stored in 'proxy' }
- Issue with a proxy saying that an inherited property is a
non-configurable property (getPropertyDescriptor trap). Basically, getPropertyDescriptor may say that a property is configurable while getPrototypeOf+getOwnPropertyDescriptor(on a native object which allows non-configurable property) may say that the property is not-configurable. They would both talk about the same property and be inconsistent.
Yes, this is a thorny issue. We could drop the getPropertyDescriptor operation & trap. What we lose by that is that proxies would be unable to fully emulate inherited properties. That is: while a proxy's get/set traps may still emulate "foo" as an inherited property (i.e. proxy.foo returns a value, but Object.getOwnPropertyDescriptor(proxy,'foo') returns undefined), the illusion would be broken once the proxy's inheritance chain is inspected (i.e. code may find out that there's actually no "foo" property on the proxy's prototype chain). Actually, one could argue that even without getPropertyDescriptor proxies already cannot emulate prototype inheritance, since they can't virtualize Object.getPrototypeOf.
For the getPropertyDescriptor trap, if the returned descriptor has "configurable:false", the engine could climb the prototype chain to check that "configurable:false" is actually legitimate (only configurability). May be costly?
Ugh :-/ I'm not keen on mandating such prototype-climbing checks.
If given the choice, I would prefer taking away the complexity introduced by |getPropertyDescriptor| by removing the proposed built-in + the trap, rather than mandating more consistency checks.
To summurize fixed propeties property-specific trap behaviors, one could say that:
- defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way)
- delete rejects
- All other property-specific traps have the default derived trap behavior (all the remaining property-specific traps are derived).
Does that sounds right?
Yes, sounds right to me.
2011/6/19 David Bruant <david.bruant at labri.fr>
Le 19/06/2011 00:30, Mark S. Miller a écrit :
Hi David, yes, that code is just a stale illustrative example. This abstraction itself is not being used, and the code predates many changes to the proxy API that render it invalid. And even then, it was sloppy in ways you point out, e.g, regarding enumerability. Sorry if i was a little harsh on my e-mail. It wasn't my intention. The critiques on the code were just meant to keep examples consistent to not puzzle newcomers to the wiki.
Thanks for pointing out these flaws. I updated the example so that the API is at least used consistently on the wiki page.
On Sat, Jun 18, 2011 at 4:44 PM, David Bruant <david.bruant at labri.fr> wrote:
Le 19/06/2011 00:30, Mark S. Miller a écrit :
Hi David, yes, that code is just a stale illustrative example. This abstraction itself is not being used, and the code predates many changes to the proxy API that render it invalid. And even then, it was sloppy in ways you point out, e.g, regarding enumerability. Sorry if i was a little harsh on my e-mail. It wasn't my intention. The critiques on the code were just meant to keep examples consistent to not puzzle newcomers to the wiki.
Hi David, I didn't take it as harsh, just explaining context. Critiques are always appreciated. Tom, thanks for fixing the examples.
However, it is a good example of the kinds of abstraction that one could write using the modern proxy API, if we don't weaken the invariants it relies on. And again, the "read" function I pointed to is a production example which also relies on these invariants. But the very question I am asking is: do we need this kind of abstractions? the "read" function you showed uses some invariants in production for SES. So these invariants should be kept (unless equivalent robustness can be guaranteed with weaker invariants). However, do all invariants have such use? I am sorry to ask this question this way, but this is all very new to me and I'm blind when it comes to justifying that such or such invariant is required to build such or such things which is used (or should be or may be) in such and such context.
Hi David,
The question you are asking is important I would like to take the time to give it a serious answer. Unfortunately, I do not currently have that time and a too-quick answer to this question, especially on es-discuss, will do harm than good. I will get back to this, but not in the next two weeks. (This isn't the only crucial es-discuss discussion I need to postpone. I'm already feeling guilty about not engaging with the "can we do without classes?" threads, but can't get to those soon either.)
That said, the short beginning of an answer is that these issues do not come up primarily in the SES implementation, they come up primarily in anticipated SES use, which will include code along the lines of the flawed example Tom fixed. Safely handling objects from potential adversaries is hard. The more useful invariants there are that such code might find useful, that adversaries cannot violate, the easier this burden becomes.
There are other ways to approach the question of what such invariants should be. E and Joe-E took a very different tack and ended up with a different (and IMO better) mixture of what-is-easy and what-is-hard. Given the legacy compatibility constraints shaping ES3.1, I think we ended up with about as good a mix as could be expected. There are several things I wish we had decided differently, but...
By the way, I am a bit puzzled by the read function. The comment on top says "The frozen property should preserve the enumerability of the original property.". However, l.636-638, the call to defineProperty doesn't set "enumerable" (implicitly set to 'false'). Shouldn't it be enumerable:desc.enumerable, or am I missing something?
In an Object.defineProperty call to change an existing property, missing attributes default to their current setting.
On Sun, Jun 19, 2011 at 4:36 AM, David Bruant <david.bruant at labri.fr> wrote:
** Le 17/06/2011 23:21, Mark S. Miller a écrit :
On Fri, Jun 17, 2011 at 1:50 PM, David Bruant <david.bruant at labri.fr>wrote:
Le 17/06/2011 21:41, David Bruant a écrit :
To summurize fixed propeties property-specific trap behaviors, one could say that:
- defineProperty trap is called (and its return value is meaningful as explained on the current strawman) (if approved) - getOwnPropertyDescriptor trap is called (and its return value is meaningful the same way)
- delete rejects
...wait a minute. There is no invariant imposing this. There is an invariant asking to a property that might /disappear/ to show "configurable:true" but it does not prevent from manually deleting a property with "configurable:false", does it?
If the delete succeeds, then the property disappears, so yes, this would be prohibited.
Ok. "Disappear" seemed to imply "not manually" to me, but I'm not a native English speaker.
I agree that this is the expectation we have from a native object, but I
am not sure this is enforced by the spec on abnormal native or non-native (host) objects.
I'm not sure that the delete operation must throw, though I hope it must. But it cannot result in a non-configurable property disappearing.
Actually, the spec is not very precise on this point. ES5.1 11.4.1 the delete operator "In addition, if a delete operator occurs within strict mode code and the property to be deleted has the attribute { [[Configurable]]: false }, a TypeError exception is thrown." An exception is thrown, however, nothing is said on the destiny of the property. Has it been deleted anyways? One can assume not, but the spec leaves it implicit.
If the spec nowhere states this explicitly, I would consider it a spec bug to be added to the errata. The intent is clearly that the property not disappear in this circumstance.
- The first observed value of [[Prototype]] of an object must remain
throughout the program lifetime
For ES5.1, we explicitly do not prohibit changes to the [[Prototype]] on extensible objects. Neither do we provide any way to change it, but implementations that allow such changes do not thereby violate ES5.1. I believe we should prohibit this in ES-next, but I cannot say we yet have consensus on that.
We do prohibit changes to [[Prototype]] on frozen objects. I think we prohibit such changes on all non-extensible objects, but no longer remember for sure.
The spec words are "if [[Extensible]] is false the value of the [[Class]] and [[Prototype]] internal properties of the object may not be modified."..."Implementation specific extensions that modify [[Class]], [[Prototype]] or [[Extensible]] must not violate the invariants defined in the preceding paragraph." Based on that a decision has to be made between: a) changes to [[Prototype]] even on extensible objects should be prohibited b) getPrototypeOf should be a proxy trap.
[labeled your bullets above so we can refer back to them]
While I do not agree with "has to be made", I do agree with "should be made". Without changing either, the spec makes a consistent even if somewhat silly stance:
x) It nowhere provides any means for modifying a [[Prototype]], so implementations in which [[Prototype]] cannot be modified are conforming. y) It prohibits changes to [[Prototype]] only on non-extensible objects, so implementations which allow such changes to extensible objects by unspecified means are conforming.
Choosing #a implies loss of #y. Choosing #b implies loss of #x.
I think we should chose #a and lose #y. If we can't agree on that, I think I prefer the status quo to choosing #b and losing #x, as it still leaves open the possibility that we could choose #a in the future.
And I am also going to ask the same question as usual: is this invariant on extensible objects and prototype actually used (in SES for instance)?
In answering this question, I think we may have a bug. I think I may be counting on [[Prototype]] not changing in general, rather than only on non-extensible objects. I need to get back to you on that. Thanks for raising it!
Le 19/06/2011 17:43, Tom Van Cutsem a écrit :
2011/6/17 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>
Symetrically to the forwarding of defineProperty, maybe that getOwnPropertyDescriptor could be forwarded too (requires to call the getOwnPropertyDescriptor trap for non-configurable properties) and the return value could be used to redefine writable on the proxy fixed property if necessary.
Yep, trapping getOwnPropertyDescriptor on fixed properties seems to close the loop.
However, I find it strange that the return value of getOwnPropertyDescriptor would actually implicitly side-effect the fixed property of the proxy. I naturally think of defineProperty as a side-effecting trap, not so for getOwnPropertyDescriptor. Perhaps it should only check the return value for consistency with the fixed descriptor, without side-effects.
It won't be enough to ensure invariants.
var ep = EvilProxy(); Object.defineProperty(ep, 'a', {value:1, configurable:false, writable:true}); // works as expected Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:true} // This is the stored fixed property descriptor Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:false} is legitimate // because it doesn't break any invariant. No side effect-here, stored descriptor remains the same Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:true} // This is coherent with what is stored in the fixed property record
My point is that the second Object.getOwnPropertyDescriptor call is legitimate, but if we do not store that writable changed from "true" to "false", then it can be later observed as "true" which violates the ES5.1 invariant: "If the [[Writable]] attribute may change from false to true, then the [[Configurable]] attribute must be true." Consequently, for this very case (changing writable from false to true for non-configurable properties), getOwnPropertyDescriptor needs to have a side-effect. That's probably the only one.
To sum up, we need to trap getOwnPropertyDescriptor for writable consistency in the forwarding use case. However, if we trap and do not have a side effect, an EvilProxy could break an invariant.
Also, as it turns out, there is actually no need to store the entire property descriptor for fixed properties to enforce invariants. There is a need to store:
- Whether the property has been observed as non-configurable and if it's the case: 1.1) Whether writable has been observed to false and if it's the case 1.1.1) latest observed/provided value (no need to keep the value until configurable and writable or both false since it can be changed anytime. First universal property invariant) 1.2) latest observed/provided get/set 1.3) latest observed/provided enumerable (there are no invariants on this one, so I'm not sure)
I think that the notion of "latest observed" is the one requiring getOwnPropertyDescriptor to have side-effects (even though it may sound counter intuitive).
2011/6/19 David Bruant <david.bruant at labri.fr>
** Le 19/06/2011 17:43, Tom Van Cutsem a écrit :
2011/6/17 David Bruant <david.bruant at labri.fr>
Symetrically to the forwarding of defineProperty, maybe that getOwnPropertyDescriptor could be forwarded too (requires to call the getOwnPropertyDescriptor trap for non-configurable properties) and the return value could be used to redefine writable on the proxy fixed property if necessary.
Yep, trapping getOwnPropertyDescriptor on fixed properties seems to close the loop.
However, I find it strange that the return value of getOwnPropertyDescriptor would actually implicitly side-effect the fixed property of the proxy. I naturally think of defineProperty as a side-effecting trap, not so for getOwnPropertyDescriptor. Perhaps it should only check the return value for consistency with the fixed descriptor, without side-effects.
It won't be enough to ensure invariants.
var ep = EvilProxy(); Object.defineProperty(ep, 'a', {value:1, configurable:false, writable:true}); // works as expected Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:true} // This is the stored fixed property descriptor Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:false} is legitimate // because it doesn't break any invariant. No side effect-here, stored descriptor remains the same Object.getOwnPropertyDescriptor(ep, 'a'); // {value:1, configurable:false, writable:true} // This is coherent with what is stored in the fixed property record
My point is that the second Object.getOwnPropertyDescriptor call is legitimate, but if we do not store that writable changed from "true" to "false", then it can be later observed as "true" which violates the ES5.1 invariant: "If the [[Writable]] attribute may change from false to true, then the [[Configurable]] attribute must be true." Consequently, for this very case (changing writable from false to true for non-configurable properties), getOwnPropertyDescriptor needs to have a side-effect. That's probably the only one.
To sum up, we need to trap getOwnPropertyDescriptor for writable consistency in the forwarding use case. However, if we trap and do not have a side effect, an EvilProxy could break an invariant.
Also, as it turns out, there is actually no need to store the entire property descriptor for fixed properties to enforce invariants. There is a need to store:
- Whether the property has been observed as non-configurable and if it's the case: 1.1) Whether writable has been observed to false and if it's the case 1.1.1) latest observed/provided value (no need to keep the value until configurable and writable or both false since it can be changed anytime. First universal property invariant) 1.2) latest observed/provided get/set 1.3) latest observed/provided enumerable (there are no invariants on this one, so I'm not sure)
I think that the notion of "latest observed" is the one requiring getOwnPropertyDescriptor to have side-effects (even though it may sound counter intuitive).
Yes, you're right. Thanks for the clarifying example. I gave a first shot at trying to define the semantics of [[GetOwnProperty]] for Proxies with support for fixed properties:
[[GetOwnProperty]] (P)
- Let handler be the value of the [[Handler]] internal property of O.
- Let getOwnProperty be the result of calling the [[Get]] internal method of handler with argument “getOwnPropertyDescriptor”.
- If getOwnProperty is undefined, throw a TypeError exception.
- If IsCallable(getOwnProperty) is false, throw a TypeError exception.
- Let trapResult be the result of calling the [[Call]] internal method of getOwnProperty providing handler as the this value and P as the first argument.
- Let fixedProperty be the result of calling Object.[GetOwnProperty]
- If trapResult is undefined a. If fixedProperty is undefined, return undefined b. Otherwise, fixedProperty is defined, so throw a TypeError
- Let desc be ToCompletePropertyDescriptor(trapResult)
- If fixedProperty is not undefined, or desc.[[Configurable]] is false a. call Object.[[DefineOwnProperty]](P, desc, true)
- Return desc
Here, Object.[[GetOwnProperty]] and Object.[[DefineOwnProperty]] refer to the algorithms for Object values from ES5 sections 8.12.1 and 8.12.9 respectively. Line 7.b. is necessary to guard against a handler reporting a previously fixed property as undefined.
It's true that the proxy does not necessarily need to store the entire fixed property descriptor. However, in terms of specifying the semantics, reusing the existing built-in semantics for Objects seems easier to grasp.
What:
Honor attempts to make individual proxy properties non-configurable.
Why:
For consistency since attempts to make all of a proxy's properties non-configurable are honored via the "fix" trap.
Example:
var x = Proxy.create({ defineProperty: function() {}, getPropertyDescriptor: function() { return { value: "whatever I want", writable: true, configurable: true, enumerable: false }; } }); Object.defineProperty(x, "foo", {value: "bar", configurable: false, writable: false, enumerable: true};
/* ... */
// logs "whatever I want", not "bar" console.log(x.foo); // non-writable, but doesn't throw object.foo = "baz"; // non-configurable, but doesn't throw Object.defineProperty(x, "foo", {configurable: true});
How:
Assume a "defineProperty" trap invocation due to |Object.defineProperty(proxy, name, pd)| where |!pd.configurable|. If return value is...
true - Henceforth bypass |proxy|'s handler for any traps with a property name parameter when the property name would be |name| false - throw TypeError similarly to "fix" trap
Update the "fix" trap semantics such that when its return value is not undefined but rather a property descriptor map, behavior is similar to |Object.defineProperties| in that improperly redefining any properties will cause a TypeError to be thrown.
Notes:
Can check |Object.getOwnPropertyDescriptor(proxy, name).configurable| to determine if a given proxy property is fixed since it will always be false in this case.
Thanks, Sean Eagan