Possible Loophole (was: Proposal: Property fixing)
Le 17/06/2011 22:20, Mark S. Miller a écrit :
On Fri, Jun 17, 2011 at 11:56 AM, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
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).
AFAIK, this is wrong. I am not aware of any requirement that so-called "writable" properties are actually writable.
Interesting. Basically, "writable" is semantic-less outside of normal objects (and arrays, because they are in the spec). On host objects, neither true nor false are enforcing any guarantee. What about downgrading the "writable" attribute to an attribute specific to normal objects (and arrays, because they work the same in the spec)? non-native objects would, of course, be invited to use this attribute if they want to provide similar semantics on their properties, but wouldn't be forced to. In this case, Object.getOwnPropertyDescriptor(document.getElementsByTagName('*'), 'length') could return {value:12, configurable:true, enumerable:false}.
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).
#1a says only "either or both of the [[Writable]] and [[Configurable]] attributes must be true", which is the only constraint I would assume here.
Hmmm. This suggests a loophole that might help out on some of the proxy cases we're concerned about. I know of no universal constraint that a proxy's handler could violate if it could 1) trap attempts to change the value of a writable non-configurable property, and 2) could respond by either setting the value as it likes or reporting a failed assignment. The handler must of course be prevented from changing any other attribute, except to change from writable to non-writable. AFAICT, this does not weaken any assumptions that are safe to assume regarding ES5.1 non-native objects.
Actually, since in the recent version of the strawman, the defineProperty trap is actualy trapped even for fixed properties, I think that what you're describing is happening already. Is it Tom?
(As a further benefit, if we reconsider retroactive proxification for data binding, this would also allow the monitoring of non-configurable writable properties without needing per-property getters/setters.)
It would be the case also in the "trap all and enforce invariants in relevant places" model.
On Fri, Jun 17, 2011 at 2:23 PM, David Bruant <david.bruant at labri.fr> wrote:
** Le 17/06/2011 22:20, Mark S. Miller a écrit :
On Fri, Jun 17, 2011 at 11:56 AM, David Bruant <david.bruant at labri.fr>wrote:
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).
AFAIK, this is wrong. I am not aware of any requirement that so-called "writable" properties are actually writable.
Interesting. Basically, "writable" is semantic-less outside of normal objects (and arrays, because they are in the spec). On host objects, neither true nor false are enforcing any guarantee.
The constraints enforced by {writable:false, configurable:false} are universal: on such a property, value must always produce the SaveValue. If what you're saying is that {writable:false, configurable:true} enforces no guarantees, I agree.
What about downgrading the "writable" attribute to an attribute specific to normal objects (and arrays, because they work the same in the spec)? non-native objects would, of course, be invited to use this attribute if they want to provide similar semantics on their properties, but wouldn't be forced to. In this case, Object.getOwnPropertyDescriptor(document.getElementsByTagName('*'), 'length') could return {value:12, configurable:true, enumerable:false}.
Correct, because it's configurable.
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).
#1a says only "either or both of the [[Writable]] and [[Configurable]] attributes must be true", which is the only constraint I would assume here.
Hmmm. This suggests a loophole that might help out on some of the proxy cases we're concerned about. I know of no universal constraint that a proxy's handler could violate if it could 1) trap attempts to change the value of a writable non-configurable property, and 2) could respond by either setting the value as it likes or reporting a failed assignment. The handler must of course be prevented from changing any other attribute, except to change from writable to non-writable. AFAICT, this does not weaken any assumptions that are safe to assume regarding ES5.1 non-native objects.
Actually, since in the recent version of the strawman, the defineProperty trap is actualy trapped even for fixed properties, I think that what you're describing is happening already. Is it Tom?
(As a further benefit, if we reconsider retroactive proxification for data binding, this would also allow the monitoring of non-configurable writable properties without needing per-property getters/setters.)
It would be the case also in the "trap all and enforce invariants in relevant places" model.
Yes. I am leaning in that direction. Thanks.
2011/6/17 David Bruant <david.bruant at labri.fr>
** Le 17/06/2011 22:20, Mark S. Miller a écrit :
Hmmm. This suggests a loophole that might help out on some of the proxy cases we're concerned about. I know of no universal constraint that a proxy's handler could violate if it could 1) trap attempts to change the value of a writable non-configurable property, and 2) could respond by either setting the value as it likes or reporting a failed assignment. The handler must of course be prevented from changing any other attribute, except to change from writable to non-writable. AFAICT, this does not weaken any assumptions that are safe to assume regarding ES5.1 non-native objects.
Actually, since in the recent version of the strawman, the defineProperty trap is actualy trapped even for fixed properties, I think that what you're describing is happening already. Is it Tom?
Yes, with one minor difference: as currently specced in the strawman, the proxy handler can trap attempts to change the value of any fixed non-configurable property, both writable and non-writable. Still, the handler will be prevented from changing writable:false to writable:true, which seems to be the important invariant to uphold.
So, updates to non-writable, non-configurable fixed properties are currently trapped, but the handler can't actually change the property. Should the strawman be changed such that defineProperty is only trapped on writable, non-configurable fixed properties?
Le 19/06/2011 16:53, 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 22:20, Mark S. Miller a écrit :
Hmmm. This suggests a loophole that might help out on some of the proxy cases we're concerned about. I know of no universal constraint that a proxy's handler could violate if it could 1) trap attempts to change the value of a writable non-configurable property, and 2) could respond by either setting the value as it likes or reporting a failed assignment. The handler must of course be prevented from changing any other attribute, except to change from writable to non-writable. AFAICT, this does not weaken any assumptions that are safe to assume regarding ES5.1 non-native objects.
Actually, since in the recent version of the strawman, the defineProperty trap is actualy trapped even for fixed properties, I think that what you're describing is happening already. Is it Tom?
Yes, with one minor difference: as currently specced in the strawman, the proxy handler can trap attempts to change the value of any fixed non-configurable property, both writable and non-writable. Still, the handler will be prevented from changing writable:false to writable:true, which seems to be the important invariant to uphold.
So, updates to non-writable, non-configurable fixed properties are currently trapped, but the handler can't actually change the property. Should the strawman be changed such that defineProperty is only trapped on writable, non-configurable fixed properties?
With the current fixed properties proposal, here is what happens in the engine at an Object.defineProperty(o, name, pd) call:
if("name" has already been observed as non-configurable){ [[DefineOwnProperty]] (name, pd) on the fixed properties record // ES5.1 - 8.12.9 // This call includes invariant checking code } else{ resPd = ToPropertydescriptor(call o's defineProperty trap with [o, name, pd]); if(!resPd.configurable){ add "name" to fixed properties record with resPd as property descriptor } }
What about doing the following:
// (Calling the trap in all cases) resPd = ToPropertyDescriptor(call o's defineProperty trap with [o, name, pd]);
if("name" has already been observed as non-configurable){ [[DefineOwnProperty]] (name, resPd) on the non-configurable properties record // ES5.1 - 8.12.9 // This call includes the exact same invariant checking code
} else{ if(!resPd.configurable){ add "name" to non-configurable properties record with resPd as property descriptor } }
The same amount of engine code is called and we have the genericity of calling the defineProperty trap every single time.
2011/6/19 David Bruant <david.bruant at labri.fr>
** With the current fixed properties proposal, here is what happens in the engine at an Object.defineProperty(o, name, pd) call:
if("name" has already been observed as non-configurable){ [[DefineOwnProperty]] (name, pd) on the fixed properties record // ES5.1
- 8.12.9 // This call includes invariant checking code } else{ resPd = ToPropertydescriptor(call o's defineProperty trap with [o, name, pd]); if(!resPd.configurable){ add "name" to fixed properties record with resPd as property descriptor } }
What about doing the following:
// (Calling the trap in all cases) resPd = ToPropertyDescriptor(call o's defineProperty trap with [o, name, pd]);
if("name" has already been observed as non-configurable){ [[DefineOwnProperty]] (name, resPd) on the non-configurable properties record // ES5.1 - 8.12.9 // This call includes the exact same invariant checking code
} else{ if(!resPd.configurable){ add "name" to non-configurable properties record with resPd as property descriptor } }
The same amount of engine code is called and we have the genericity of calling the defineProperty trap every single time.
The updated strawman is already more akin to your proposed second implementation. The way I currently think about a Proxy's [[DefineOwnProperty]] method with support for fixed properties is as follows:
[[DefineOwnProperty]] (P, Desc, Throw)
- Let handler be the value of the [[Handler]] internal property of O.
- Let defineProperty be the result of calling the [[Get]] internal method of handler with argument “defineProperty”.
- If defineProperty is undefined, throw a TypeError exception.
- If IsCallable(defineProperty) is false, throw a TypeError exception.
- Let trapResult be the result of calling the [[Call]] internal method of defineProperty providing handler as the this value, P as the first argument and Desc as the second argument.
- If ToBoolean(trapResult) is false, reject.
- Let desc be ToPropertyDescriptor(trapResult)
- If [GetOwnProperty] is not undefined, or desc.[[Configurable]] is false a. return [[DefineOwnProperty]](P, desc, Throw) (as per ES5 8.12.9)
- return true
2011/6/20 Tom Van Cutsem <tomvc.be at gmail.com>
The updated strawman is already more akin to your proposed second implementation. The way I currently think about a Proxy's [[DefineOwnProperty]] method with support for fixed properties is as follows:
[[DefineOwnProperty]] (P, Desc, Throw)
- Let handler be the value of the [[Handler]] internal property of O.
- Let defineProperty be the result of calling the [[Get]] internal method of handler with argument “defineProperty”.
- If defineProperty is undefined, throw a TypeError exception.
- If IsCallable(defineProperty) is false, throw a TypeError exception.
- Let trapResult be the result of calling the [[Call]] internal method of defineProperty providing handler as the this value, P as the first argument and Desc as the second argument.
- If ToBoolean(trapResult) is false, reject.
- Let desc be ToPropertyDescriptor(trapResult)
- If [GetOwnProperty] is not undefined, or desc.[[Configurable]] is false a. return [[DefineOwnProperty]](P, desc, Throw) (as per ES5 8.12.9)
- return true
Bugfix: we can't just let the handler reject if P already denotes a fixed property:
[[DefineOwnProperty]] (P, Desc, Throw)
- Let handler be the value of the [[Handler]] internal property of O.
- Let defineProperty be the result of calling the [[Get]] internal method of handler with argument “defineProperty”.
- If defineProperty is undefined, throw a TypeError exception.
- If IsCallable(defineProperty) is false, throw a TypeError exception.
- Let trapResult be the result of calling the [[Call]] internal method of defineProperty providing handler as the this value, P as the first argument and Desc as the second argument.
- Let fixedProperty be the result of calling Object.[GetOwnProperty]
- If ToBoolean(trapResult) is false, a. If fixedProperty is undefined, reject. b. Otherwise, fixedProperty is defined, so throw a TypeError.
- Let desc be ToPropertyDescriptor(trapResult)
- If fixedProperty is not undefined, or desc.[[Configurable]] is false a. Return Object.[[DefineOwnProperty]](P, desc, Throw)
- Return true
Clarification: Object.[[GetOwnProperty]] and Object.[[DefineOwnProperty]] refer to the algorithms for Object values from ES5 sections 8.12.1 and 8.12.9 respectively. They are used to manipulate a proxy's set of fixed properties, which are all non-configurable (since the only way they end up in that record is by virtue of the second condition in line 9)
On Fri, Jun 17, 2011 at 11:56 AM, David Bruant <david.bruant at labri.fr>wrote:
Rest assured that none of this is naive. This stuff is hard to think about. However, due to time constraints, I'll only answer now the things I can answer quickly.
AFAIK, this is wrong. I am not aware of any requirement that so-called "writable" properties are actually writable.
#1a says only "either or both of the [[Writable]] and [[Configurable]] attributes must be true", which is the only constraint I would assume here.
Hmmm. This suggests a loophole that might help out on some of the proxy cases we're concerned about. I know of no universal constraint that a proxy's handler could violate if it could 1) trap attempts to change the value of a writable non-configurable property, and 2) could respond by either setting the value as it likes or reporting a failed assignment. The handler must of course be prevented from changing any other attribute, except to change from writable to non-writable. AFAICT, this does not weaken any assumptions that are safe to assume regarding ES5.1 non-native objects.
(As a further benefit, if we reconsider retroactive proxification for data binding, this would also allow the monitoring of non-configurable writable properties without needing per-property getters/setters.)
Exactly. Outside of normal native, "configurable" represents only the absence of knowledge about what can and cannot happen. Only non-configurable gives any actual knowledge about what cannot happen.
Agreed. The naming is unfortunate. In retrospect the old negative names (DontDelete, ReadOnly) were more accurate. However, this led into statements full of double negatives. Even with our noses rubbed in this naming issue, on the whole I'm glad we switched to positive names, and would do it over again if given the chance.
Better would be accurate positive names, but none come to mind. Even though it's too late to switch, if anyone has suggestions I'd like to hear them.
Yes. There are no violations of the universal constraints among the specified normal and abnormal native behaviors. The reason we're discussing reforming array.length is only our inability to emulate it using a proxy mechanism that is itself constrained to enforce these constraints. Perhaps the loophole above is a better answer? I don't see that it violates any constraints and does seem to solve this emulate-ability problem.
Thanks for raising these issues!