archives

« Bugzilla Issues Index

#3113 — Add `Function.prototype.arguments` and `Function.prototype.caller`


http://javascript.spec.whatwg.org/#function.prototype.arguments
http://javascript.spec.whatwg.org/#function.prototype.caller

These are required for Web compatibility. Adding them to the ES6 spec (and properly defining their semantics) would improve interoperability, as at the moment all engines implement them slightly differently.

Some tests: http://mathias.html5.org/tests/javascript/function/


The whatwg spec is wrong. It describes the behavior of sloppy function own properties but not the behavior of the Function.prototype properties, as it claims.


properties with these names are explicitly poisoned for built-in functions and for ECMAScript functions defined in strict mode.


(In reply to Mark Miller from comment #1)
> The whatwg spec is wrong. It describes the behavior of sloppy function own
> properties but not the behavior of the Function.prototype properties, as it
> claims.

Right; apologies for my confusion. Forget about the WHATWG spec. Let’s continue discussing the `Function.prototype` properties.

(In reply to Allen Wirfs-Brock from comment #2)
> properties with these names are explicitly poisoned for built-in functions
> and for ECMAScript functions defined in strict mode.

Are you saying this is in the ES6 draft already? Where? I searched for `Function.prototype.caller` and `Function.prototype.arguments` but couldn’t find anything.

> Function.prototype.caller
null
> Function.prototype.arguments
null

Re-opening


(In reply to Mathias Bynens from comment #3)
> Are you saying this is in the ES6 draft already? Where? I searched for
> `Function.prototype.caller` and `Function.prototype.arguments` but couldn’t
> find anything.

It is here: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-createintrinsics
step 12: Perform AddRestrictedFunctionProperties(funcProto, realmRec).


Note that this is a change from ES5, which is what current browsers implement: the equivalent of AddRestrictedFunctionProperties() is applied to every strict-mode function (see [2], step 19), but not to `Function.prototype`; whereas ES6 apply it to `Function.prototype` only and rely on prototypal inheritance.

[2] ES5.1, section 13.2: http://www.ecma-international.org/ecma-262/5.1/#sec-13.2


(In reply to Claude Pache from comment #4)
> ...
> It is here:
> http://people.mozilla.org/~jorendorff/es6-draft.html#sec-createintrinsics
> step 12: Perform AddRestrictedFunctionProperties(funcProto, realmRec).
>
>
> Note that this is a change from ES5, which is what current browsers
> implement: the equivalent of AddRestrictedFunctionProperties() is applied to
> every strict-mode function (see [2], step 19), but not to
> `Function.prototype`; whereas ES6 apply it to `Function.prototype` only and
> rely on prototypal inheritance.
>
and upon the restriction in http://people.mozilla.org/~jorendorff/es6-draft.html#sec-forbidden-extensions


SpiderMonkey just got rid of .arguments and .caller on *all* functions (including strict mode functions), in favor of single accessors on Function.prototype. Those accessors, when invoked upon strict mode functions, and (for .caller) upon functions where the result would be a strict mode function, have almost identical behavior to the ES5 semantics. The only difference is in this case:

[jwalden@find-waldo-now src]$ dbg/js/src/js # with patch
js> function f() {}
js> "use strict"; f.caller = 5
5

where according to ES5 semantics the result should actually be:

[jwalden@find-waldo-now src]$ dbg/js/src/js # without patch
js> function f() {}
js> "use strict"; f.caller = 5
typein:2:14 TypeError: setting a property that has only a getter

It remains to be seen whether anyone depends upon the ES5 semantics. I heavily doubt anyone does.

If this succeeds in a release (it's too new to be in anything but nightlies), I see no reason why the spec couldn't be modified to make arguments/caller into accessors on Function.prototype. Pace comment 0, I don't think it's desirable or necessary to specify their behavior on all inputs. But it wouldn't be that bad to say they have algorithms that tail-call into implementation-defined behavior, once the spec-required must-throw-a-TypeError cases are handled.


(In reply to Jeff Walden (remove +beo to mail) from comment #6)
> SpiderMonkey just got rid of .arguments and .caller on *all* functions
> (including strict mode functions), in favor of single accessors on
> Function.prototype. Those accessors, when invoked upon strict mode
> functions, and (for .caller) upon functions where the result would be a
> strict mode function, have almost identical behavior to the ES5 semantics.
> The only difference is in this case:
>
> [jwalden@find-waldo-now src]$ dbg/js/src/js # with patch
> js> function f() {}
> js> "use strict"; f.caller = 5
> 5
>
> where according to ES5 semantics the result should actually be:
>
> [jwalden@find-waldo-now src]$ dbg/js/src/js # without patch
> js> function f() {}
> js> "use strict"; f.caller = 5
> typein:2:14 TypeError: setting a property that has only a getter
>
> It remains to be seen whether anyone depends upon the ES5 semantics. I
> heavily doubt anyone does.

Ironically, SES currently does, and is broken by this change. See https://code.google.com/p/google-caja/issues/detail?id=1935
But don't worry about that. I support this change and will fix SES to work with it.

Thanks for trying this out!



>
> If this succeeds in a release (it's too new to be in anything but
> nightlies), I see no reason why the spec couldn't be modified to make
> arguments/caller into accessors on Function.prototype. Pace comment 0, I
> don't think it's desirable or necessary to specify their behavior on all
> inputs. But it wouldn't be that bad to say they have algorithms that
> tail-call into implementation-defined behavior, once the spec-required
> must-throw-a-TypeError cases are handled.


The Rev27 section 16.1 restrictions on no-own 'caller' and 'arguments' only seem to restrict strict function per se, without explicitly restricting built-ins. The good news re the Nightly implementation is that at least the builtins I examined do not implement their own 'caller':

> var gopd = Object.getOwnPropertyDescriptor;
> gopd(Object, 'caller') === void 0
true

> Object.hasOwnProperty('caller')
false

Several bits of bad news re the Nightly implementation:

The various occurrences of ThrowTypeError are not ===:

> var tte = gopd(Function.prototype, 'caller').get;
> var tte2 = gopd(Function.prototype, 'arguments').get;
> tte === tte2
false

ThrowTypeError doesn't always throw. Sometimes it returns null instead:

> Object.caller;
null

> tte.call(Object);
null


The spec is too weak for security, and the Nightly implementation seems to deviate from the spec in ways I can't predict and thus don't understand the implications of. Because a mistake here is a potentially fatal security hole -- which is why we worked so hard on getting the ES5 poisoning right -- I'm escalating this to "Highest"/"blocker".


Those tests were on Nightly 35.0a1 (2014-09-02)


(In reply to Mark Miller from comment #8)
> Several bits of bad news re the Nightly implementation:
>
> The various occurrences of ThrowTypeError are not ===:
>
> > var tte = gopd(Function.prototype, 'caller').get;
> > var tte2 = gopd(Function.prototype, 'arguments').get;
> > tte === tte2
> false

I have no idea how this could be expected to work. These accessors exist once, only, on Function.prototype. But they have to behave like the old things on any non-strict, non-builtin function. That means .caller returns a caller function, .arguments returns an arguments object. Those return values aren't the same, so how could the two accessor functions be identical?

> ThrowTypeError doesn't always throw. Sometimes it returns null instead:
>
> > Object.caller;
> null
>
> > tte.call(Object);
> null

I deliberately didn't at this time make the accessors throw a TypeError when called on builtin functions. I wanted to perform the minimal experiment of having .caller and .arguments exist only on Function.prototype. I didn't want to expand the experiment to require that these properties throw when accessed on builtin functions. That would have introduced noise into the results: if something broke, was it due to accessors only on Function.prototype, or was it due to throwing on builtins? The kind of code that breaks for the first is quite different from the kind that breaks on the second.

It's easy at this point to change the behavior for builtins. I'll try to go back and do that later today.


(In reply to Mark Miller from comment #8)
> The Rev27 section 16.1 restrictions on no-own 'caller' and 'arguments' only
> seem to restrict strict function per se, without explicitly restricting
> built-ins.

Not correct, last sentence of first bullet in Rev27 16.1:

">>...Built-in functions<<, Strict mode functions created using the Function or Generator constructors and functions created using the bind and toMethod methods also must not (be, added in rev28) created with such own properties."

> ...
>
> The spec is too weak for security,

In what way, specifically? It's saying what we agreed to at recent TC39 meetings.


>and the Nightly implementation seems to
> deviate from the spec in ways I can't predict and thus don't understand the
> implications of. Because a mistake here is a potentially fatal security hole
> -- which is why we worked so hard on getting the ES5 poisoning right -- I'm
> escalating this to "Highest"/"blocker".


(In reply to Jeff Walden (remove +beo to mail) from comment #10)
...
> > > var tte = gopd(Function.prototype, 'caller').get;
> > > var tte2 = gopd(Function.prototype, 'arguments').get;
> > > tte === tte2
> > false
>
> I have no idea how this could be expected to work. These accessors exist
> once, only, on Function.prototype. But they have to behave like the old
> things on any non-strict, non-builtin function. That means .caller returns
> a caller function, .arguments returns an arguments object. Those return
> values aren't the same, so how could the two accessor functions be identical?

The intent is that the legacy extension caller/arguments properties must be implemented as own property (either an access or via [[Get]] hacks) of each non-strict function.

The values of F.p.caller and F.p.arguments are supposed to be the same (per realm) intrinsic function that unconditionally throws a TypeError exception


(In reply to Jeff Walden (remove +beo to mail) from comment #10)
> (In reply to Mark Miller from comment #8)
> > Several bits of bad news re the Nightly implementation:
> >
> > The various occurrences of ThrowTypeError are not ===:
> >
> > > var tte = gopd(Function.prototype, 'caller').get;
> > > var tte2 = gopd(Function.prototype, 'arguments').get;
> > > tte === tte2
> > false
>
> I have no idea how this could be expected to work. These accessors exist
> once, only, on Function.prototype. But they have to behave like the old
> things on any non-strict, non-builtin function.

Outside the spec, let's call these "sloppy functions". As Allen points out, each sloppy function that wishes to implement the legacy magic should have an own .caller and .arguments property. The .caller and .arguments inherited from Function.prototype must be only as specified -- accessors whose getter and setter are the unique per-realm ThrowTypeError, where this function's [[Call]] behavior is only to throw a TypeError of that realm.

Further, for safety (as was discussed at the TC39 meeting but may not have been recorded): The magic legacy own .caller and .arguments properties of sloppy functions should remain pseudo-data properties, so that their magic behavior is not reifiable as first class getter functions. The hard safety constraint is, if such a magic getter is visible, it *must not* act magically if applied to a non-sloppy function. By making these pseudo-data properties, we mostly[*] don't need to worry about this possibility.

In writing this, I wrote "mostly" just now because a possible problematic case occurred to me:

function fStrict() {"use strict"; ...}
function gSloppy() {...}

fStrict.__proto__ = gSloppy;
... fStrict.caller ...

Now that all non-sloppy functions other than Function.prototype no longer have their own .caller and .arguments, no matter whether gSloppy.caller is an own accessor or an own pseudo-data property, without additional mechanism, fStrict above would inherit gSloppy's .caller. However, safety demands that gSloppy's .caller's magic, as applied to fStrict, not act magically. Ideally, it would still throw a TypeError. This probably requires additional spec and implementation mechanism.

In light of these edge cases, I worry that it may be too high risk to try to reform the ES5 poisoning at this late date, since we cannot afford a mistake in this area. Perhaps we should keep the ES5 poison in ES6 and reform it afterwards, once we can take the time to get it right?




> That means .caller returns
> a caller function, .arguments returns an arguments object. Those return
> values aren't the same, so how could the two accessor functions be identical?
>
> > ThrowTypeError doesn't always throw. Sometimes it returns null instead:
> >
> > > Object.caller;
> > null
> >
> > > tte.call(Object);
> > null
>
> I deliberately didn't at this time make the accessors throw a TypeError when
> called on builtin functions. I wanted to perform the minimal experiment of
> having .caller and .arguments exist only on Function.prototype. I didn't
> want to expand the experiment to require that these properties throw when
> accessed on builtin functions. That would have introduced noise into the
> results: if something broke, was it due to accessors only on
> Function.prototype, or was it due to throwing on builtins? The kind of code
> that breaks for the first is quite different from the kind that breaks on
> the second.
>
> It's easy at this point to change the behavior for builtins. I'll try to go
> back and do that later today.

Yes, that is extremely important. Thanks!

FWIW, the right way to have taken the intermediate experimental step you explain above is not to complexify the behavior of Function.prototype.caller and Function.prototype.arguments themselves. Safety of the new design demands that these be dirt simple and obviously safe. They should just unconditionally throw TypeError irrespective of the this-arg they are applied to. The right way to have done this intermediate experiment would have been to give each builtin (other than Function.prototype itself) its own configurable/deletable own .caller and .arguments properties that overrode the inherited behavior -- unless and until deleted.


(In reply to Allen Wirfs-Brock from comment #11)
> (In reply to Mark Miller from comment #8)
> > The Rev27 section 16.1 restrictions on no-own 'caller' and 'arguments' only
> > seem to restrict strict function per se, without explicitly restricting
> > built-ins.
>
> Not correct, last sentence of first bullet in Rev27 16.1:
>
> ">>...Built-in functions<<, Strict mode functions created using the Function
> or Generator constructors and functions created using the bind and toMethod
> methods also must not (be, added in rev28) created with such own properties."

Oops. My mistake. Somehow I missed the "Built-in functions" part of that sentence.



> > ...
> >
> > The spec is too weak for security,
>
> In what way, specifically? It's saying what we agreed to at recent TC39
> meetings.

When I wrote this, I was concerned about the allegedly missing "Built-in functions" which you correctly point out was not missing.

However, there remain the issues I just raised in comment 13: Since the magic is still allowed to exist for own .caller and .arguments of sloppy functions, we must ensure the magic stays quarantined there. At TC39 we thought that it would be adequate for them to remain pseudo-data properties, so that their magic getters would not be reified. However, the fStrict and gSloppy scenario of comment 13 should that even this restriction is not adequate to ensure that the magic not be applied to non-sloppy functions.


>
>
> >and the Nightly implementation seems to
> > deviate from the spec in ways I can't predict and thus don't understand the
> > implications of. Because a mistake here is a potentially fatal security hole
> > -- which is why we worked so hard on getting the ES5 poisoning right -- I'm
> > escalating this to "Highest"/"blocker".


(In reply to Allen Wirfs-Brock from comment #12)
> (In reply to Jeff Walden (remove +beo to mail) from comment #10)
> ...
> > > > var tte = gopd(Function.prototype, 'caller').get;
> > > > var tte2 = gopd(Function.prototype, 'arguments').get;
> > > > tte === tte2
> > > false
> >
> > I have no idea how this could be expected to work. These accessors exist
> > once, only, on Function.prototype. But they have to behave like the old
> > things on any non-strict, non-builtin function. That means .caller returns
> > a caller function, .arguments returns an arguments object. Those return
> > values aren't the same, so how could the two accessor functions be identical?
>
> The intent is that the legacy extension caller/arguments properties must be
> implemented as own property (either an access or via [[Get]] hacks) of each
> non-strict function.

Yes, but only of sloppy functions. Built-ins and bounds are non-strict but must not get any magic.

And the magic must stay quarantined to these sloppy functions somehow. Having these be explicit accessors a) makes this more difficult, and b) is a change from the current pseudo-data legacy.

As the fStrict / gSloppy scenario of comment 13 demonstrates, just making these be pseudo-data own properties is no longer adequate to quarantine the magic.

>
> The values of F.p.caller and F.p.arguments are supposed to be the same (per
> realm) intrinsic function that unconditionally throws a TypeError exception

Yes. And only that. These must do no more than the spec specifies -- always throw. Keep these simple enough to be obviously safe!


(In reply to Allen Wirfs-Brock from comment #12)
> The intent is that the legacy extension caller/arguments properties must be
> implemented as own property (either an access or via [[Get]] hacks) of each
> non-strict function.

Our experiment isn't concerned about spec intent at all. (At the time we did it, I don't believe there was any draft.) It's trying to get rid of all magical-appearance behavior anywhere for .caller and .arguments on any function, and to demonstrate this is feasible. That's strict mode functions, non-strict functions (or sloppy if you want, I don't care), builtins, arrow functions, functions with rest arguments, bound functions, *everything*.

> The values of F.p.caller and F.p.arguments are supposed to be the same (per
> realm) intrinsic function that unconditionally throws a TypeError exception

Again, this was not a goal of our experiment. Burdening non-strict functions with anti-poison pills is, IMO, as hostile to implementation as poison pills on strict mode/builtin/bound functions is. Worse, even, as such functions are presently the common case.

(In reply to Mark Miller from comment #13)
> Further, for safety (as was discussed at the TC39 meeting but may not have
> been recorded): The magic legacy own .caller and .arguments properties of
> sloppy functions should remain pseudo-data properties, so that their magic
> behavior is not reifiable as first class getter functions. The hard safety
> constraint is, if such a magic getter is visible, it *must not* act
> magically if applied to a non-sloppy function. By making these pseudo-data
> properties, we mostly[*] don't need to worry about this possibility.

The SpiderMonkey team generally considers anything exceeding the scope of data properties or accessor properties, in ES5 terms, to be incredibly unsafe. We have such a concept (more than one, depending how you count) in SpiderMonkey now. We're working to get rid of it because we are not smart enough to reason about it -- its implementation, its security properties, its correctness considered in concert with all ES6's flexibility, particularly proxies. The ES5 concepts we can handle. We don't want to add something more, and have to provide our own fallible reasoning as to how it fits into every aspect of the ES5/6 MOP. I'm strongly opposed to any behavior, magical or otherwise, that can't be exposed as a data property or an accessor property.

I think your "mostly" case is a contribution to the argument that magic properties are a bad idea. :-)

> FWIW, the right way to have taken the intermediate experimental step you
> explain above is not to complexify the behavior of Function.prototype.caller
> and Function.prototype.arguments themselves.

Your proposed step is a good deal more complex than the one we took, for what it's worth.

> Safety of the new design demands that these be dirt simple and obviously
> safe.

I disagree. The requirement that such accessors throw in an enumerated list of cases is pretty easy to specify. Behavior in other cases can easily be specified as implementation-defined behavior.

In a build with the make-builtins-throw change applied, these are the cases that throw for arguments accessors:

* if the function is builtin
* if the function has strict mode code
* if the function is a bound function
* if the function has a rest argument

And these are the cases that throw for caller accessors:

* if the function is builtin
* if the function has strict mode code
* if the function is a bound function

It's true caller needs a little more to say that if the retrieved caller function would be strict mode code, a TypeError should be thrown. But that doesn't particularly change the safety/complexity of the restrictions.


(In reply to Jeff Walden (remove +beo to mail) from comment #16)
> (In reply to Allen Wirfs-Brock from comment #12)
> > The intent is that the legacy extension caller/arguments properties must be
> > implemented as own property (either an access or via [[Get]] hacks) of each
> > non-strict function.
>
> Our experiment isn't concerned about spec intent at all.

My mistake. I had not realized this. Given that this happened after the last TC39 mtg, I assumed this was more than just a coincidence ;).


> (At the time we
> did it, I don't believe there was any draft.) It's trying to get rid of all
> magical-appearance behavior anywhere for .caller and .arguments on any
> function, and to demonstrate this is feasible.

The best solution all around would be to kill the magic legacy .caller and .arguments behavior completely. I doubt this is what you mean, but since it would be so wonderful, I thought I'd check first before assuming away this happy possibility. Is it?

If not, and we still retain this unsafe magical behavior, then the question is how to quarantine the magic, so that it doesn't prevent safety within contexts that prohibit sloppy code. Agreed?


> That's strict mode
> functions, non-strict functions (or sloppy if you want, I don't care),

Just to be clear, the reason I care is built-in functions are neither strict nor sloppy. Since they are not strict, IMO we sow confusion by saying that builtins are "not strict and not non-strict". To the non-pedant, if builtins are "not non-strict", then surely they must be strict, right? I think our terminology on this is screwed up. I doubt we can agree on "sloppy" as an official spec term, but we need something clearer than "non-strict".


> builtins, arrow functions, functions with rest arguments, bound functions,
> *everything*.
>
> > The values of F.p.caller and F.p.arguments are supposed to be the same (per
> > realm) intrinsic function that unconditionally throws a TypeError exception
>
> Again, this was not a goal of our experiment. Burdening non-strict
> functions with anti-poison pills is, IMO, as hostile to implementation as
> poison pills on strict mode/builtin/bound functions is. Worse, even, as
> such functions are presently the common case.

It's no worse than the ES5 situation, which is where we are unless we can agree on something that's clearly safer.

In any case, the point of the change that TC39 discussed was to unburden non-sloppy functions from requiring the poison pills. Making sloppy functions cheaper would also be good, all else being equal. Perhaps this can still be an outcome of this design direction -- I am actually hopeful it can. But this level of research this late in the ES6 standardization cycle is scary. At this point, this discussion makes me feel more strongly that ES6 should stick with ES5 poisoning, and this poisoning reform research should be postponed to ES7.

That said, if we do want to sacrifice the schedule to get this into ES6, let's arrange some f2f meetings to hash through the various issues at low latency. I do think something like you propose can be made to work.


> (In reply to Mark Miller from comment #13)
> > Further, for safety (as was discussed at the TC39 meeting but may not have
> > been recorded): The magic legacy own .caller and .arguments properties of
> > sloppy functions should remain pseudo-data properties, so that their magic
> > behavior is not reifiable as first class getter functions. The hard safety
> > constraint is, if such a magic getter is visible, it *must not* act
> > magically if applied to a non-sloppy function. By making these pseudo-data
> > properties, we mostly[*] don't need to worry about this possibility.
>
> The SpiderMonkey team generally considers anything exceeding the scope of
> data properties or accessor properties, in ES5 terms, to be incredibly
> unsafe. We have such a concept (more than one, depending how you count) in
> SpiderMonkey now. We're working to get rid of it because we are not smart
> enough to reason about it -- its implementation, its security properties,
> its correctness considered in concert with all ES6's flexibility,
> particularly proxies. The ES5 concepts we can handle. We don't want to add
> something more, and have to provide our own fallible reasoning as to how it
> fits into every aspect of the ES5/6 MOP. I'm strongly opposed to any
> behavior, magical or otherwise, that can't be exposed as a data property or
> an accessor property.
>
> I think your "mostly" case is a contribution to the argument that magic
> properties are a bad idea. :-)

I do not know how to reason about the legacy sloppy .caller and .arguments behaviors. Again, if we can actually get rid of that magic, I would be overjoyed; but surprised. Can we?



> > FWIW, the right way to have taken the intermediate experimental step you
> > explain above is not to complexify the behavior of Function.prototype.caller
> > and Function.prototype.arguments themselves.
>
> Your proposed step is a good deal more complex than the one we took, for
> what it's worth.

I can't tell whether the step you took safely quarantines the legacy sloppy .caller and .arguments behaviors. If it does not, then it is either too complex, or is simply fatally unsafe. Can you convince me that you have safely quarantined these?

Given two designs that we can be confident safely quarantine these behaviors, I agree that the less magical one, whichever that is, is almost certainly better.


More later...


(In reply to Mark Miller from comment #15)
> > The values of F.p.caller and F.p.arguments are supposed to be the same (per
> > realm) intrinsic function that unconditionally throws a TypeError exception
>
> Yes. And only that. These must do no more than the spec specifies -- always
> throw. Keep these simple enough to be obviously safe!

Let me try to convince you that Jeff's approach is "obviously safe".

- Each of the two properties has a single implementation, a getter function.

- The code of those getters says that if |this| is a strict function, we throw.

This completes the proof. The safety rules are explicitly spelled out in a single place, as close as possible to the location of the danger---literally in the same algorithm, just before the part where dangerous information would be exposed.

That "as close as possible" bit is what gets me. The other approaches end up spreading the enforcement of the safety rules through multiple parts of the spec and multiple parts of our codebase. Jeff's approach is the obviously safe one.

As a bonus, we get to make Function objects ordinary, which to me makes them a lot easier to reason about.


(In reply to Jason Orendorff from comment #18)
> (In reply to Mark Miller from comment #15)
> > > The values of F.p.caller and F.p.arguments are supposed to be the same (per
> > > realm) intrinsic function that unconditionally throws a TypeError exception
> >
> > Yes. And only that. These must do no more than the spec specifies -- always
> > throw. Keep these simple enough to be obviously safe!
>
> Let me try to convince you that Jeff's approach is "obviously safe".
>
> - Each of the two properties has a single implementation, a getter function.
>
> - The code of those getters says that if |this| is a strict function, we
> throw.

Since you make a claim of obviousness, I feel unembarrassed to make a pedantic reply:

What about builtins (and bound, etc)? Without these, your allegedly obviously safe rule is actually obviously unsafe.




>
> This completes the proof. The safety rules are explicitly spelled out in a
> single place, as close as possible to the location of the danger---literally
> in the same algorithm, just before the part where dangerous information
> would be exposed.
>
> That "as close as possible" bit is what gets me. The other approaches end up
> spreading the enforcement of the safety rules through multiple parts of the
> spec and multiple parts of our codebase. Jeff's approach is the obviously
> safe one.
>
> As a bonus, we get to make Function objects ordinary, which to me makes them
> a lot easier to reason about.


(In reply to Jason Orendorff from comment #18)
> - The code of those getters says that if |this| is a strict function, we
> throw.

P.S. This of course is not the actual complete suite of safety rules, but that doesn't affect the line of reasoning I'm trying to convey here, to wit: The best way to specify a set of safety rules is to write them all down in a single place. The best way to enforce them is by checking immediately before the dangerous operation.


(In reply to Mark Miller from comment #19)
> Since you make a claim of obviousness, I feel unembarrassed to make a
> pedantic reply:

Oh, for shame. :-)

I hope comment 20 is an adequate reply! Otherwise we should chat.


(In reply to Jason Orendorff from comment #20)
> (In reply to Jason Orendorff from comment #18)
> > - The code of those getters says that if |this| is a strict function, we
> > throw.
>
> P.S. This of course is not the actual complete suite of safety rules, but
> that doesn't affect the line of reasoning I'm trying to convey here, to wit:
> The best way to specify a set of safety rules is to write them all down in a
> single place. The best way to enforce them is by checking immediately before
> the dangerous operation.

You may be right. Please make an "obviously safe" argument that is complete enough that I can try to attack it.

> I hope comment 20 is an adequate reply! Otherwise we should chat.

We should chat.


(In reply to Mark Miller from comment #22)
> > I hope comment 20 is an adequate reply! Otherwise we should chat.
>
> We should chat.

OK, cool -- please join the #jslang channel on irc.mozilla.org and say my name -- I'm jorendorff there (and on Freenode, in case that's more convenient). Other options would be fine too, I'm easy.


(In reply to Mark Miller from comment #22)
> You may be right. Please make an "obviously safe" argument that is complete
> enough that I can try to attack it.

(bashfully) Well, I may not understand all the rules! But here is a first cut at a concrete specification of .caller:


### get Function.prototype.caller

Function.prototype.caller is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

1. Let F be the this value.

2. If F is not a non-strict mode ECMAScript function, throw a TypeError exception.

3. Let C be an implementation-defined value that is either a Function Object or null.

4. If C is not a non-strict mode ECMAScript function, return null.

5. Return C.


Now, I may not have correctly specified the safety rules in steps 2 and 4, but surely you grant that it's possible to do so? And that done, "obviously" the rules would then be correctly enforced, right?

I further claim that this style of specification (whitelisting the kind of legacy code that we have to avoid breaking) makes gaps less likely.


For completeness, here are some NOTEs I would add:

NOTE This is a legacy feature. Implementations have long provided a .caller property on function objects that provides (very) rudimentary access to the call stack. As a tool for examining the state of a running program, it is less than ideal: it does not handle stacks containing recursion; it does not properly identify scripts or eval code, as those are not functions; it skips built-in functions; it was never fully specified. Furthermore, exposing this capability in the first place is a security concern for some systems, as the .caller property can be used by untrusted code to obtain a reference to a function that might otherwise be impossible to reach. For this reason, the mechanism is explicitly specified *not* to work except for non-strict mode functions. Secure systems can force the untrusted code they run into strict mode by prepending a Use Strict Directive before running it.

NOTE 2 In implementations that predate this standard, .caller is commonly implemented as a special own data property on all Function objects. Deleting `Function.prototype.caller` is useless as a security measure in such implementations.


Mark,

Rather than continuing to invent asymptotically better roadblocks to keep implementers from implementing the legacy 'caller' and 'argument' properties, why don't we just get even more specific in 16.1 about what they are forbidden to do. After all, totally forbidding 'caller' still wouldn't stop an implementation from implementing 'callerEx' that had the same undesirable behavior.

I think you probably need to take the first draft at articulating what you want said, but I imagine it could be along the lines of something like:

An implementation must not provide any extensions that, depending solely based upon access to a function, reveals the identity of any strict or built-in function that, has previously called the accessible function.


(In reply to Jason Orendorff from comment #24)
> (In reply to Mark Miller from comment #22)
> > You may be right. Please make an "obviously safe" argument that is complete
> > enough that I can try to attack it.
>
> (bashfully) Well, I may not understand all the rules! But here is a first
> cut at a concrete specification of .caller:
>
>
> ### get Function.prototype.caller
>
> Function.prototype.caller is an accessor property whose set accessor
> function is undefined. Its get accessor function performs the following
> steps:
>
> 1. Let F be the this value.
>
> 2. If F is not a non-strict mode ECMAScript function, throw a TypeError
> exception.
>
> 3. Let C be an implementation-defined value that is either a Function Object
> or null.
>
> 4. If C is not a non-strict mode ECMAScript function, return null.
>
> 5. Return C.
>
>
> Now, I may not have correctly specified the safety rules in steps 2 and 4,
> but surely you grant that it's possible to do so? And that done, "obviously"
> the rules would then be correctly enforced, right?
>
> I further claim that this style of specification (whitelisting the kind of
> legacy code that we have to avoid breaking) makes gaps less likely.


Hi Jason, this is excellent. It is clear enough that I've been able to think about attacking it, and I can tell I'm failing to attack it. This is precisely what I was looking for. The NOTEs are good too, especially NOTE 2.

Of course, we need a similar treatment of Function.prototype.arguments.

We should wonder if there are any changes we should make to arguments.callee and arguments.caller. It seems a bit weird that these remain own-poisoned, but I don't immediately see an alternative. Assuming these are not changed, we should still wonder if there is some interaction between these and arguments.calle* that potentially creates a problem. But I don't see one.

I feel much more relaxed about all this. Thanks!


(In reply to Allen Wirfs-Brock from comment #26)
> Mark,
>
> Rather than continuing to invent asymptotically better roadblocks to keep
> implementers from implementing the legacy 'caller' and 'argument'
> properties, why don't we just get even more specific in 16.1 about what they
> are forbidden to do. After all, totally forbidding 'caller' still wouldn't
> stop an implementation from implementing 'callerEx' that had the same
> undesirable behavior.
>
> I think you probably need to take the first draft at articulating what you
> want said,

I agree. I will think about how to approach this. When would you need it by for it to be a candidate for inclusion in ES6?


> but I imagine it could be along the lines of something like:
>
> An implementation must not provide any extensions that, depending solely
> based upon access to a function, reveals the identity of any strict or
> built-in function that, has previously called the accessible function.

I see various problems with this phrasing, but no matter. I take your point, and this suggestion is enough to seed the effort. Thanks!


(In reply to Mark Miller from comment #28)
> (In reply to Allen Wirfs-Brock from comment #26)
> > Mark,
> >
> > Rather than continuing to invent asymptotically better roadblocks to keep
> > implementers from implementing the legacy 'caller' and 'argument'
> > properties, why don't we just get even more specific in 16.1 about what they
> > are forbidden to do. After all, totally forbidding 'caller' still wouldn't
> > stop an implementation from implementing 'callerEx' that had the same
> > undesirable behavior.
> >
> > I think you probably need to take the first draft at articulating what you
> > want said,
>
> I agree. I will think about how to approach this. When would you need it by
> for it to be a candidate for inclusion in ES6?
>

How about by the end of September. Of course, sooner is always better.


(In reply to Allen Wirfs-Brock from comment #29)
> (In reply to Mark Miller from comment #28)
> > (In reply to Allen Wirfs-Brock from comment #26)
> > > Mark,
> > >
> > > Rather than continuing to invent asymptotically better roadblocks to keep
> > > implementers from implementing the legacy 'caller' and 'argument'
> > > properties, why don't we just get even more specific in 16.1 about what they
> > > are forbidden to do. After all, totally forbidding 'caller' still wouldn't
> > > stop an implementation from implementing 'callerEx' that had the same
> > > undesirable behavior.
> > >
> > > I think you probably need to take the first draft at articulating what you
> > > want said,
> >
> > I agree. I will think about how to approach this. When would you need it by
> > for it to be a candidate for inclusion in ES6?
> >
>
> How about by the end of September. Of course, sooner is always better.

Yeah, I was afraid of that but not surprised. I'm sure I couldn't get that done by then. I'll take it as an ES7 suggestion.

In any case, the more I think about it, the more I like Jason's formulation of the new .caller rules. I am hopeful that we can get this solid by ES6.


(In reply to Mark Miller from comment #27)
> We should wonder if there are any changes we should make to arguments.callee
> and arguments.caller. It seems a bit weird that these remain own-poisoned,
> but I don't immediately see an alternative. Assuming these are not changed,
> we should still wonder if there is some interaction between these and
> arguments.calle* that potentially creates a problem. But I don't see one.

FWIW, V8 uses own getter/setter for arguments.calle* for strict functions and own data property for non strict functions.

function strictF() {
'use strict';
console.log(Object.getOwnPropertyDescriptor(arguments, 'callee'))
}
strictF() // {get: function, set: function, enumerable: false, configurable: false}

function sloppyF() {
console.log(Object.getOwnPropertyDescriptor(arguments, 'callee'))
}
sloppyF() // {value: function, writable: true, enumerable: false, configurable: true}


(In reply to Mark Miller from comment #30)
> (In reply to Allen Wirfs-Brock from comment #29)
> >
> > How about by the end of September. Of course, sooner is always better.
>
> Yeah, I was afraid of that but not surprised. I'm sure I couldn't get that
> done by then. I'll take it as an ES7 suggestion.

If necessary, I can probably give you more time. Maybe even until mid-November. It's shouldn't be a lot to integrate


There has been language in clause 16.1 (forbidden extensions) for quite awhile that address this issue. In Rev34 I've augment that language a bit.

Mark, you should review that.


I do not see a flaw with the current language. Looks great.

Is there anything in particular you remain worried about, that you'd like me to examine in greater depth?


fixed in rev34