Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <akodat at rocketsoftware.com>
wrote:
Is there any reason that there’s no flavor of Object.seal that would throw an exception on read references to properties that are not found on the specified object?
This sounds like a good idea at a high level. Will be very interested to know what implementers have to say about it from an implementation perspective.
In spec terms, at first glance it's mostly just a new pair of steps in OrdinaryGet ordinary object internal function (added Step 3.a and 3.b):
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
a. Let readExtendable be ? IsReadExtensible(Receiver).
b. If readExtendable is false, throw TypeError.
c. Let parent be ? O.[[GetPrototypeOf]]().
d. If parent is null, return undefined.
e. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).
...where IsReadExtensible
is an op to test the new flag. (Used pre
markdown to avoid the [[...](.)
being treated as links without having to
add \
for those not reading the rendered version.)
One concern is people already have trouble keeping sealed and frozen straight (or is that just me?). Adding a locked (or whatever) makes that even more confusing. But the semantics of sealed and frozen can't be changed to include this now.
-- T.J. Crowder
FWIW, I’m not sure I agree with your OrdinaryGet steps. Also, not sure I like readExtensible as it doesn’t really have anything to do with extensibility. Maybe readStrict? But maybe I’m thinking about extensibility wrong?
In any case, I think the description of [[Get]] would have to be like (sorry, I’ve never done this sort of spec pseudo code so some of this is probably syntactically incorrect):
[[Get]] (P, Receiver, ReadStrict)
- Return ? OrdinaryGet(O, P, Receiver, ReadStrict).
And OrdinaryGet would be like:
OrdinaryGet (O, P, Receiver, ReadStrict)#
-
Assert: IsPropertyKey(P) is true.
-
Let desc be ? O.[GetOwnProperty].
-
If desc is undefined, then
-
Let readStrict be ? O.IsReadStrict(Receiver) || ReadStrict
-
Let parent be ? O.[GetPrototypeOf].
-
If parent is null then
i. Assert: readStrict is false
ii. return undefined.
-
Return ? parent.[[Get]](P, Receiver, readStrict).
-
If IsDataDescriptor(desc) is true, return desc.[[Value]].
-
Assert: IsAccessorDescriptor(desc) is true.
-
Let getter be desc.[[Get]].
-
If getter is undefined, return undefined.
-
Return ? Call(getter, Receiver).
Probably adjustments need to be made to other references to [[Get]] and OrdinaryGet, passing false for ReadStrict.
I guess this pseudo code also points out an issue as to whether Object.lock would affect a dynamic property with no getter. Intuitively, it seems to me that it should.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.comwww.rocketsoftware.com
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Monday, August 14, 2017 12:00 PM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <akodat at rocketsoftware.com<mailto:akodat at rocketsoftware.com>> wrote:
Is there any reason that there’s no flavor of Object.seal that would throw an exception on read references to properties that are not found on the specified object?
This sounds like a good idea at a high level. Will be very interested to know what implementers have to say about it from an implementation perspective.
In spec terms, at first glance it's mostly just a new pair of steps in OrdinaryGet ordinary object internal function (added Step 3.a and 3.b):
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
a. Let readExtendable be ? IsReadExtensible(Receiver).
b. If readExtendable is false, throw TypeError.
c. Let parent be ? O.[[GetPrototypeOf]]().
d. If parent is null, return undefined.
e. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).
...where IsReadExtensible
is an op to test the new flag. (Used pre
markdown to avoid the [[...](.)
being treated as links without having to add \
for those not reading the rendered version.)
One concern is people already have trouble keeping sealed and frozen straight (or is that just me?). Adding a locked (or whatever) makes that even more confusing. But the semantics of sealed and frozen can't be changed to include this now.
-- T.J. Crowder
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
On Mon, Aug 14, 2017 at 7:16 PM, Alex Kodat <akodat at rocketsoftware.com>
wrote:
FWIW, I’m not sure I agree with your OrdinaryGet steps.
Why not? (Not that spec language is important at this stage.)
Also, not sure I like readExtensible as it doesn’t really have anything to do with extensibility.
That's fine, it was just a placeholder variable name in an internal method.
:-) "sealed"
and "frozen"
are referred to with that terminology, and
this is in the same general area although it isn't literally extensibility.
In any case, I think the description of [[Get]] would have to be like...
Why do you think [[Get]]
needs a flag? The object will have the flag on
it, and [[Get]]
has the object.
OrdinaryGet
certainly doesn't need the flag. It has the object, and it
has the relevant object at each stage in the prototype chain (note that
it's indirectly recursive via [[Get]]
).
Again, though, spec language isn't important at this stage. It's a good idea and I'd love to see it get legs.
-- T.J. Crowder
Why do you think
[[Get]]
needs a flag? The object will have the flag on it, and[[Get]]
has the object.
I also think a new flag or something similar is needed, because it would be good to throw if some object in the prototype chain is locked, not necessarily the receiver. I would expect this:
let locked = Object.lock({});
let inherits = Object.create(locked);
Reflect.get(locked, "foo"); // TypeError
Reflect.get(locked, "foo", {}); // TypeError
Reflect.get({}, "foo", locked); // undefined
Reflect.get(inherits, "foo"); // TypeError
<imho>Land mines like this are bad for JS. Reads should always be safe to
do as in "foo && foo.bar". Throwing errors from that is always bad. duck-typing, feature detection etc are all valid uses for objects ... even sealed/frozen/locked/obfuscated ones :) </imho>
Just give me "undefined" on reads like these. I would much rather have that and possibly Object.isFrozen/isLocked/isSealed etc methods to test these conditions if appropriate.
Best, Don
Don Griffin Sr Director of Engineering Sencha, Inc. www.sencha.com
On Mon, Aug 14, 2017 at 7:24 PM, T.J. Crowder < tj.crowder at farsightsoftware.com> wrote:
On Mon, Aug 14, 2017 at 7:16 PM, Alex Kodat <akodat at rocketsoftware.com>
wrote:
FWIW, I’m not sure I agree with your OrdinaryGet steps.
Why not? (Not that spec language is important at this stage.)
Ignore me, sorry. Appear to have left brain at office.
-- T.J. Crowder
I'm ok with the basic idea. I don't think "lock" is a good name for it. Nothing about "Object.lock(myObj)" suggests that future access to a non-existent property of myObj will throw. Also "lock" carries a well-established, unrelated meaning in computer science.
Could this be done with an additional argument to Object.seal(), Object.freeze(), and Object.preventExtensions()?Like so: "Object.seal(myObj, Object.THROW_ON_ABSENT_PROPERTY_READ)"--wordy, but the intent is unmistakable. On Monday, August 14, 2017, 1:16:39 PM CDT, Alex Kodat <akodat at rocketsoftware.com> wrote:
#yiv9333647376 #yiv9333647376 -- _filtered #yiv9333647376 {panose-1:2 4 5 3 5 4 6 3 2 4;} _filtered #yiv9333647376 {font-family:Calibri;panose-1:2 15 5 2 2 2 4 3 2 4;} _filtered #yiv9333647376 {panose-1:2 11 6 9 4 5 4 2 2 4;}#yiv9333647376 #yiv9333647376 p.yiv9333647376MsoNormal, #yiv9333647376 li.yiv9333647376MsoNormal, #yiv9333647376 div.yiv9333647376MsoNormal {margin:0in;margin-bottom:.0001pt;font-size:12.0pt;}#yiv9333647376 a:link, #yiv9333647376 span.yiv9333647376MsoHyperlink {color:blue;text-decoration:underline;}#yiv9333647376 a:visited, #yiv9333647376 span.yiv9333647376MsoHyperlinkFollowed {color:purple;text-decoration:underline;}#yiv9333647376 p.yiv9333647376MsoListParagraph, #yiv9333647376 li.yiv9333647376MsoListParagraph, #yiv9333647376 div.yiv9333647376MsoListParagraph {margin-top:0in;margin-right:0in;margin-bottom:0in;margin-left:.5in;margin-bottom:.0001pt;font-size:12.0pt;}#yiv9333647376 p.yiv9333647376CodeExample, #yiv9333647376 li.yiv9333647376CodeExample, #yiv9333647376 div.yiv9333647376CodeExample {margin:0in;margin-bottom:.0001pt;font-size:12.0pt;color:#0070C0;}#yiv9333647376 p.yiv9333647376msonormal0, #yiv9333647376 li.yiv9333647376msonormal0, #yiv9333647376 div.yiv9333647376msonormal0 {margin-right:0in;margin-left:0in;font-size:12.0pt;}#yiv9333647376 span.yiv9333647376EmailStyle19 {color:#1F497D;}#yiv9333647376 .yiv9333647376MsoChpDefault {} _filtered #yiv9333647376 {margin:1.0in 1.0in 1.0in 1.0in;}#yiv9333647376 div.yiv9333647376WordSection1 {}#yiv9333647376 _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {margin-left:1.0in;} _filtered #yiv9333647376 {margin-left:1.25in;} _filtered #yiv9333647376 {margin-left:1.75in;} _filtered #yiv9333647376 {margin-left:2.25in;} _filtered #yiv9333647376 {margin-left:2.75in;} _filtered #yiv9333647376 {margin-left:3.25in;} _filtered #yiv9333647376 {margin-left:3.75in;} _filtered #yiv9333647376 {margin-left:4.25in;} _filtered #yiv9333647376 {margin-left:4.75in;} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {} _filtered #yiv9333647376 {}#yiv9333647376 ol {margin-bottom:0in;}#yiv9333647376 ul {margin-bottom:0in;}#yiv9333647376 FWIW, I’m not sure I agree with your OrdinaryGet steps. Also, not sure I like readExtensible as it doesn’t really have anything to do with extensibility. Maybe readStrict? But maybe I’m thinking about extensibility wrong?
In any case, I think the description of [[Get]] would have to be like (sorry, I’ve never done this sort of spec pseudo code so some of this is probably syntactically incorrect):
[[Get]] (P, Receiver, ReadStrict)
- Return ? OrdinaryGet(O, P, Receiver, ReadStrict).
And OrdinaryGet would be like:
OrdinaryGet (O, P, Receiver, ReadStrict)#
-
Assert: IsPropertyKey(P) is true.
-
Let desc be ? O.[GetOwnProperty].
-
If desc is undefined, then
-
Let readStrict be ? O.IsReadStrict(Receiver) || ReadStrict
-
Let parent be ? O.[GetPrototypeOf].
-
If parent is null then
i. Assert: readStrict is false
ii. return undefined.
-
Return ? parent.[[Get]](P, Receiver, readStrict).
-
If IsDataDescriptor(desc) is true, return desc.[[Value]].
-
Assert: IsAccessorDescriptor(desc) is true.
-
Let getter be desc.[[Get]].
-
If getter is undefined, return undefined.
-
Return ? Call(getter, Receiver).
Probably adjustments need to be made to other references to [[Get]] and OrdinaryGet, passing false for ReadStrict.
I guess this pseudo code also points out an issue as to whether Object.lock would affect a dynamic property with no getter. Intuitively, it seems to me that it should.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Monday, August 14, 2017 12:00 PM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <akodat at rocketsoftware.com> wrote:
Is there any reason that there’s no flavor of Object.seal that
would throw an exception on read references to properties that are
not found on the specified object?
This sounds like a good idea at a high level. Will be very interested to know what implementers have to say about it from an implementation perspective.
In spec terms, at first glance it's mostly just a new pair of steps in OrdinaryGet ordinary object internal function (added Step 3.a and 3.b):
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
a. Let readExtendable be ? IsReadExtensible(Receiver).
b. If readExtendable is false, throw TypeError.
c. Let parent be ? O.[[GetPrototypeOf]]().
d. If parent is null, return undefined.
e. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).
...where IsReadExtensible
is an op to test the new flag. (Used pre
markdown to avoid the [[...](.)
being treated as links without having to add \
for those not reading the rendered version.)
One concern is people already have trouble keeping sealed and frozen straight (or is that just me?). Adding a locked (or whatever) makes that even more confusing. But the semantics of sealed and frozen can't be changed to include this now.
-- T.J. Crowder
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
Agree, lock’s not a good name. That was just a dumb name because I was too lazy to think of something better. Other names that come to mind (some also with other software connotations):
Object.box Object.protect Object.close Object.guard Object.fence Object.shield
But don’t care that much and probably someone could come up with better. I guess of the above I like Object.guard. So I’ll use that for now…
Also, I’ll retract an earlier stupid statement I made where I suggested that if a property referenced an accessor descriptor without a getter it should throw if the guard bit is set. This is obviously wrong. As long as the property name is present, not having a getter is equivalent to the property being explicitly set to undefined which should not throw. That is:
let foo = Object.guard({a: 1, b: undefined}); console.log(foo.b);
should not throw.
Also, FWIW, I had originally thought of Object.guard as Object.seal on steroids but some thought makes me realize it’s really orthogonal and can picture cases where one might want to seal an object but not guard it and vice versa.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.comwww.rocketsoftware.com
From: Jerry Schulteis [mailto:jdschulteis at yahoo.com] Sent: Monday, August 14, 2017 5:21 PM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: es-discuss at mozilla.org Subject: Re: RE: Object.seal, read references, and code reliability
I'm ok with the basic idea. I don't think "lock" is a good name for it. Nothing about "Object.lock(myObj)" suggests that future access to a non-existent property of myObj will throw. Also "lock" carries a well-established, unrelated meaning in computer science.
Could this be done with an additional argument to Object.seal(), Object.freeze(), and Object.preventExtensions()? Like so: "Object.seal(myObj, Object.THROW_ON_ABSENT_PROPERTY_READ)"--wordy, but the intent is unmistakable. On Monday, August 14, 2017, 1:16:39 PM CDT, Alex Kodat <akodat at rocketsoftware.com<mailto:akodat at rocketsoftware.com>> wrote:
FWIW, I’m not sure I agree with your OrdinaryGet steps. Also, not sure I like readExtensible as it doesn’t really have anything to do with extensibility. Maybe readStrict? But maybe I’m thinking about extensibility wrong?
In any case, I think the description of [[Get]] would have to be like (sorry, I’ve never done this sort of spec pseudo code so some of this is probably syntactically incorrect):
[[Get]] (P, Receiver, ReadStrict)
- Return ? OrdinaryGet(O, P, Receiver, ReadStrict).
And OrdinaryGet would be like:
OrdinaryGet (O, P, Receiver, ReadStrict)#
-
Assert: IsPropertyKey(P) is true.
-
Let desc be ? O.[GetOwnProperty].
-
If desc is undefined, then
-
Let readStrict be ? O.IsReadStrict(Receiver) || ReadStrict
-
Let parent be ? O.[GetPrototypeOf].
-
If parent is null then
i. Assert: readStrict is false
ii. return undefined.
-
Return ? parent.[[Get]](P, Receiver, readStrict).
-
If IsDataDescriptor(desc) is true, return desc.[[Value]].
-
Assert: IsAccessorDescriptor(desc) is true.
-
Let getter be desc.[[Get]].
-
If getter is undefined, return undefined.
-
Return ? Call(getter, Receiver).
Probably adjustments need to be made to other references to [[Get]] and OrdinaryGet, passing false for ReadStrict.
I guess this pseudo code also points out an issue as to whether Object.lock would affect a dynamic property with no getter. Intuitively, it seems to me that it should.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.comwww.rocketsoftware.com
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Monday, August 14, 2017 12:00 PM To: Alex Kodat <akodat at rocketsoftware.com<mailto:akodat at rocketsoftware.com>>
Cc: es-discuss at mozilla.org<mailto:es-discuss at mozilla.org>
Subject: Re: Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <akodat at rocketsoftware.com<mailto:akodat at rocketsoftware.com>> wrote:
Is there any reason that there’s no flavor of Object.seal that
would throw an exception on read references to properties that are
not found on the specified object?
This sounds like a good idea at a high level. Will be very interested to know what implementers have to say about it from an implementation perspective.
In spec terms, at first glance it's mostly just a new pair of steps in OrdinaryGet ordinary object internal function (added Step 3.a and 3.b):
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
a. Let readExtendable be ? IsReadExtensible(Receiver).
b. If readExtendable is false, throw TypeError.
c. Let parent be ? O.[[GetPrototypeOf]]().
d. If parent is null, return undefined.
e. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).
...where IsReadExtensible
is an op to test the new flag. (Used pre
markdown to avoid the [[...](.)
being treated as links without having to add \
for those not reading the rendered version.)
One concern is people already have trouble keeping sealed and frozen straight (or is that just me?). Adding a locked (or whatever) makes that even more confusing. But the semantics of sealed and frozen can't be changed to include this now.
-- T.J. Crowder
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
Is there some reason this is not just a compile-time type check? Bob
On Mon Aug 14 18:49:35 UTC 2017 Don Griffin wrote
<imho>Land mines like this are bad for JS. Reads should always be safe to do as in "foo && foo.bar". Throwing errors from that is always bad. duck-typing, feature detection etc are all valid uses for objects ... even sealed/frozen/locked/obfuscated ones :) </imho>
Just give me "undefined" on reads like these. I would much rather have that and possibly Object.isFrozen/isLocked/isSealed etc methods to test these conditions if appropriate.
No one would force anyone to use Object.guard (what I initially called Object.lock). It would introduce no backward incompatibilities.
Whatever landmines this feature would introduce have been there at least since proxies were introduced since, with proxies, a get for a non-existent property might throw. In fact, even with a simple property getter you can get an exception from a getter. So the good old days when reads were always safe was a very long time ago. Of course, in your own code, you can very easily make your reads always safe and I'd say more power to you.
Duck-typing, feature detection, etc. can be wonderful things but so can objects with a well-defined set of properties. And in the latter case, it makes debugging much easier and faster if references to non-existent properties throw an exception.
Even in the face of Object.guard, feature detection could be done with Object.getOwnPropertyNames and Object.getPrototypeOf or Object.prototype.hasOwnProperty (the latter if one doesn't care about properties in prototype classes). In fact, as a general rule doing a feature test as if (foo.feature) is kinda sketchy as, of course, if foo.feature were 0 or "" or false your test would suggest a missing feature. Even if foo.feature returned undefined you don't know whether the property's explicitly set to undefined or simply not there (shame on you if there's a difference?).
Your e-mail does point to one minor fly in the ointment for the proposed Object.guard function, specifically the toJSON function for which a [[Get]] will be performed for every object being JSON.stringify'ed. This means that every object that is Object.guard'ed would require a toJSON in its prototype chain. A little annoying but a price to pay, I guess for using Object.guard. One would probably need to do the same for inspect. Of course one can set toJSON and inspect to undefined on the base Object prototype but that would probably be pretty uncool for most JavaScript engines. There'd be other ways around this such as redefining JSON.stringify's use of toJSON to check for presence of the toJSON property before doing a [[Get]]. This would be indistinguishable from current behavior. And though it might seem expensive, I suspect most of the time there will be no toJSON property for most objects so the test for the property existence would be all that would be done.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
Don't forget besides .toJSON
, all the other methods that builtins do a
[[Get]] on: .toString
, .valueOf
, .then
, and all the well-known
symbols (Symbol.iterator, Symbol.isConcatSpreadable, etc) - and in node,
.inspect
in the repl.
You're totally right that Proxy, and ES5 getters before that, makes
"accessing a property" always be something that can throw - just pointing
out that there's a lot of places that [[Get]]
s happen in the language.
It's not a compile-time check because a compile-time check is impossible:
function noname(foo) { let abc = foo.abc; }
How could the compiler possibly know whether abc exists on foo?
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
From: Bob Myers [mailto:rtm at gol.com] Sent: Monday, August 14, 2017 11:55 PM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: Jerry Schulteis <jdschulteis at acm.org>; es-discuss at mozilla.org
Subject: Re: RE: Object.seal, read references, and code reliability
Is there some reason this is not just a compile-time type check? Bob
On Tue, Aug 15, 2017 at 4:16 AM, Alex Kodat <mailto:akodat at rocketsoftware.com> wrote:
Agree, lock’s not a good name. That was just a dumb name because I was too lazy to think of something better. Other names that come to mind (some also with other software connotations):
Object.box Object.protect Object.close Object.guard Object.fence Object.shield
But don’t care that much and probably someone could come up with better. I guess of the above I like Object.guard. So I’ll use that for now…
Also, I’ll retract an earlier stupid statement I made where I suggested that if a property referenced an accessor descriptor without a getter it should throw if the guard bit is set. This is obviously wrong. As long as the property name is present, not having a getter is equivalent to the property being explicitly set to undefined which should not throw. That is:
let foo = Object.guard({a: 1, b: undefined}); console.log(foo.b);
should not throw.
Also, FWIW, I had originally thought of Object.guard as Object.seal on steroids but some thought makes me realize it’s really orthogonal and can picture cases where one might want to seal an object but not guard it and vice versa.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
From: Jerry Schulteis [mailto:mailto:jdschulteis at yahoo.com] Sent: Monday, August 14, 2017 5:21 PM To: Alex Kodat <mailto:akodat at rocketsoftware.com>
Cc: mailto:es-discuss at mozilla.org Subject: Re: RE: Object.seal, read references, and code reliability
I'm ok with the basic idea. I don't think "lock" is a good name for it. Nothing about "Object.lock(myObj)" suggests that future access to a non-existent property of myObj will throw. Also "lock" carries a well-established, unrelated meaning in computer science.
Could this be done with an additional argument to Object.seal(), Object.freeze(), and Object.preventExtensions()? Like so: "Object.seal(myObj, Object.THROW_ON_ABSENT_PROPERTY_READ)"--wordy, but the intent is unmistakable. On Monday, August 14, 2017, 1:16:39 PM CDT, Alex Kodat <mailto:akodat at rocketsoftware.com> wrote:
FWIW, I’m not sure I agree with your OrdinaryGet steps. Also, not sure I like readExtensible as it doesn’t really have anything to do with extensibility. Maybe readStrict? But maybe I’m thinking about extensibility wrong?
In any case, I think the description of [[Get]] would have to be like (sorry, I’ve never done this sort of spec pseudo code so some of this is probably syntactically incorrect):
[[Get]] (P, Receiver, ReadStrict)
- Return ? OrdinaryGet(O, P, Receiver, ReadStrict).
And OrdinaryGet would be like:
OrdinaryGet (O, P, Receiver, ReadStrict)#
- Assert: IsPropertyKey(P) is true.
- Let desc be ? O.[GetOwnProperty].
- If desc is undefined, then a. Let readStrict be ? O.IsReadStrict(Receiver) || ReadStrict b. Let parent be ? O.[GetPrototypeOf]. c. If parent is null then i. Assert: readStrict is false ii. return undefined. d. Return ? parent.[[Get]](P, Receiver, readStrict).
- If IsDataDescriptor(desc) is true, return desc.[[Value]].
- Assert: IsAccessorDescriptor(desc) is true.
- Let getter be desc.[[Get]].
- If getter is undefined, return undefined.
- Return ? Call(getter, Receiver).
Probably adjustments need to be made to other references to [[Get]] and OrdinaryGet, passing false for ReadStrict.
I guess this pseudo code also points out an issue as to whether Object.lock would affect a dynamic property with no getter. Intuitively, it seems to me that it should.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Monday, August 14, 2017 12:00 PM To: Alex Kodat <mailto:akodat at rocketsoftware.com>
Cc: mailto:es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <mailto:akodat at rocketsoftware.com> wrote:
Is there any reason that there’s no flavor of Object.seal that would throw an exception on read references to properties that are not found on the specified object?
This sounds like a good idea at a high level. Will be very interested to know what implementers have to say about it from an implementation perspective.
In spec terms, at first glance it's mostly just a new pair of steps in OrdinaryGet ordinary object internal function (added Step 3.a and 3.b):
1. Assert: IsPropertyKey(P) is true.
2. Let desc be ? O.[[GetOwnProperty]](P).
3. If desc is undefined, then
a. Let readExtendable be ? IsReadExtensible(Receiver).
b. If readExtendable is false, throw TypeError.
c. Let parent be ? O.[[GetPrototypeOf]]().
d. If parent is null, return undefined.
e. Return ? parent.[[Get]](P, Receiver).
4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
5. Assert: IsAccessorDescriptor(desc) is true.
6. Let getter be desc.[[Get]].
7. If getter is undefined, return undefined.
8. Return ? Call(getter, Receiver).
...where IsReadExtensible
is an op to test the new flag. (Used pre
markdown to avoid the [[...](.)
being treated as links without having to add \
for those not reading the rendered version.)
One concern is people already have trouble keeping sealed and frozen straight (or is that just me?). Adding a locked (or whatever) makes that even more confusing. But the semantics of sealed and frozen can't be changed to include this now.
-- T.J. Crowder
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
How could the compiler possibly know whether abc exists on foo?
Sorry for not being clearer. It would know by means of appropriate type declarations/assertions.
Bob
That's not a feature JavaScript has - you might be thinking of alternative languages, which are not JavaScript.
That's not a feature JavaScript has - you might be thinking of alternative languages, which are not JavaScript.
Yes, I know. JavaScript doesn't have lock
either, yet here we are talking
about it.
The OP is concerned about accesses to unknown or undefined properties. As I understand it, he proposes mechanisms to throw errors on attempts to make such accesses.
The astronauts in the Mars lander would certainly not be happy with a solution involving the landing app crashing when it encountered some rare code path which accessed an unknown property on an object. I'm sure they'd prefer a compile-time error to be generated safely back on earth during the development process, long before the code shipped.
What I am saying is that as far as I can tell the OP's notion of locking is an attempt to add a feature to the language which amounts to enforcing type constraints at run-time. In my humble opinion, type constraints should be enforced during a type-checking phase, based on type assertions, annotations, declarations, inferences, and definitions. Yes, I know JS does not have those now. I hope it does sometime in the future. If it never does, then I don't think we should try to enforce them on a piece-meal basis via run-time checks and special locked statuses.
Bob
Your points are goods ones. However, I believe .toString and .valueOf are on the Object prototype so should not be a problem. .then is on the Promise object prototype so also not a problem.
The well-known Symbols are indeed an issue. My inclination would be to exempt Symbols from Object.guard exceptions as their whole point is feature detection (wonder why toJSON didn't use a symbol?). The point of Object.guard is to catch extremely common screw-ups, AKA, typos or misunderstandings such as using foo.longlist when it should be foo.longList (or vice versa).
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
From: Jordan Harband [mailto:ljharb at gmail.com] Sent: Tuesday, August 15, 2017 12:02 AM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: don at sencha.com; es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
Don't forget besides .toJSON
, all the other methods that builtins do a [[Get]] on: .toString
, .valueOf
, .then
, and all the well-known symbols (Symbol.iterator, Symbol.isConcatSpreadable, etc) - and in node, .inspect
in the repl.
You're totally right that Proxy, and ES5 getters before that, makes "accessing a property" always be something that can throw - just pointing out that there's a lot of places that [[Get]]
s happen in the language.
On Mon, Aug 14, 2017 at 9:57 PM, Alex Kodat <mailto:akodat at rocketsoftware.com> wrote:
On Mon Aug 14 18:49:35 UTC 2017 Don Griffin wrote
<imho>Land mines like this are bad for JS. Reads should always be safe to do as in "foo && foo.bar". Throwing errors from that is always bad. duck-typing, feature detection etc are all valid uses for objects ... even sealed/frozen/locked/obfuscated ones :) </imho>
Just give me "undefined" on reads like these. I would much rather have that and possibly Object.isFrozen/isLocked/isSealed etc methods to test these conditions if appropriate.
No one would force anyone to use Object.guard (what I initially called Object.lock). It would introduce no backward incompatibilities.
Whatever landmines this feature would introduce have been there at least since proxies were introduced since, with proxies, a get for a non-existent property might throw. In fact, even with a simple property getter you can get an exception from a getter. So the good old days when reads were always safe was a very long time ago. Of course, in your own code, you can very easily make your reads always safe and I'd say more power to you.
Duck-typing, feature detection, etc. can be wonderful things but so can objects with a well-defined set of properties. And in the latter case, it makes debugging much easier and faster if references to non-existent properties throw an exception.
Even in the face of Object.guard, feature detection could be done with Object.getOwnPropertyNames and Object.getPrototypeOf or Object.prototype.hasOwnProperty (the latter if one doesn't care about properties in prototype classes). In fact, as a general rule doing a feature test as if (foo.feature) is kinda sketchy as, of course, if foo.feature were 0 or "" or false your test would suggest a missing feature. Even if foo.feature returned undefined you don't know whether the property's explicitly set to undefined or simply not there (shame on you if there's a difference?).
Your e-mail does point to one minor fly in the ointment for the proposed Object.guard function, specifically the toJSON function for which a [[Get]] will be performed for every object being JSON.stringify'ed. This means that every object that is Object.guard'ed would require a toJSON in its prototype chain. A little annoying but a price to pay, I guess for using Object.guard. One would probably need to do the same for inspect. Of course one can set toJSON and inspect to undefined on the base Object prototype but that would probably be pretty uncool for most JavaScript engines. There'd be other ways around this such as redefining JSON.stringify's use of toJSON to check for presence of the toJSON property before doing a [[Get]]. This would be indistinguishable from current behavior. And though it might seem expensive, I suspect most of the time there will be no toJSON property for most objects so the test for the property existence would be all that would be done.
Alex Kodat Senior Product Architect Rocket Software t: tel:%2B1%20781%20684%202294 • m: tel:%2B1%20315%20527%204764 • w: www.rocketsoftware.com
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: tel:%2B1%20877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
-
Every non-null/undefined value is checked for "then" when it's passed to a Promise's resolver, not just promises.
-
Every non-null/undefined value, including
Object.create(null)
ornew (class extends null {})
, has valueOf and/or toString checked when attempting to convert it to a primitive. -
toJSON was added in 2009/2011; Symbols were added in 2015.
Just a quick note: Look up "strong mode", an experiment by Google attempting to look for code quality improvement and perf gains for locking class instances by default. I feel it's pretty relevant here, and here's a quick summary of their general findings:
- It helped code quality only a little (stuff better code design could solve better)
- It had very little gain in performance
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <akodat at rocketsoftware.com>
wrote:
We’ve found Object.seal to be a huge aid in improving programmer productivity by catching coding errors early and preventing people from intentionally or unintentionally adding properties to objects outside the constructor.
Bob raised this but no one's actually said the name and you didn't mention it, so: If you haven't already, you might look into TypeScript, which does these things at compile time via type declarations (or with IDE support, even earlier). At least until/unless this idea goes somewhere. Your team seem to want the kind of constraints it puts on you.
-- T.J. Crowder
-- Jordan Harband wrote ---
- Every non-null/undefined value is checked for "then" when it's passed to a Promise's resolver, not just promises.
OK, fair enough. I didn't realize Promise does this (haven't really used it much). This can be dealt with easily enough by the same technique used for toJSON, simply adding it to guarded object prototypes. All of these issues could also be dealt with by, if Object.guard were to ever make it into the spec, adding magic functions like toJson and then to the base object prototype in the same spec (set to undefined). This would present no backward incompatibilities (well, unless some code is doing something truly odd, IMO) and provides some visibility to these magic functions.
-- Jordan Harband wrote ---
- Every non-null/undefined value, including
Object.create(null)
ornew (class extends null {})
, has valueOf and/or toString checked when attempting to convert it to a primitive.
Sure but these all exist on Object.prototype so these would never throw for guarded objects. Maybe I'm missing something here?
-- Jordan Harband wrote ---
- toJSON was added in 2009/2011; Symbols were added in 2015.
OK, that explains it. And Promise probably didn't use Symbol.then (or whatever) because of the slight extra hassle of having to do object[Symbol.then] = ... instead of just adding a function using object literals, I'd guess.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.com
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
On Tue, Aug 15, 2017 at 2:43 PM, Alex Kodat <akodat at rocketsoftware.com>
wrote:
Sure but these all exist on Object.prototype so these would never throw for guarded objects. Maybe I'm missing something here?
Not all objects inherit from Object.prototype
. Example:
const o = Object.create(null);
console.log("valueOf" in o); // false
In fact, not even all objects with prototypes inherit from
Object.prototype
:
const b = Object.create(null);
console.log("valueOf" in b); // false
console.log(Object.getPrototypeOf(b)); // null
const d = Object.create(b);
console.log("valueOf" in d); // false
console.log(Object.getPrototypeOf(d)); // b
-- T.J. Crowder
Yup, I kinda liked strong mode but the good news/bad news was that you got a lot of behaviors in a single package which was convenient but also problematic if any of the behaviors proved inappropriate for a context.
IMO, improving code reliability is trench warfare where progress is usually measured in inches and while I think Object.guard is a useful tool I’m under no illusions that it’s a game-changer (any more than Object.seal, say). But there are a large number of cases where when a programmer mistakenly types foo.bah as opposed to foo.bar it is inarguably a mistake and you’re doing the programmer a favor by immediately pointing out the error. So if we could catch these cases relatively easily with (almost?) no cost, why not?
I’ll concede that there are issues here that I and others have brought up that suggest that Object.guard is not quite as simple as Object.seal but I believe these issues are not so daunting as to disqualify the feature. But reasonable people could disagree.
As far as performance goes, I don’t see how Object.guard could possibly improve performance. That’s not its purpose.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 • m: +1 315 527 4764 • w: www.rocketsoftware.comwww.rocketsoftware.com
From: Isiah Meadows [mailto:isiahmeadows at gmail.com] Sent: Tuesday, August 15, 2017 12:57 AM To: Alex Kodat <akodat at rocketsoftware.com>; Jordan Harband <ljharb at gmail.com>
Cc: es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
Just a quick note: Look up "strong mode", an experiment by Google attempting to look for code quality improvement and perf gains for locking class instances by default. I feel it's pretty relevant here, and here's a quick summary of their general findings:
- It helped code quality only a little (stuff better code design could solve better)
- It had very little gain in performance
================================ Rocket Software, Inc. and subsidiaries ■ 77 Fourth Avenue, Waltham MA 02451 ■ Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.
by immediately pointing out the error.
This does not "immediately point out the error". It would "point out the error" (by raising an exception) only f and when the code path involving the mistyped property name were actually followed. The thrown error might be swallowed in some contexts, or disappear into the ether in a production environment, or cause a server to crash.
We have had tools for immediately pointing out such errors for more than half a century. They are called "compilers".
with (almost?) no cost,
I will go out on a limb here and predict that the community would be unlikely to accept any alternative which adds a nanosecond of cost.
Bob
Switching to my personal gmail account to reduce junk in the postings (sorry about that)...
My proposal was prompted by an internal discussion about using TypeScript. As you might guess I was opposed but the fact that there was no good way to catch typos in referencing properties on objects that had a well-defined set of properties was used against my position. Note that I'm not opposed to TypeScript, it’s just that adding an extra layer is not without its costs.
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Tuesday, August 15, 2017 1:12 AM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: es-discuss at mozilla.org Subject: Re: Object.seal, read references, and code reliability
On Mon, Aug 14, 2017 at 5:32 PM, Alex Kodat <mailto:akodat at rocketsoftware.com> wrote:
We’ve found Object.seal to be a huge aid in improving programmer productivity by catching coding errors early and preventing people from intentionally or unintentionally adding properties to objects outside the constructor.
Bob raised this but no one's actually said the name and you didn't mention it, so: If you haven't already, you might look into TypeScript, which does these things at compile time via type declarations (or with IDE support, even earlier). At least until/unless this idea goes somewhere. Your team seem to want the kind of constraints it puts on you.
-- T.J. Crowder
This is interesting but don’t think it affects the arguments for/against Object.guard significantly. For objects that don’t have Object.prototype in their prototype chain, don’t guard them. Or guard them if it suits your needs. And add or don’t add valueOf, toString, etc.. to the objects or their prototypes, depending on your needs. You would certainly not use Object.guard on every object.
My pictured common use case is a constructor or block of code would create an object that represents a bunch of related data and, when done, Object.guards and Object.seals the object. We already tend to do the latter. This would catch typos (yes, only when they’re executed) and also forces unruly programmers (including me) to add properties in one place so it’s easy to see what all the properties are on an object (Object.seal accomplishes this).
For APIs we tend to almost always use closures as, of course, these provide much better encapsulation than objects. We’ve never bothered to Object.seal/Object.freeze such API objects as assigning to a property in them is simply is not something someone does accidentally. But, now that I think about it, we might Object.guard them as it would catch the occasional boo-boo where one forgets to put the () on a function call (I do this every now and then). Not a huge deal but perhaps a little useful.
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Tuesday, August 15, 2017 8:50 AM To: Alex Kodat <akodat at rocketsoftware.com>
Cc: Jordan Harband <ljharb at gmail.com>; es-discuss at mozilla.org
Subject: Re: Object.seal, read references, and code reliability
On Tue, Aug 15, 2017 at 2:43 PM, Alex Kodat <akodat at rocketsoftware.com <mailto:akodat at rocketsoftware.com> > wrote:
Sure but these all exist on Object.prototype so these would never
throw for guarded objects. Maybe I'm missing something here?
Not all objects inherit from Object.prototype
. Example:
const o = Object.create(null);
console.log("valueOf" in o); // false
In fact, not even all objects with prototypes inherit from Object.prototype
:
const b = Object.create(null);
console.log("valueOf" in b); // false
console.log(Object.getPrototypeOf(b)); // null
const d = Object.create(b);
console.log("valueOf" in d); // false
console.log(Object.getPrototypeOf(d)); // b
-- T.J. Crowder
On Tue, Aug 15, 2017 at 4:10 PM, Alex Kodat <alexkodat at gmail.com> wrote:
But, now that I think about it, we might Object.guard them as it would catch the occasional boo-boo where one forgets to put the () on a function call
How would Object.guard
catch that? My understanding of your suggestion
was that reading a property from a "guarded" object that it didn't have
("propname" in obj
would be false) would throw. If you do foo.bar
where
you meant foo.bar()
, the bar
property still exists on foo
.
-- T.J. Crowder
An alternative suggestion to catch mistyped properties is "automated tests", which hopefully you have anyways.
Sorry, that was a stupid comment on my part. I was thinking that if you typed foo.bah instead of foo.bar() (making two mistakes) we’d catch it. Pretty thin gruel. I guess, at least with the current incarnation of V8 you would get a slightly nicer error message if you typed foo.bah() instead of foo.bar() assuming foo were guarded. Right now foo.bah() indicates that foo.bah is not a function. But that’s just an implementation detail.
So I’d go back to Object.guard being mostly useful for validating references to non-function properties which was my original motivation.
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Tuesday, August 15, 2017 10:39 AM To: Alex Kodat <alexkodat at gmail.com>
Cc: Jordan Harband <ljharb at gmail.com>; es-discuss at mozilla.org
Subject: Re: Object.seal, read references, and code reliability
On Tue, Aug 15, 2017 at 4:10 PM, Alex Kodat <mailto:alexkodat at gmail.com> wrote:
But, now that I think about it, we might Object.guard them as it would catch the occasional boo-boo where one forgets to put the () on a function call
How would Object.guard
catch that? My understanding of your suggestion was that reading a property from a "guarded" object that it didn't have ("propname" in obj
would be false) would throw. If you do foo.bar
where you meant foo.bar()
, the bar
property still exists on foo
.
-- T.J. Crowder
Of course, testing is absolutely critical. Programmers rereading their own code, code reviews, hiring good programmers, coding standards. All good things. I'm under no illusions that Object.guard would be sufficient to keep the world safe from bad code. It would just be another arrow in the quiver.
Object.guard would augment tests by making sure the an if (foo.bah) (should be foo.bar) throws rather than simply bypasses the if condition. Yes, of course you should make sure your tests would detect that the if condition wasn't exercised when it should be but well... heat of battle.. etc... Coverage tools are also, of course a good thing though they might not help if your mistake was if (!foo.bah). Coding's complicated and the more weapons you have the better as long as you're not forced to use them and as long as they don't needlessly complicate the language.
Object.guard doesn't really change the language and it would be up to programmers' discretion as to whether to use it so I think it's a reasonable arrow to add to the JavaScript quiver but I also understand that lines have to be drawn somewhere...
From: Jordan Harband [mailto:ljharb at gmail.com] Sent: Tuesday, August 15, 2017 12:22 PM To: T.J. Crowder <tj.crowder at farsightsoftware.com>
Cc: Alex Kodat <alexkodat at gmail.com>; es-discuss at mozilla.org
Subject: Re: Object.seal, read references, and code reliability
An alternative suggestion to catch mistyped properties is "automated tests", which hopefully you have anyways.
On Tue, Aug 15, 2017 at 8:38 AM, T.J. Crowder <mailto:tj.crowder at farsightsoftware.com> wrote:
On Tue, Aug 15, 2017 at 4:10 PM, Alex Kodat <mailto:alexkodat at gmail.com> wrote:
But, now that I think about it, we might Object.guard them as it would catch the occasional boo-boo where one forgets to put the () on a function call
How would Object.guard
catch that? My understanding of your suggestion was that reading a property from a "guarded" object that it didn't have ("propname" in obj
would be false) would throw. If you do foo.bar
where you meant foo.bar()
, the bar
property still exists on foo
.
-- T.J. Crowder
My mind's been completely changed on this by the arguments from various folks.
It seems to me that Object.seal
and Object.freeze
have a runtime
purpose: Protecting your objects from extension when passed to foreign
code. It's great you're also getting value out of it within your code
base, but I don't see that as the primary motivation of them.
Whereas this guard concept is (almost?) entirely a "catch the bug early" feature, which it seems to me is more in the realm of linting, TypeScript-style compilation, and (as Jordan mentioned) testing.
Adopting something like TypeScript is indeed a non-trivial change not just to your build process but primarily to your working methods. It would have costs and benefits. Your team will have to weigh the inevitable costs ("How the heck do I tell TypeScript _____?!") against the benefits of catching these kinds of problems at the point of authoring (with IDE support) or compiling the code, far earlier than you would have relying on a runtime check.
My main point being: There's a clear runtime purpose to what we have. I'm not seeing the runtime purpose for this guard idea. Is it a failure of imagination on my part?
-- T.J. Crowder
If you want to protect your objects from the ravages of the unwashed masses, the best way is with closures. Object.seal doesn’t really buy you a heck of a lot of protection as while, sure, it prevents someone from adding new properties to your objects it doesn’t prevent them from messing with your existing properties which seems a lot worse. Plus, even if they don’t mess with them, they can see them and build dependencies on implementation details (the Node.js ecosystem has plenty of this) that make it difficult to change things intended for internal use only. If you’re worried about this, use closures (or Symbol properties or weak maps though I think closures are generally superior).
So maybe it’s my failure of imagination but I view Object.seal as a tool for catching typos in assignments and for enforcing a coding standard where all properties must be added “up front”. At least in V8, defining all properties up front minimizes the number of (internal C++) Maps and helps the optimizer out and seal simply prevents people from messing this up by deleting a property but performance seems an esoteric worry when consumers are deleting object properties.
Object.freeze I guess lets you expose an object that you know should not change but on whose contents your code depends. I guess this comes up in applications but all the uses of Object.freeze I've come across are in internal code -- I know some object should not change after some event so I freeze it to catch any mistaken attempts to change it. Again, trapping errors, not providing encapsulation. Even when the object's frozen, I don't want to expose it outside my code as this can paint me in a corner.
I certainly have a hard time picturing Object.seal or Object.freeze as encapsulation aids which is what I understand when I read "protecting your objects". So, at least from my point of view, Object.guard would be a tool in the same problem space as Object.seal and to a somewhat lesser degree Object.freeze. I'll freely admit, Object.seal is a more powerful tool than Object.guard for my purposes as it allows enforcement (more or less) of certain coding standards. But I'd still love to have Object.guard.
From: T.J. Crowder [mailto:tj.crowder at farsightsoftware.com] Sent: Tuesday, August 15, 2017 12:52 PM To: Alex Kodat <alexkodat at gmail.com>
Cc: Jordan Harband <ljharb at gmail.com>; es-discuss at mozilla.org
Subject: Re: Object.seal, read references, and code reliability
My mind's been completely changed on this by the arguments from various folks.
It seems to me that Object.seal
and Object.freeze
have a runtime purpose: Protecting your objects from extension when passed to foreign code. It's great you're also getting value out of it within your code base, but I don't see that as the primary motivation of them.
Whereas this guard concept is (almost?) entirely a "catch the bug early" feature, which it seems to me is more in the realm of linting, TypeScript-style compilation, and (as Jordan mentioned) testing.
Adopting something like TypeScript is indeed a non-trivial change not just to your build process but primarily to your working methods. It would have costs and benefits. Your team will have to weigh the inevitable costs ("How the heck do I tell TypeScript _____?!") against the benefits of catching these kinds of problems at the point of authoring (with IDE support) or compiling the code, far earlier than you would have relying on a runtime check.
My main point being: There's a clear runtime purpose to what we have. I'm not seeing the runtime purpose for this guard idea. Is it a failure of imagination on my part?
-- T.J. Crowder
I'm sure this has been brought up before but my naïve searches come up empty handed so...
Is there any reason that there's no flavor of Object.seal that would throw an exception on read references to properties that are not found on the specified object? While this could be accomplished with an extra parameter on seal, for the save of this discussion I'll call it a different function: Object.lock.
We've found Object.seal to be a huge aid in improving programmer productivity by catching coding errors early and preventing people from intentionally or unintentionally adding properties to objects outside the constructor. Our constructors typically add all the properties to an object and then seal it. This ensures that there's one place to see all the properties for an object and the extra cost of adding perhaps seldom used properties is won back because the shape of the object is guaranteed not to change after the constructor playing very nicely with V8 optimizations though presumably other engines would also benefit. While, of course, there are objects that don't fit this static-ish model, the bulk of them do and for those, this approach is a huge win for code maintainability.
But, what still bites frequently is read references to non-existent properties which are returned as undefined. Quite often, this results in a quick exception when the undefined is used as a this and sometimes the problem is exposed as a NaN or the string "undefined" appearing in text or whatever, at which point one must backtrack to the source of the problem. The worst is when the property is used as a Boolean and undefined simply behaves as false. While I'm sure there are cases where this behavior is useful, we certainly haven't run across them and the fact that Object.seal doesn't protect against reads of non-existent properties is a complete negative. But, of course, backward-compatibility means Object.seal cannot do this.
My guess is that the difficulty would lie in the prototype chain where the runtime wouldn't know that the property doesn't exist until it's at the end of the prototype chain at which point it might not have a reference to the original object. Even worse, an Object.lock might have locked some intermediate object in the prototype chain which presumably should cause an exception on a non-existent property reference. Essentially an Object.lock on any object in the prototype chain should act as a barrier to the return of undefined for non-existent properties.
This would suggest an implementation where before going down the prototype chain the JS runtime would OR the lock bit in the current object with a running lock barrier bit. If it hits the end of the prototype chain and the property has not been found and the lock barrier bit is set, exception. Of course, this would add at least one extra OR for every prototype chain step but that doesn't seem too horrible and certainly for something like V8 with its underlying object Maps this would essentially add no overhead for the vast majority of property retrievals.
And yes, this can be accomplished with a simple generic proxy but that seems like a heavyweight solution to the problem.
Alex Kodat Senior Product Architect Rocket Software t: +1 781 684 2294 * m: +1 315 527 4764 * w: www.rocketsoftware.comwww.rocketsoftware.com
================================ Rocket Software, Inc. and subsidiaries ? 77 Fourth Avenue, Waltham MA 02451 ? Main Office Toll Free Number: +1 877.328.2932 Contact Customer Support: my.rocketsoftware.com/RocketCommunity/RCEmailSupport Unsubscribe from Marketing Messages/Manage Your Subscription Preferences - www.rocketsoftware.com/manage-your-email-preferences Privacy Policy - www.rocketsoftware.com/company/legal/privacy-policy
This communication and any attachments may contain confidential information of Rocket Software, Inc. All unauthorized use, disclosure or distribution is prohibited. If you are not the intended recipient, please notify Rocket Software immediately and destroy all copies of this communication. Thank you.