Last call: Function
2008/3/20 Lars Hansen <lhansen at adobe.com>:
Last call for comments on the Function spec.
-
There's a conflict between defining call, apply, and bind as static methods on the Function constructor vs defining them as instance methods on functions (or as members of Function.prototype). The problem is that the Function constructor is itself a function. When we see
Function.call(...)
is this invoking the static "call" method of the function constructor, or is it invoking the instance "call" method common to all functions?
- At the last ES-wide face-to-face, I thought we'd all agreed that the value of the thisObj argument to call/apply/bind, no matter what this value is, would be the value that gets bound to "this" in the called function. If I understand this proposed spec, f.call(null, ...) would invoke f with its "this" bound to the global object. That behavior makes JavaScript harder to secure, since this implicit access to the global object enable privilege escalation attacks. Such implicit access to the global object also prevents garbage collection opportunities: In a frame in which the global object were otherwise inaccessible, it would still need to be retained by any function that might be called so as to manufacture access to the global object.
-----Original Message----- From: Mark S. Miller [mailto:erights at google.com] Sent: 20. mars 2008 17:51 To: Lars Hansen Cc: es4-discuss at mozilla.org Subject: Re: Last call: Function
2008/3/20 Lars Hansen <lhansen at adobe.com>:
Last call for comments on the Function spec.
There's a conflict between defining call, apply, and bind as static methods on the Function constructor vs defining them as instance methods on functions (or as members of Function.prototype). The problem is that the Function constructor is itself a function. When we see
Function.call(...)
is this invoking the static "call" method of the function constructor, or is it invoking the instance "call" method common to all functions?
That depends. There are three things called 'call' here: the intrinsic method on Function instances; the property (in the empty namespace) on the Function prototype object; and the property (in the empty namespace) on the Function object. The latter shadows the prototype property if accessed via the Function object. So if I say Function.call I either get the intrinsic one if the intrinsic namespace is open, or the static one otherwise. There is IMO not really any room for ambiguity because the intrinsic namespace is not open by default, and if it is opened then it takes precedence by normal rules. The two functions have different signatures, but that's OK.
I agree this may not be the best design. At the same time it does not seem to be actually wrong. On the third hand, the reference implementation does not behave as I thought it ought to, so my reasoning could be faulty. I will log the question as a ticket to be investigated.
- At the last ES-wide face-to-face, I thought we'd all agreed that the value of the thisObj argument to call/apply/bind, no matter what this value is, would be the value that gets bound to "this" in the called function.
I think you have found a spec bug. The code in the spec assumes a single global object and is correct for that, but it's not the right way. If thisObj is null then it should be passed as null, and the language implementation will substitute the callee's global object for that value, it should not be done in apply as it is now. Thanks.
If I understand this proposed spec, f.call(null, ...) would invoke f with its "this" bound to the global object.
f's global object, yes.
That behavior makes JavaScript harder to secure, since this implicit access to the global object enable privilege escalation attacks.
Access to the global object is unfortunately pervasive in the language, and we're not making any effort to crack down on that.
Such implicit access to the global object also prevents garbage collection opportunities: In a frame in which the global object were otherwise inaccessible, it would still need to be retained by any function that might be called so as to manufacture access to the global object.
The global object is in principle part of every function's scope chain, and in a multi-global environment (like a browser) it's hard to get away from that. Consider that even code generated for an object initializer expression needs access to the global object to find the 'Object' constructor (in ES3 anyway), and similarly the expression x.y may need to convert x to a String object if it is a primitive string (in ES3 for any y, and in ES4 for at least some y). It may not need the global object for that, but it needs access to a primitive global-like environment that has a lot of things connected to it.
Given enough information it's probably possible to optimize the global object away. I don't know about any implementations that do that (but I haven't looked at the guts of e.g. IE and Safari).
Thanks for the very useful comments.
On Mar 20, 2008, at 5:50 PM, Mark S. Miller wrote:
- At the last ES-wide face-to-face, I thought we'd all agreed that the value of the thisObj argument to call/apply/bind, no matter what this value is, would be the value that gets bound to "this" in the called function. If I understand this proposed spec, f.call(null, ...) would invoke f with its "this" bound to the global object.
I remember agreeing that the ES3 special case for null and undefined
passed to Function.prototype.{apply, call} as the first actual
parameter, where the null or undefined was replaced by "the global
object", was confusing and error-prone. But it was not agreed that
ES4 would incompatibly change this, although at least you and I
entertained the idea.
I don't think we can remove this special case from engines handling
unversioned or known-ES3/JS1.x-version content. Making yet another
versioned change to the runtime semantics is not attractive at this
point, compared to bigger fish to fry elsewhere that are entirely new
and better, not bound by compatibility.
That behavior makes JavaScript harder to secure, since this implicit access to the global object enable privilege escalation attacks.
This is true, but it's not peculiar to apply and call as Lars noted
in reply. The ES3 |this| binding for inner functions called without
an explicit |this| parameter is of course another case, and as far as
I know the progenitor of the apply/call special case. Ticket 276, you
well know (MILLEM-GOO :-P), deals with the |this| mess:
bugs.ecmascript.org/ticket/276
but it needs review and comments from others, including me (I'll get
to it tomorrow).
Such implicit access to the global object also prevents garbage collection opportunities: In a frame in which the global object were otherwise inaccessible, it would still need to be retained by any function that might be called so as to manufacture access to the global object.
Per ECMA-262 Edition 3 10.2.3, function objects already must capture
their scope chains including the global object terminating the chain.
In browsers, each window (tab), frame in frameset, and iframe has a
global object. Cross-frame calls uphold static scope by following
their [[Scope]] internal property's value to find the right globals,
the ones lexically closed when the function and its surrounding code
were loaded via a <script> tag.
Thanks for the reminder about all of "this". ;-)
On Mar 20, 2008, at 9:46 PM, Brendan Eich wrote:
Per ECMA-262 Edition 3 10.2.3, function objects already must capture their scope chains including the global object terminating the chain. In browsers, each window (tab), frame in frameset, and iframe has a global object. Cross-frame calls uphold static scope by following their [[Scope]] internal property's value to find the right globals, the ones lexically closed when the function and its surrounding code were loaded via a <script> tag.
I should have added that optimizing implementations that close
bindings instead of activation objects still have a problem deciding
what bindings are global if the function uses outer names that are
not DontDelete (var or equivalent) bindings in outer activations. If
the function uses no outer names, then it need not entrain outer
scopes at all, and you're right that the only requirement for
[[Scope]], not explicit in ES3 with its lame talk of only one "global
object", comes from apply/call as specified.
It seems likely that "new Function" somehow needs to accept a version parameter in the manner of eval, for backward compatibility.
Last call for comments on the Function spec.