Array Generics and null

# John Resig (18 years ago)

Hello all -

I wanted to bring up one point for discussion. Based upon Mozilla's implementation of Array generics (and what will, presumably, be standardized here) you can see that typically doing:

Array.forEach(null, function(){alert(arguments);});

produces no alerts. However, performing it in a page that has an iframe will cause it to loop over all the iframes in the page, as in this test case:

ejohn.org/files/bugs/foreach

The issue here is that Array.forEach(null, ...) maps to Array.prototype.forEach.call(null, ...) - and doing a .call() on a function produces the global object (which, in a browser like Firefox, is equivalent to window.frames - looping over the iframes on the page).

Obviously this issue extends beyond browsers, or this specific situation, since this could occur in any situation where the global object has a length property.

It seems like there could be a couple solutions:

  • Make .call(null) not map to .call(global). I know that the Caja guys have argued this in the past - where does this stand?
  • Make .forEach(null) do nothing (it's not an object, therefore there's nothing to loop over).
  • Make .forEach(null) throw an exception.

For reference here's the Mozilla bug on the matter: bugzilla.mozilla.org/show_bug.cgi?id=424411

# Garrett Smith (18 years ago)

On Sat, Mar 22, 2008 at 9:01 AM, John Resig <jresig at mozilla.com> wrote:

Hello all -

It seems like there could be a couple solutions:

Objects that are not capable of running in an Array-generic should not be attempted to run.

Would it make sense to use - like - for all the Array generics? Is there an Iterable interface?

Array generic methods will be safer if they check their args and throw an error - InvalidArgumentError, TypeError, UnlikeError - (whatever).

Invalid: (this will crash Firefox with endless loop):- Array.forEach( { length : -1, "0": 12 }, iter );

function iter(a) { console.log( a ); }

Garrett

# Dean Edwards (17 years ago)

John Resig wrote:

The issue here is that Array.forEach(null, ...) maps to Array.prototype.forEach.call(null, ...) - and doing a .call() on a function produces the global object (which, in a browser like Firefox, is equivalent to window.frames - looping over the iframes on the page).

Obviously this issue extends beyond browsers, or this specific situation, since this could occur in any situation where the global object has a length property.

I'll quote from the base2 mailing list:

Dean Edwards wrote:

I think that the problem is that Array generics were added later.

From bugzilla:

Array.generic(t, ...) is intended to be equivalent to Array.prototype.generic.call(t, ...).

If generics were considered when first designing the language then the above statement would have been the other way round.

e.g.

Array.prototype.forEach = function(block, context) { Array.forEach(this, block, context); }

That's how the ES4 defined built-in works:

prototype function forEach(eacher, thisObj=null) { Array.forEach(this, eacher, thisObj); }

# Mike Shaver (17 years ago)

On Sat, Mar 22, 2008 at 4:32 PM, Dean Edwards <dean at edwards.name> wrote:

Dean Edwards wrote:

I think that the problem is that Array generics were added later.

From bugzilla:

Array.generic(t, ...) is intended to be equivalent to Array.prototype.generic.call(t, ...).

If generics were considered when first designing the language then the above statement would have been the other way round.

e.g.

Array.prototype.forEach = function(block, context) { Array.forEach(this, block, context); }

I don't see how that helps, unless you expect A.p.g.c(null, f) to differ from A.g(null, f) -- the former will need to make a |this| from null, giving the window object in browser embeddings. I don't believe that they should so differ -- do you?

Mike

# Dean Edwards (17 years ago)

Mike Shaver wrote:

Dean Edwards wrote:

Array.prototype.forEach = function(block, context) { Array.forEach(this, block, context); }

I don't see how that helps, unless you expect A.p.g.c(null, f) to differ from A.g(null, f) -- the former will need to make a |this| from null, giving the window object in browser embeddings. I don't believe that they should so differ -- do you?

I'd prefer Array.forEach(null) to do nothing, just like for (var i in null) does nothing. I'm prepared to be convinced otherwise. :-)

# Garrett Smith (17 years ago)

