Strict undefined this

Mark S. Miller erights at google.com
Fri Aug 29 19:03:02 PDT 2008


Given strict function F that internally uses "this" as an rvalue, what
should happen when F is called as a function? Concretely, given

"use strict";
function F(t) {
    if (t) {
        return this;
    } else {
        return 0;
    }
}

what should be the outcome of F(true) and F(false)? Previously, we
have examined at least the following three possibilities and agreed on
the first:

a) F(true) throws. F(false) returns 0.

The rule is that, in an execution context in which 'this' is bound to
null or undefined, evaluating 'this' as an rvalue in strict code
throws. (In non-struct code it returns the global object.)

b) F(anything) always throws before executing any code in its body.

The rule would be: if F is a strict function which mentions 'this'
freely, then an attempt to call it with 'this' bound to null or
undefined throws rather than entering F.

c) F(true) returns undefined. F(false) returns 0.

The rule would be that no coercion or special case happens at all.
Whatever value is associated with 'this' in the present execution
context is returned.



With regard to the original safety concern, any of these three are
fine solutions. They all prevent the privilege escalation attack of
obtaining access to the global object by magic. However, I've just
noticed that the first solution adds yet another break with Tennent
Correspondence. The other two bend Tennent Correspondence, but don't
break it:

"use strict";
function test(cond,then,els) {
    if (cond) { return then(); } else { return els(); }
}
function F2(t) {
    return test(t,
        (function(){ return this;}).bind(this),
        function() { return 0; }
    );
}

The bending is that, instead of forming a closure around an expression
by surrounding it simply with "function() {return ...;}", if the
original expression mentions "this" freely, you'd have to add a
".bind(this)" on the end to get a proper lambda abstraction of that
expression. Other variations:

Do renaming rather than binding

function F3(t) {
    const that = this;
    return test(t,
        function(){ return that;},
        function() { return 0; }
    );
}

Always bind unconditionally, whether or not the expression in question
mentions "this":

function F4(t) {
    return test(t,
        (function(){ return this;}).bind(this),
        (function() { return 0; }).bind(this)
    );
}

Under which rule are which of these closure-based rewrites equivalent
to the original F?

Under rule #a, none are equivalent to F because F(false) doesn't throw
but F2(false), F3(false), and F4(false) would all throw.

Under rule #b, all mention 'this' freely, and so F*(*) would throw on
entry. However, the always-bind-unconditionally variation would still
break Tennent Correspondence under rule #b, since it will introcude a
this-breakage into a function G that never mentions 'this'.

Under rule #c,  F*(true) would always yield undefined. Any of these
variations would satisfy Tennent Correspondence.

Therefore, I'd prefer either #b or #c to #a. Between #b and #c I don't
have a strong preference, but I think I prefer #b (it's safer) to #c
(it more clearly preserves Tennent Correspondence and is generally
easier to reason about). But I'd be happy with either.

This question needs to be settled for the ES3.1 spec, and so has a
greater urgency than many of our other recent discussions.

-- 
    Cheers,
    --MarkM


More information about the Es-discuss mailing list