Array methods applied to strings

# James Graham (16 years ago)

It seems that ES5 throws a TypeError for constructs like

Array.prototype.pop.call("abc")

whereas current implementations (and, I think ES3, but I didn't check that closely) will return "c".

Is this change intentional? There seems to be a note in the compatibility appendix that is about a similar, but not quite identical issue (or maybe it is identical and I have misunderstood). Are there grounds to be confident that the old behavior is not needed for web compatibility?

Apologies if I have overlooked something or misunderstood something.

# Erik Arvidsson (16 years ago)

The old behavior is indeed needed for web compatibility. All Array methods are supposed to be generic and I know js libraries use them heavily on NodeLists and ther non Array objects.

ES5, 15.4.4.6, has no type check by itself but it is calling [[ThrowingPut]] which will throw when called on a String. It seems like step 5.d needs to be changed to a [[Put]] instead?

I assume we have to update all the other Array methods as well?

# Allen Wirfs-Brock (16 years ago)

Agreed, this is a problem.

Which compatibility appendix item did you think might apply? None of them were intended to cover this situation.

# Allen Wirfs-Brock (16 years ago)

The motivation for replacing [[Put]] and [Delete]] calls with throwing versions in the Array prototype functions was to provide proactive notification when these algorithms were applied to arrays (or other objects) where the existence of non-writable properties might result in violations of the normally expected post conditions of the algorithms. Note that this had not been a problem for ES3 because ES3 provided no programmatic way to set the readonly attribute and the only built-in readonly property that might impact these algorithms was the length property of strings.

Arguably this situations comes up in ES3 only when these functions are applied to String instances but note that manifesting array index properties on String objects is not specified by ES3 so strictly speaking this is not an ES3 compatibility issues but a compatibility issues relative to implementations that have extended ES3 with such String properties. Also note that IE (including IE8, although it has some SunSpider compatibility hacks in this area) has never supported array-like indexing of String objects. Hence, it is debatable whether this is truly a "web compatibility" issue, as calling of these functions on Strings does not work identically across all widely used browsers.

When we talked about this in the ES3.1 working group we didn't specifically think about strings. Instead we were thinking that reified properties attributes creates a hazard where these functions might be inadvertently be applied to Arrays or (or other objects) with non-writable properties and that in such situations it would be better to throw an exception (making the programmer aware of a likely bug) rather than allowing the functions to silently complete but leaving objects in a state that violates the expected post conditions of the functions.

In reexamining this issue now, I started out thinking we had made a mistake but after reconstructing the login chain that lead to this decision I find myself "on the fence" and perhaps leaning a bit towards keeping the exceptions. The choice is between avoiding creating a new category of silent bugs or maintaining compatibility with a not universally implemented extension to ES3.

How often are these functions really applied to strings? The use case for applying pop to strings seemed reasonably compelling but as I look at most of the other array functions I don't see that they make much sense in the presence of non-writable elements.

The other alternative, would be to special case the algorithms for these functions such that they do not throw when the this object is a string object but throw in all other scenarios. This is actually a pretty trivial change to make to the specification that would maintain compatibility with existing implementations that support string indexing but still throw in all other cases (including Arrays) where non-writable properties would result in violations of the expected post conditions.

Thoughts??

# Erik Arvidsson (16 years ago)

On Mon, May 11, 2009 at 11:47, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com> wrote:

The motivation for replacing [[Put]] and [Delete]] calls with throwing versions in the Array prototype functions was to provide proactive notification when these algorithms were applied to arrays (or other objects) where the existence of non-writable properties might result in violations of the normally expected post conditions of the algorithms. Note that this had not been a problem for ES3 because ES3 provided no programmatic way to set the readonly attribute and the only built-in readonly property that might impact these algorithms was the length property of strings.

Arguably this situations comes up in ES3 only when these functions are applied to String instances but note that manifesting array index properties on String objects is not specified by ES3 so strictly speaking this is not an ES3 compatibility issues but a compatibility issues relative to implementations that have extended ES3 with such String properties. Also note that IE (including IE8, although it has some SunSpider compatibility hacks in this area) has never supported array-like indexing of String objects.  Hence, it is debatable whether this is truly a "web compatibility" issue, as calling of these functions on Strings does not work identically across all widely used browsers.

When we talked about this in the ES3.1 working group we didn't specifically think about strings.  Instead we were thinking that reified properties attributes creates a hazard where these functions might be inadvertently be applied to Arrays or (or other objects) with non-writable properties and that in such situations it would be better to throw an exception (making the programmer aware of a likely bug) rather than allowing the functions to silently complete but leaving objects in a state that violates the expected post conditions of the functions.

In reexamining this issue now, I started out thinking we had made a mistake but after reconstructing the login chain that lead to this decision I find myself "on the fence" and perhaps leaning a bit towards keeping the exceptions.  The choice is between avoiding creating a new category of silent bugs or maintaining compatibility with a not universally implemented extension to ES3.

How often are these functions really applied to strings? The use case for applying pop to strings seemed reasonably compelling but as I look at most of the other array functions I don't see that they make much sense in the presence of non-writable elements.

The other alternative, would be to special case the algorithms for these functions such that they do not throw when the this object is a string object but throw in all other scenarios.  This is actually a pretty trivial change to make to the specification that would maintain compatibility with existing implementations that support string indexing  but still throw in all other cases (including Arrays) where non-writable properties would result in violations of the expected post conditions.