I suspected that. It is the problem with Google Mail, which exhibited a bug in Firefox.

Use case: 0) hit reply

  1. click 'send'
  2. hit 'stop'
  3. click 'reply to all' and watch the To: field change.
  4. watch Gmail send a mail instantly. Gmail displays headers in the 'show details' for all recipients (the same ones that appeared in the To field).

Gmail does eventually update the incorrect headers; that last mail now says it went only to 'Dean Edwards'.

The fix for this would require subscription-based Async's and locking. Just another distraction from my message.

Garrett

# Garrett Smith (17 years ago)

On Sun, Mar 23, 2008 at 12:45 AM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:

I suspected that. It is the problem with Google Mail, which exhibited a bug in Firefox.

Use case: 0) hit reply

  1. click 'send'
  2. hit 'stop'
  3. click 'reply to all' and watch the To: field change.

3.5): click 'send'

  1. watch Gmail send a mail instantly. Gmail displays headers in the 'show details' for all recipients (the same ones that appeared in the To field).

Garrett

# Mike Shaver (17 years ago)

On Sun, Mar 23, 2008 at 3:45 AM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:

I suspected that. It is the problem with Google Mail, which exhibited a bug in Firefox.

Sorry, do you mean that this is caused by Array.generic behaviour? I'm lost as to what bug you're talking about (bug number?).

Mike

# Garrett Smith (17 years ago)

On Sun, Mar 23, 2008 at 11:53 AM, Mike Shaver <mike.shaver at gmail.com> wrote:

On Sun, Mar 23, 2008 at 3:45 AM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:

I suspected that. It is the problem with Google Mail, which exhibited a bug in Firefox.

Sorry, do you mean that this is caused by Array.generic behaviour? I'm lost as to what bug you're talking about (bug number?).

No, that's not what I meant at all.

Dean asked me: "Did you mean to reply to list?" (or something like)

So I replied: "Yes, Dean I meant to reply to list, but I had a problem with Gmail" Then I went on to explain the bug of why reply to list failed. The problem I had was trying to reply to the list and GMail had a bug in my particular use-case. Nothing to do with Generics.

My original message had to do with cases of dealing with handling a case of Array generics with a non-Array-like argument.

Here it is:

Garrett Smith wrote:

Is there typed null?

If so, then typed null could typecheck for Array-like-ness.

Anything that is not Array-like or invalid could throw an error. Such values would include: null undefined any non-dynamic object that does not have a numeric length property

Ideally this would happen at compile time.

Garrett

Garrett

# Mike Shaver (17 years ago)

On Sun, Mar 23, 2008 at 2:44 AM, Dean Edwards <dean at edwards.name> wrote:

I'd prefer Array.forEach(null) to do nothing, just like for (var i in null) does nothing. I'm prepared to be convinced otherwise. :-)

forEach isn't like enumeration, though, it's like the more common Array pattern of

for (var i = 0; i < a.length; i++) { doIt(a[i]); }

And if a is |window|, you get exactly the same effects, no? If a is null, you get an exception, which I think is probably not what we want. FWIW, ES3 specifies that |for (var i in null)| throw a TypeError (see 12.6.4 step 3, and 9.9), but we and AFAIK all other browsers intentionally diverge; we made that change relatively recently, I think in FF2's timeframe. I expect that ES4 will specify the non-throwing behaviour, because an exception is just too harsh a punishment for trying to iterate over null or undefined.

Mike

# Dean Edwards (17 years ago)

Mike Shaver wrote:

On Sun, Mar 23, 2008 at 2:44 AM, Dean Edwards <dean at edwards.name> wrote:

I'd prefer Array.forEach(null) to do nothing, just like for (var i in null) does nothing. I'm prepared to be convinced otherwise. :-)

forEach isn't like enumeration, though, it's like the more common Array pattern of

for (var i = 0; i < a.length; i++) { doIt(a[i]); }

