Array.prototype change (Was: @@toStringTag spoofing for null and undefined)

# Allen Wirfs-Brock (9 years ago)

On Feb 11, 2015, at 8:30 AM, Allen Wirfs-Brock wrote:

TC39 members were aware that changing to using ordinary objects as the prototypes of the legacy built-ins was a breaking change (for example, it changes the prototypes {}.toString value). What we agreed to do was to make that change but to undo it for individual legacy built-ins if we discovered that it would cause actual significant web breakage. We discovered that this was the case for Function.prototype and so it remains a function instances in the ES6 spec

What we need for the other legacy built-ins is real evidence that there is a problem, rather than speculation. Let us know if you are aware of a significant Web site or widely used framework that depends upon String.prototype being a String instance or any other similar dependency.

So:

@awbjs example:

a.reduce((c,v)=>{if (!~c.indexOf(v)) return c. concat(v)else return c}, Array.prototype)

Original Tweet: twitter.com/getify/status/568440928433602560

and

@awbjs @getify I'd say specially for .concat() is quite common pattern so instead of [].concat() few of us go Array.prototype.concat

Original Tweet: twitter.com/WebReflection/status/568443370089123842

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.

# Domenic Denicola (9 years ago)

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?

# Andrea Giammarchi (9 years ago)

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.

# Domenic Denicola (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

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?

# Mark S. Miller (9 years ago)

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 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

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.

# Allen Wirfs-Brock (9 years ago)

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.

# Allen Wirfs-Brock (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

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

# Domenic Denicola (9 years ago)

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

# Andrea Giammarchi (9 years ago)

Annex E ... I've never reached that part ...

# Kyle Simpson (9 years ago)

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.

# Jordan Harband (9 years ago)

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)

# Kyle Simpson (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

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 )

# Axel Rauschmayer (9 years ago)

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

# Mark Miller (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

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.

# Andrea Giammarchi (9 years ago)

P.S. yes, the semantic already frozen purpose of an empty property though, I agree that'd be cool

# Kyle Simpson (9 years ago)

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.

# Kyle Simpson (9 years ago)

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 ø.

# Allen Wirfs-Brock (9 years ago)

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

  1. do we need to revert Array.prototype to being an Array exotic object (I think we do)

  2. 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)

# Michał Wadas (9 years ago)

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.

# Mark S. Miller (9 years ago)

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

  1. do we need to revert Array.prototype to being an Array exotic object (I think we do)

  2. 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.

# Jordan Harband (9 years ago)

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.

# Mark S. Miller (9 years ago)
  1. 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.

  2. 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.

# Allen Wirfs-Brock (9 years ago)

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

  1. do we need to revert Array.prototype to being an Array exotic object (I think we do)

  2. 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)

# Kyle Simpson (9 years ago)

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.

# Claude Pache (9 years ago)

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".

# Brendan Eich (9 years ago)

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?

# Gary Guo (9 years ago)

To me I would like it to be reverted for back-compatiblity.

# Isiah Meadows (9 years ago)

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.

# Mark S. Miller (9 years ago)

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.

# Mark S. Miller (9 years ago)

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.

# Brendan Eich (9 years ago)

No one has taken my bet :-P.

# Mark S. Miller (9 years ago)

Since gambling with real money is technically still illegal, I have responded to Brendan privately.