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!
On Fri, Jun 17, 2011 at 11:56 AM, David Bruant <david.bruant at labri.fr>wrote: > ** > 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, > 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. > 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). > AFAIK, this is wrong. I am not aware of any requirement that so-called "writable" properties are actually writable. > 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. (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.) > 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. > 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. > 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. > 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. > > > On a last note, array.length already do respect all Universal property > constraints since writable is true. Or am I missing something? > 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! > > Thanks for your explanations, > > David > -- Cheers, --MarkM -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110617/d625be93/attachment-0001.html>