And if a is |window|, you get exactly the same effects, no? If a is null, you get an exception, which I think is probably not what we want. FWIW, ES3 specifies that |for (var i in null)| throw a TypeError (see 12.6.4 step 3, and 9.9), but we and AFAIK all other browsers intentionally diverge; we made that change relatively recently, I think in FF2's timeframe. I expect that ES4 will specify the non-throwing behaviour, because an exception is just too harsh a punishment for trying to iterate over null or undefined.

Like I said previously, Array.generic delegating to Array.prototype.generic.call is an artefact of the way JS grew as a language. Generics were added later. If generics were originally specified then Array.prototype.generic would delegate to Array.generic. From the ES4 reference implementation Array.es:

prototype function forEach(eacher, thisObj=null) { Array.forEach(this, eacher, thisObj); }

It seems we have three choices for Array.forEach(null)

  1. Do nothing
  2. Throw an exception
  3. Use the current object and iterate that

I prefer the first one, throwing an exception is acceptable too but the last one is counter-intuitive IMO.

# Mark S. Miller (17 years ago)

On Mon, Mar 24, 2008 at 10:31 AM, Dean Edwards <dean at edwards.name> wrote:

It seems we have three choices for Array.forEach(null)

  1. Do nothing
  2. Throw an exception
  3. Use the current object and iterate that

By "current object" do you mean the global object?

I prefer the first one, throwing an exception is acceptable too but the last one is counter-intuitive IMO.

I would find either of the first two acceptable. I prefer #2.

# Mike Shaver (17 years ago)

On Sat, Mar 22, 2008 at 1:42 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:

Array generic methods will be safer if they check their args and throw an error - InvalidArgumentError, TypeError, UnlikeError - (whatever).

Invalid: (this will crash Firefox with endless loop):- Array.forEach( { length : -1, "0": 12 }, iter );

Well, really a loop to 0xffffffff, just as the ES3 generics will do. Many ES3 Array.prototype methods explicitly specified to be generic (see the NOTEs for join, concat, pop, push, reverse, shift, slice, unshift, sort, splice in 15.4.4.*) and I don't see why forEach would be different; that generic nature is very useful when operating on DOM NodeLists, ES3 argument objects, and so forth.

o = {length:-1, reverse:[].reverse, forEach:[].forEach}; o.reverse() o.forEach(function () { }) Array.forEach(o, function() { })

In my FF3 nightly all of those are handled the same way: slow script dialog after a few seconds. Fx2 doesn't have the same mechanism bounding "internal" loops, but that's just a bug, and eliminating script-DoS on the web is pretty Sisyphean.

Mike

# Dean Edwards (17 years ago)

Mark S. Miller wrote:

On Mon, Mar 24, 2008 at 10:31 AM, Dean Edwards <dean at edwards.name> wrote:

It seems we have three choices for Array.forEach(null)

  1. Do nothing
  2. Throw an exception
  3. Use the current object and iterate that

By "current object" do you mean the global object?

I meant the object that was used by function.call(null). Most of the time that is the global object. I'm not sure how "this propagation" affects that.

# Garrett Smith (17 years ago)

---------- Forwarded message ---------- From: Garrett Smith <dhtmlkitchen at gmail.com>

Date: Mon, Mar 24, 2008 at 11:27 AM Subject: Re: Array Generics and null To: Dean Edwards <dean at edwards.name>

On Mon, Mar 24, 2008 at 10:31 AM, Dean Edwards <dean at edwards.name> wrote:

Mike Shaver wrote:

On Sun, Mar 23, 2008 at 2:44 AM, Dean Edwards <dean at edwards.name> wrote:

It seems we have three choices for Array.forEach(null)

  1. Do nothing
  2. Throw an exception
  3. Use the current object and iterate that

I prefer the first one, throwing an exception is acceptable too but the last one is counter-intuitive IMO.

(2) Throw an exception.

When? Checked or unchecked?

-dean


Es4-discuss mailing list Es4-discuss at mozilla.org, mail.mozilla.org/listinfo/es4-discuss

# Brendan Eich (17 years ago)

On Mar 22, 2008, at 10:18 PM, Mike Shaver wrote:

