Lexical scoping of 'function' in sloppy mode breaks legal ES5

# Andreas Rossberg (12 years ago)

While debugging a V8 issue I just realised another incompatibility with introducing lexical function declarations in sloppy mode that I think we haven't observed yet. Consider the following code:

function f() {
  var x = 0
  try {
    throw 1
  } catch (x) {
    eval("function g() { return x }")
  }
  return g()
}

This function is legal, and supposed to return 1 according to my reading of the ES5 spec. But lexically scoped function declarations should make it throw a ReferenceError instead.

Unlike the other legacy issues with sloppy-mode lexical function scoping that we have discussed before this one is actually covered by the current spec. Is such a breaking change an issue? V8 currently crashes on the above code, so this particular example probably is not a real-world problem. 8) However, there are simpler examples, e.g.:

function f() {
  if (true) { eval("function g() { return 1 }") }
  return g()
}

Do we know if this is something that occurs in practice?

# Allen Wirfs-Brock (12 years ago)

On Aug 19, 2013, at 8:38 AM, Andreas Rossberg wrote:

This function is legal, and supposed to return 1 according to my reading of the ES5 spec.

I don't think so. according to ES5 10.4.2 The VariableEnvironment of the eval'ed code should be set to the VariableEnvironment of the calling context. That variable environment is the outer cope of function f that has 0 as the value of its x binding. When g is instantiated that variable environment will be its [[Scope]] so "return x" should return 0.

But lexically scoped function declarations should make it throw a ReferenceError instead.

But, regardless of what g returns, I agree that the ES5 spec. says that the above code produces a binding for g in f's outer scope.

Unlike the other legacy issues with sloppy-mode lexical function scoping that we have discussed before this one is actually covered by the current spec.

It seems like this could be specified such that sloppy direct eval instantiates such functions using exactly the ES5 scoping rules. However, I suspect that is likely to cause even more confusion in the future.

Is such a breaking change an issue? V8 currently crashes on the above code, so this particular example probably is not a real-world problem. 8) However, there are simpler examples, e.g.:

function f() {
 if (true) { eval("function g() { return 1 }") }
 return g()
}

Do we know if this is something that occurs in practice?

Does V8 crash on the simpler example? Does it produce a ReferenceError (not conforming to ES5) on the call to g()? You're implementation may be the best test of what occurs in practice.

# Andreas Rossberg (12 years ago)

On 19 August 2013 19:02, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I don't think so. according to ES5 10.4.2 The VariableEnvironment of the eval'ed code should be set to the VariableEnvironment of the calling context. That variable environment is the outer cope of function f that has 0 as the value of its x binding. When g is instantiated that variable environment will be its [[Scope]] so "return x" should return 0.

You are right, although the discrepancy between FunctionExpression and FunctionDeclaration feels really odd -- I wonder how that came about. It also means that both Firefox and Safari handle the example incorrectly (though at least they don't crash :) ).

It seems like this could be specified such that sloppy direct eval instantiates such functions using exactly the ES5 scoping rules. However, I suspect that is likely to cause even more confusion in the future.

I agree, that sounds like a rather unattractive solution.

Does V8 crash on the simpler example? Does it produce a ReferenceError (not conforming to ES5) on the call to g()? You're implementation may be the best test of what occurs in practice.

V8 works fine on the simpler example, and it seems that FF and Safari do, too. So such code could be out in the wild.

# Till Schneidereit (12 years ago)

On Mon, Aug 19, 2013 at 7:20 PM, Andreas Rossberg <rossberg at google.com>wrote:

On 19 August 2013 19:02, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Does V8 crash on the simpler example? Does it produce a ReferenceError (not conforming to ES5) on the call to g()? You're implementation may be the best test of what occurs in practice.

V8 works fine on the simpler example, and it seems that FF and Safari do, too. So such code could be out in the wild.

SpiderMonkey and JSC accept both of the example scripts - and print 1 for both of them.

# André Bargull (12 years ago)

You are right, although the discrepancy between FunctionExpression and FunctionDeclaration feels really odd -- I wonder how that came about. It also means that both Firefox and Safari handle the example incorrectly (though at least they don't crash :) ).

This difference was introduced between 8. Dec. 2008 and 15. Dec. 2008, in the earlier drafts both, function declarations and function expressions, used the LexicalEnvironment of the running execution context for their [[Scope]]. And this is also what ES3 specified ("Pass in the scope chain of the running execution context as the Scope."). So maybe this is just another bug in the ES5 specification?

Source: es3.1:es3.1_proposal_working_draft

# Andreas Rossberg (12 years ago)

This difference was introduced between 8. Dec. 2008 and 15. Dec. 2008, in the earlier drafts both, function declarations and function expressions, used the LexicalEnvironment of the running execution context for their [[Scope]]. And this is also what ES3 specified ("Pass in the scope chain of the running execution context as the Scope."). So maybe this is just another bug in the ES5 specification?

I tend to agree that this particular detail should be considered a spec bug. As far as I can tell, examples like the above are the only way one can actually tell the difference in proper ES5, and it seems rather wrong there. In all other contexts allowing function declarations, Lexical- and VariableEnvironment are the same anyway. Perhaps this change was made in vague anticipation of local function declarations?

Either way, for ES6, this necessarily will change back again, since otherwise lexical scoping for function declarations would be completely broken. The current draft already does the equivalent (modulo some major refactoring).

# Brendan Eich (12 years ago)

Andreas Rossberg wrote:

I tend to agree that this particular detail should be considered a spec bug.

Yes, this is a spec bug. How did we miss it? Allen, do you have notes on the history?

# Allen Wirfs-Brock (12 years ago)

On Aug 20, 2013, at 11:53 AM, Brendan Eich wrote:

Andreas Rossberg wrote:

I tend to agree that this particular detail should be considered a spec bug.

Yes, this is a spec bug. How did we miss it? Allen, do you have notes on the history?

I was looking at the change history and ES5-discuss archives earlier today and couldn't find anything definitive on this specific point. This happen as part of the larger task of formalizing environment records, variable/function instantiations, and the correct lexical scoping of with statements and catch clauses. I suspect we just missed identifying this very special case as it is the only situation in non-extended ES where a FunctionDeclaration can actually be instantiated within an inner scope.

The good news is that the FF/Safari behavior is a better match to what we want for ES6 inner scope FunctionDeclarations.

# Brendan Eich (12 years ago)

Allen Wirfs-Brock wrote:

The good news is that the FF/Safari behavior is a better match to what we want for ES6 inner scope FunctionDeclarations.

:-D