I think the right solution would be to use a non throwing [[Put]] for the Array methods for backwards compatibility. NodeList and Arguments are the common array like structures that people use the array generics for and I can see other things like ImageData getting more common in the near term future. Special casing String will not help the common case.

# Mark S. Miller (16 years ago)

On Mon, May 11, 2009 at 12:29 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

I think the right solution would be to use a non throwing [[Put]] for the Array methods for backwards compatibility.

On IE: [].pop.call('abc') === undefined

Since IE mutating array generics don't work on strings, cross-browser web content[1] does not depend on these working on strings.

NodeList and Arguments are the common array like structures that people use the array generics for and I can see other things like ImageData getting more common in the near term future. Special casing String will not help the common case.

Having mutating operations fail silently when applied to frozen arrays is unacceptably integrity hostile. I'd rather withdraw the addition of numeric indexing to strings than introduce a new silent failure hazard.

[1] To the extent that we also wish to be compatible with browser-specific content, since most browser instances are IE, most browser-specific content is specific to IE (simple economics). That's why the 3 out of 4 browser rule at es3.1:es3.1_goals states:

Browser implementation unification: Consider adopting features that are already implemented in 3 of the 4 browser brands, or that are deployed in 3 out 4 user computers and reduce cross browser incompatibilities.

This clause gives us a choice. We have consistently used that choice to pick the saner alternative. Generally that has meant the "3 of 4 browser brands" rule. On the application of mutating array generics to strings, the saner choice may be "3 of 4 browser instances".

# Mark S. Miller (16 years ago)

On Mon, May 11, 2009 at 8:40 AM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

The old behavior is indeed needed for web compatibility. All Array methods are supposed to be generic and I know js libraries use them heavily on NodeLists and ther non Array objects.

ES5, 15.4.4.6, has no type check by itself but it is calling [[ThrowingPut]] which will throw when called on a String. It seems like step 5.d needs to be changed to a [[Put]] instead?

Informally, the spec language has been using the [[Put]] vs [[ThrowingPut]] as an internal spec consistency check: We should only cause [[Put]] when a failure that would have been reported by [[ThrowingPut]](..., true) can't happen. Anywhere we do decide[1] to suppress the reporting of such a failure, we should do so by explicitly calling [[ThrowingPut]](..., false) instead.

We should add a note to this effect where we define [[Put]].

[1] This point is independent of whether we decide to suppress reporting this specific failure.

# Allen Wirfs-Brock (16 years ago)

-----Original Message----- From: Erik Arvidsson [mailto:erik.arvidsson at gmail.com] ... I think the right solution would be to use a non throwing [[Put]] for the Array methods for backwards compatibility. NodeList and Arguments are the common array like structures that people use the array generics for and I can see other things like ImageData getting more common in the near term future. Special casing String will not help the common case.

Arguments objects in ES5 have Array.prototype as their [[Prototype]] so they now actually inherit all the array methods. The array index properties and the length property of array objects are writable so the potential exceptions in the ES5 algorithms have no impact unless somebody explicitly changes the attributes of an argument object's properties.

Regarding NodeList, if its elements are writable then there is no issue. If its elements are readonly then I have the same question that I had for Strings what are the actual use cases for applying the other mutable array functions other than pop to it? In addition, NodeList is a host object so its actually behavior for [[Put]] and [[ThrowingPut]] are defined anyway. In particular I note on IE evaluating: Array.prototype.pop.call(elem) in a shell yields when elem is a NodeList (well, at least the result of a call to getElementsByTagName) yields: TypeError: JScript object expected

# Brendan Eich (16 years ago)

On May 11, 2009, at 1:26 PM, Mark S. Miller wrote:

[1] To the extent that we also wish to be compatible with browser-specific content, since most browser instances are IE,

Note that right now, as IE8 is pushed via Windows Update and takes
over IE7's share, Firefox 3 / Gecko 1.9 moves to the front of the
market share by "user-agent rendering engine" race.

Of course, IE8 does not implement string indexing, so
Array.prototype.pop.call("abc") returns undefined in IE8. But it does
not throw.

My position is that if you want higher integrity, use strict mode
(which means getting a modern browser). Otherwise, compatibility
trumps marginal integrity through incompatible fail-stop changes.

# Brendan Eich (16 years ago)

On May 11, 2009, at 1:54 PM, Brendan Eich wrote:

On May 11, 2009, at 1:26 PM, Mark S. Miller wrote:

[1] To the extent that we also wish to be compatible with browser-specific content, since most browser instances are IE,

Note that right now, as IE8 is pushed via Windows Update and takes
over IE7's share, Firefox 3 / Gecko 1.9 moves to the front of the
market share by "user-agent rendering engine" race.

Of course, IE8 does not implement string indexing, so
Array.prototype.pop.call("abc") returns undefined in IE8. But it
does not throw.

Same for IE7 -- undefined result, no throw.

# James Graham (16 years ago)

Mark S. Miller wrote:

On Mon, May 11, 2009 at 12:29 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:

I think the right solution would be to use a non throwing [[Put]] for the Array methods for backwards compatibility.

On IE: [].pop.call('abc') === undefined

Since IE mutating array generics don't work on strings, cross-browser web content[1] does not depend on these working on strings.

I will try and think about more of the issues here when I have a little more time, but in the meantime I think it is worth noting that this argument assumes that the same code is being executed by all web browsers. Since it is common practice to differentiate code path based on UA, it is quite possible that changing this behavior would have serious compatibility implications for non-IE browsers despite the lack of IE support.