On Sat, Mar 22, 2008 at 4:32 PM, Dean Edwards <dean at edwards.name>
wrote:

Dean Edwards wrote:

I think that the problem is that Array generics were added later.

From bugzilla:

Array.generic(t, ...) is intended to be equivalent to Array.prototype.generic.call(t, ...).

If generics were considered when first designing the language
then the above statement would have been the other way round.

e.g.

Array.prototype.forEach = function(block, context) { Array.forEach(this, block, context); }

Just FYI, I don't think the argument order matters when discussing
the substitution of "the global object" for |this|. So to separate
those two, let's try to agree on the order of arguments in the
generic case matching, as if by arguments.shift(), the order for the
prototype counterparts. The order is a "sailed ship", IMHO.

I don't see how that helps, unless you expect A.p.g.c(null, f) to differ from A.g(null, f) -- the former will need to make a |this| from null, giving the window object in browser embeddings. I don't believe that they should so differ -- do you?

There's an open issue in ES-future designs where we are trying to
agree not to replace null with the global object in the ES1-3
internal specification language, or anything equivalent, where
calling a function by its name, or by an expression without an
explicit reference base, results in |this| defaulting to null, for
which the spec and implementations then substitute "the global
object" before any scripted code can detect the null.

We would rather:

function topLevel(a,b,c) { print(this, a, b, c); } topLevel(1,2,3);

print "undefined 1 2 3" than "[object global] 1 2 3".

This is incompatible, so it would happen only in some opt-in version,
or opt-in version + strict pragma enabled.

The issues are whether undefined or null should be the default
(undefined has won, AFAICT), and whether this is a migration tax
imposed by the new version when programmers opt in, or new version
plus 'use strict'.

Anyway, if we do this, we could change Function apply/call/bind and
the Array/String generic prototypes and their static counterparts to
eliminate this bad old null->global behavior. The library code could

stay as is, true -- the opt-in version +/- 'use strict' could affect
only top-level functions called without an explicit |this|. But it
seems better to make everything consistent in the "no global for null
substitution" sense. Comments?

# Lars Hansen (17 years ago)

-----Original Message----- From: es4-discuss-bounces at mozilla.org [mailto:es4-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich Sent: 29. mars 2008 11:05 To: Mike Shaver; es4-discuss Cc: Dean Edwards Subject: Re: Array Generics and null

We would rather:

function topLevel(a,b,c) { print(this, a, b, c); } topLevel(1,2,3);

print "undefined 1 2 3" than "[object global] 1 2 3".

This is incompatible, so it would happen only in some opt-in version, or opt-in version + strict pragma enabled.

The issues are whether undefined or null should be the default (undefined has won, AFAICT),

The third option on the table is that the reference to 'this' inside the body of topLevel simply throws an error. This has both less and more utility: the function can't discover if it was called as a function or as a method; but functions that simply assume they were called as methods will fail earlier.

# Mark S. Miller (17 years ago)

On Sun, Mar 30, 2008 at 6:54 AM, Lars Hansen <lhansen at adobe.com> wrote:

The third option on the table is that the reference to 'this' inside the body of topLevel simply throws an error. This has both less and more utility: the function can't discover if it was called as a function or as a method; but functions that simply assume they were called as methods will fail earlier.

Since the example function here is called "topLevel", I'd like to remind everyone that the constraint we're talking about would apply to all functions -- the lexical "this propagation" rule is dead.

Regarding the choices,

  • "undefined" is more uniform and easier to explain.
  • Throwing an exception is safer.
  • Even safer would be that a function that mentions "this" is considered a method, and an attempt to call it as a function throws without ever entering the function.

Caja currently does the last. I'm happy with any of these choices.

# Erik Arvidsson (17 years ago)

I agree with Lars (and Mark) on this. It would be best if access to 'this' would throw. Throwing in the actual call to the function seems a bit harsh since the statement that refers to 'this' might never be reached. Making the access throw would allow people to at least catch the error and fall back on some other behavior.

My vote goes to throwing an exception when a non provided this is accessed.