Array.prototype change (Was: @@toStringTag spoofing for null and undefined)
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock
This looks like the sort of evidence we asked for.
I don't really think so. This is some tweets and books, not evidence of real-world usage that would break popular websites and cause browser game theory to kick in. Such evidence is best gathered by browser vendors making the change and seeing what it impacts. I believe IE12/Spartan might already be doing so---Brian, confirm/deny?
not evidence of real-world usage that would break popular websites
the Web is (still and thankfully) not about popular websites only.
Using the Array.prototype
instead of creating instances in the wild has
been seen for long time, same way you don't do {}.toString.call
but
Object.prototype.toString.call
instead.
When a method like concat
has no side effect to the prototype but can be
used as empty starting point for an Array creation, it's perfectly fine to
use it as such utility.
I am not sure that's the only exception though, and I don't have strong opinion about this specific matter (there must be reasons to change and software needs updates anyway) but I agree with Kyle that if ES6 claims backward compatibility, it should stick with it.
This is a breaking change, small or big (famous/populare websites) is sort of less relevant.
In github, as example, there's some usage already showing up: search?utf8=✓&q="Array.prototype.concat("&type=Code&ref=searchresults
I've also seen many Array.prototype.concat.call([], ...)
which is
extremely pointless since that is the equivalent of [].concat(...)
but
from time to time I use similar logic shown in Kyle example with reduce.
Again, I don't remember why these builtins needed such change, but things like these should be probably announced as "potential breaking" so that developers can be aware and eventually fix things here or there.
Andrea, you seem to not understand what change is being discussed here. Nobody is talking about removing or changing the behavior of Array.prototype.concat. Please re-read.
I think I do understand ... is this operation valid in ES6 ?
var oneTwoThree = Array.prototype.concat(1, 2, 3); // [1, 2, 3]
'cause that was the initial concern, and as far as I understand that will break.
Or does it?
On Thu, Feb 19, 2015 at 8:57 AM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
not evidence of real-world usage that would break popular websites
the Web is (still and thankfully) not about popular websites only.
Using the
Array.prototype
instead of creating instances in the wild has been seen for long time, same way you don't do{}.toString.call
butObject.prototype.toString.call
instead.When a method like
concat
has no side effect to the prototype but can be used as empty starting point for an Array creation, it's perfectly fine to use it as such utility.I am not sure that's the only exception though, and I don't have strong opinion about this specific matter (there must be reasons to change and software needs updates anyway) but I agree with Kyle that if ES6 claims backward compatibility, it should stick with it.
This is a breaking change, small or big (famous/populare websites) is sort of less relevant.
In github, as example, there's some usage already showing up:
search?utf8=✓&q="Array.prototype.concat("&type=Code&ref=searchresults
Take a look again at those results. Although you are indeed searching for "Array.prototype.concat(", the first four pages of Github matches I saw were for "Array.prototype.concat.apply(", which would still work fine.
Anyone know how to do an exact match search on Github? Since these results are sorted by best match, perhaps this indicates that there are no exact matches for "Array.prototype.concat(" ?
I've also seen many
Array.prototype.concat.call([], ...)
which is extremely pointless since that is the equivalent of[].concat(...)
but from time to time I use similar logic shown in Kyle example with reduce.Again, I don't remember why these builtins needed such change, but things like these should be probably announced as "potential breaking" so that developers can be aware and eventually fix things here or there.
On Thu, Feb 19, 2015 at 4:38 PM, Domenic Denicola <d at domenic.me> wrote:
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock
This looks like the sort of evidence we asked for.
I don't really think so. This is some tweets and books, not evidence of real-world usage that would break popular websites and cause browser game theory to kick in. Such evidence is best gathered by browser vendors making the change and seeing what it impacts.
Agree with Domenic. The other thing needed quickly is for someone to add tests for this to test262, so that there is browser game theory pressure in the right direction.
I believe IE12/Spartan might already be doing so---Brian, confirm/deny?
Indeed. If there is already a browser testing the waters, that would be exactly the kind of evidence we need.
On Feb 19, 2015, at 9:01 AM, Andrea Giammarchi wrote:
I think I do understand ... is this operation valid in ES6 ?
var oneTwoThree = Array.prototype.concat(1, 2, 3); // [1, 2, 3]
'cause that was the initial concern, and as far as I understand that will break.
Or does it?
Yes, it will break if Array.prototype is an ordinary object because of the special treatment that Array.prototype.concat gives to arrays. Also, JSON.stringify.
An alternative fix would be to make IsArray(Array.prototype) answer true, but that seems like even a big hack. It's also hard to specify in a manner that works cross-realms.
On Feb 19, 2015, at 9:01 AM, Andrea Giammarchi wrote:
I think I do understand ... is this operation valid in ES6 ?
var oneTwoThree = Array.prototype.concat(1, 2, 3); // [1, 2, 3]
you would get: [Array.prototype, 1, 2, 3]
That's what convinced me.
I remember once upon a time double quotes meant explicit intent of exact match, t least in Google, IIRC, dunno when that good idea got lost.
Yes, it's hard to find for exact matches but if you also check the reduce
example, Array.prototype
is simply passed around.
Again, I don't know how much would break here, all I know is that it would be excellent to have a list of potentially breaking changes like this one. We can debate for days how breaking this is, but as a change ... it breaks, as Allen confirmed.
Can we have either a list of these changes (I'm sure I've missed many, like this one, for example) or can we just stick with the "ES6 is backward compat" umbrella?
Best
From: Andrea Giammarchi [mailto:andrea.giammarchi at gmail.com]
Can we have either a list of these changes (I'm sure I've missed many, like this one, for example)
people.mozilla.org/~jorendorff/es6-draft.html#sec-in-the-6th-edition
Annex E ... I've never reached that part ...
I'm not writing to start or join a debate on the merits of using Function.prototype
and Array.prototype
in the aforementioned ways. I'm writing to confirm that they are in fact used, not just theoretically made up.
I have been writing about and teaching for several years usage of Function.prototype
as a (convenience) no-op empty function and Array.prototype
as a (convenience) default empty array (not to be mutated, obviously). The most recent case of me publicly talking about these techniques is in my recently published book "YDKJS: Types & Grammar":
getify/You-Dont-Know-JS/blob/master/types & grammar/ch3.md#prototypes-as-defaults
While I can't go back now and get at all those old code bases that I either consulted on or taught on in workshops, and though that code may unfortunately not show up in GitHub searches, I assure you that such code exists. Moreover, I have right now a local (non-GH, for certain reasons) fork of Esprima that I've been hacking on for about a year, and atm I have 34 occurrences in it of using Array.prototype
as a shared empty default array for starting iterations, etc.
There is no debate of if it will break code, but rather if it's ok to break code since the numbers are sufficiently low. Please don't pretend this is just academic contrarianism at play.
Furthermore, I would posit that whatever evidence was used to roll back the Function.prototype
change -- that is, people who use it as an empty no-op function -- would be the symmetric evidence, and the nearly identical mindset, to using Array.prototype
as a default empty array. That is, I think there's at least a decent amount of correlation/overlap there.
However, I'm not seeing or finding the contra-argument of why it's so much better to justify making this breaking change, nor why it makes more sense to break Array.prototype
usage but not Function.prototype
usage.
Personally I'd love to see Array.empty
, Function.empty
, Map.empty
,
String.empty
, etc as nonconfigurable nonwritable
frozen/sealed/extensionsPrevented available by default - that might provide
the (polyfillable) use case Kyle is currently describing, and might provide
a path to making builtins’ prototypes not be instances of the builtin,
without breaking code.
That said, I also agree with Kyle that this is something that will
certainly break some code. It’s already been decided that
Function.prototype
remains a function - if Array.prototype
remains an
array, are there any other builtins that anybody (Kyle, or otherwise) sees
as problematic to continue with the breaking change (ie, continue with
making them normal objects)?
Kyle, if there was Array.empty
and Function.empty
, which would both be
polyfillable, would you find those sufficient replacements for your current
usages of Function.prototype
and Array.prototype
?
(my thought process here is, can we provide for this use case without also needing to keep the magic behavior of builtin's prototypes)
are there any other builtins that anybody (Kyle, or otherwise) sees as problematic to continue with the breaking change
As that book chapter mentions, the only other one I've ever used is RegExp.prototype (being the default empty match /(?:)/ regular expression). I have used that only once in my recollection, though I've certainly taught it so I don't know if others ever did. I would like it to keep working, but it's not a sword I'd die on. AWB has suggested on twitter a patch to test() and exec() that could hack around that case while letting the ES6 change go through.
Kyle, if there was Array.empty and Function.empty, which would both be polyfillable, would you find those sufficient replacements for your current usages of Function.prototype and Array.prototype?
Yes, a long time back I proposed (not on here, but informally) that there should be just such a thing Function.empty, but basically just settled back into using Function.prototype
since it was already there (and I didn't conceive of it ever changing).
The points in favor of either the prototype exotic or an "empty" stand-in are: 1) convenience 2) possible performance aide to engines.
can we provide for this use case
I certainly wasn't coming to this list to propose new features for ES6, as late as it is. I only just late last nite found out about this change, and was just hoping it wasn't too late to abort the change. But if the fix/compromise is empty stand-ins that give a polyfill path to migration, I'd be OK with that.
if we'd like to have Array.empty, Function.empty, String.empty and friends, what's wrong with having these as we always had already: as prototypes?
I see just moving and duplicating gotchas, instead of keeping in a well known behavior.
This exotic "problem" ... I never really understood it, I blindly trusted it was needed to avoid that kind of invisible Empty constructor Function.prototype inherits from ... which is the Empty we all look for too.
Oh well, chicken/eggs inheritance is hard I guess :D
Just kidding, if not for good reasons, throw away 60% of Annex E ( also the Array thingy is duplicated in there, it's at the bottom and before too in the HTML version ... just saying )
On 19 Feb 2015, at 21:09, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
if we'd like to have Array.empty, Function.empty, String.empty and friends, what's wrong with having these as we always had already: as prototypes?
I find it more self-explanatory. Here, prototypes are taking on a role that they don’t usually have: they are empty instances of “their” constructors.
A much more important reason, though, is that you want to freeze everything *.empty
, because these instances will get shared and then mutability is a problem. However, you can’t freeze Array.prototype
.
Greetings,
Axel
On Thu, Feb 19, 2015 at 12:14 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
However, you can’t freeze
Array.prototype
We do.
yep, not only you can, but also consider if that was frozen, half of ES5 wouldn't have existed.
You want to exted it? Thanks to ES5 just go for it! You want to be obtrusive or that secure? Freeze it. Does this affect the ability to use Array.prototype as empty array? Not at all, it does not change a single bit of empty purpose.
P.S. yes, the semantic already frozen purpose of an empty property though, I agree that'd be cool
you want to freeze everything *.empty
I don't think most of those need to be frozen, per se, since they're already immutable: Function
, String
, Number
, Boolean
, RegExp
, … all immutable themselves. Array.prototype
is however mutable (Array.prototype.push(1,2,3)
), so freezing it from mutation is an extra step of caution you might want to take.
FWIW, I've used Array.prototype
as an empty array in quite a few cases, and never actually run across one where it got mutated. That part is currently just theory, I think.
you can’t freeze Array.prototype.
I think what he meant was, freezing Array.prototype would both prevent it from being mutated, but also prevent it from being extended (Array.prototype.superCool = ..). That seems, to me anyway, as a negative. So in support of Axel's argument, an Array.empty
could definitely be frozen, if it were separate, without affecting Array.prototype
extensibility.
I just remembered that I also do a sort of Object.empty
in my own code somewhat frequently, as can be seen here for example:
getify/asynquence/blob/master/asq.src.js#L826
Declaring an empty object: var ø = Object.create(null)
, and then using that ø
as a sort of "global" DMZ object that I use for any place where I need a throw-away this
binding, like apply(..)
, bind(..)
, etc.
I also wrote about that technique in "YDKJS: this & Object Prototypes", here:
getify/You-Dont-Know-JS/blob/master/this & object prototypes/ch2.md#safer-this
So, having an Object.empty
might be nice in place of ø
.
On Feb 19, 2015, at 12:09 PM, Andrea Giammarchi wrote:
if we'd like to have Array.empty, Function.empty, String.empty and friends, what's wrong with having these as we always had already: as prototypes?
Just to keep things focused. *.empty is not something that is on the table for ES6. It would have to go into the ES7 track.
Today's issue, is
-
do we need to revert Array.prototype to being an Array exotic object (I think we do)
-
do we need to revert RegExp.prototype to being a RegExp instance.(with initialize RegExp internal slots) (maybe not, and alternatively I have very minor tweaks to the exec and test methods that will maintain the most likely such legacy RegExp.prototype uses without needing those internal slots)
Is there a good reason behind making prototypes ordinary objects?
On the margin - I would assume Array.empty to be static method to clean array.
On Thu, Feb 19, 2015 at 12:56 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
On Feb 19, 2015, at 12:09 PM, Andrea Giammarchi wrote:
if we'd like to have Array.empty, Function.empty, String.empty and friends, what's wrong with having these as we always had already: as prototypes?
Just to keep things focused. *.empty is not something that is on the table for ES6. It would have to go into the ES7 track.
Today's issue, is
do we need to revert Array.prototype to being an Array exotic object (I think we do)
do we need to revert RegExp.prototype to being a RegExp instance.(with initialize RegExp internal slots) (maybe not, and alternatively I have very minor tweaks to the exec and test methods that will maintain the most likely such legacy RegExp.prototype uses without needing those internal slots)
RegExp.prototype.compile people.mozilla.org/~jorendorff/es6-draft.html#sec-regexp.prototype.compile is a visible side effect that survives Object.freeze. It is only not a global communications channel because there is no longer a primordial RegExp instance. So we cannot consider making RegExp.prototype a RegExp unless Object.freeze must disable RegExp.prototype.compile on RegExp instances.
Does it not make sense anyways for Object.freeze on a RegExp instance to prohibit people.mozilla.org/~jorendorff/es6-draft.html#sec-regexpinitialize from being called?
var regex = Object.freeze(/a/g); regex.compile('b', 'i');
should throw
imo.
-
The ES6 class pattern makes abstractions whose .prototype is an ordinary object. This change makes the builtin abstractions more consistent with class-based abstractions, minimizing surprise for future JS programmers who learn the language at ES6 or later.
-
From a security perspective, the one ES5 builtin abstraction that opened a global communications channel through .prototype was Date, since Date.prototype was a Date instance, and Date instances have mutable state which remains observably mutable after Object.freeze. With the addition of RegExp.prototype.compile, RegExp joins the set of problematic ES5 abstractions.
Neither of these argue against making Array.prototype specifically be an Array instance. I prefer that it not be on esthetic grounds only, and am willing to switch given evidence. I concede that Kyle's book is sufficient evidence.
On Feb 19, 2015, at 1:13 PM, Mark S. Miller wrote:
On Thu, Feb 19, 2015 at 12:56 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Feb 19, 2015, at 12:09 PM, Andrea Giammarchi wrote:
if we'd like to have Array.empty, Function.empty, String.empty and friends, what's wrong with having these as we always had already: as prototypes?
Just to keep things focused. *.empty is not something that is on the table for ES6. It would have to go into the ES7 track.
Today's issue, is
do we need to revert Array.prototype to being an Array exotic object (I think we do)
do we need to revert RegExp.prototype to being a RegExp instance.(with initialize RegExp internal slots) (maybe not, and alternatively I have very minor tweaks to the exec and test methods that will maintain the most likely such legacy RegExp.prototype uses without needing those internal slots)
RegExp.prototype.compile people.mozilla.org/~jorendorff/es6-draft.html#sec-regexp.prototype.compile is a visible side effect that survives Object.freeze. It is only not a global communications channel because there is no longer a primordial RegExp instance. So we cannot consider making RegExp.prototype a RegExp unless Object.freeze must disable RegExp.prototype.compile on RegExp instances.
Good. I alwasys remember Date.prototype but tend to forget about compile.
I think we can keep RegExp as a plain vanilla object (RegExp instances are ordinary, but not plain vanilla) and still have good enough legacy compat for RegExp "misuse" via my exec and test tweaks. (they basically just test if they are being called with %RegExpProtoype% as their this object. Doesn't catch cross-realm, but probably still good enough)
Just curious… for RegExp, Date, String and the others that are changing to plain objects… does that mean Object.prototype.toString.call( .. )
will return "[object Object]" on them?
Sorry, I've kinda gotten lost on what the default @@toStringTag behavior is going to be here.
Le 20 févr. 2015 à 02:11, Kyle Simpson <getify at gmail.com> a écrit :
Just curious… for RegExp, Date, String and the others that are changing to plain objects… does that mean
Object.prototype.toString.call( .. )
will return "[object Object]" on them?Sorry, I've kinda gotten lost on what the default @@toStringTag behavior is going to be here.
The definitive algorithm is here: people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring
For pre-ES6 builtin constructors and prototypes, there is no pre-installed @@toStringTag property, but there are checks for internal structure determining a legacy builtin tag. For example, since RegExp.prototype
is a plain vanilla object, its builtin tag is "Object".
Has any engine tried implementing the ES6 changes from ur-instances to plain objects?
We are kidding ourselves about this change being web compatible if the answer is "no". I would bet real money that something will break. Who wants to wager?
To me I would like it to be reverted for back-compatiblity.
Is Array.prototype an exotic Array Instance? Or is it still a standard exotic object? This is somewhat relevant to V8 bug 3890 code.google.com/p/v8/issues/detail?id=3890, which is targeted at
implementing that change.
Array.prototype has reverted to being an empty initially empty exotic array object. Function.prototype remains a no-op function object.
All the other X.prototypes remain plain vanilla non-exotic objects.
On Sun, Feb 22, 2015 at 3:12 PM, Mark S. Miller <erights at google.com> wrote:
Array.prototype has reverted to being an empty initially empty exotic array object. Function.prototype remains a no-op function object.
Empty in terms of the indexed properties, and with a .length of 0. Array.prototype does of course have all the normal methods it always had, to be inherited by array instances.
No one has taken my bet :-P.
Since gambling with real money is technically still illegal, I have responded to Brendan privately.
On Feb 11, 2015, at 8:30 AM, Allen Wirfs-Brock wrote:
So:
and
This looks like the sort of evidence we asked for.
None of the fixes I have thought of are as simple as simply saying that Array.prototype is an Array exotic object.
So unless, I hear an uproar in the next few hours, I'm going to change the ES6 draft to say that.