ES4 draft: strict mode
2008/4/10 Lars Hansen <lhansen at adobe.com>:
Here is the first draft of the spec for strict mode in ES4. Since ES3.1 will probably also incorporate a strict mode I have tried to be mindful of that in a couple of places (notably with respect to the syntax).
The pragma "use strict" enables strict mode for code within the scope of the pragma.
The pragma takes the form of either an ES4 pragma or a do-nothing one-armed if statement:
"use" "strict" "if" "(" "false" ")" "use" "(" "strict" ")"
NOTE The second form of the "use strict" pragma is a concession to ES3.1, because that form of the pragma is compatible with ES3 syntax. It is not recommended for ES4 code.
Thanks Lars,
I hadn't anticipated the "if (false) ..." hack. It does satisfy the ES3.1 parsing requirements, but it sure is ugly. I don't yet have a better suggestion though.
"use" "standard"
"if" "(" "false" ")" "use" "(" "standard" ")"
NOTE The "use standard" pragma is a concession to programs that intermix standard and strict parts. It is restricted to the top level because ambiguities would result if it were to be used inside other scopes.
Should we adopt the term "loose", or some other antonym for "strict", rather than "standard"? As someone mentioned previously, the problem with the term "standard" is that both modes are specified by the various standards we are writing. Historically, "standard mode" was understood on the web as contrasted with "quirks mode", which is exactly the wrong connotations.
Run-time checks this never captures the global object
In ES3, when a function is called "as a function" (that is, not as a method on some receiver object -- the notion is not syntactic, but can depend on the binding of the function) the value of this passed to the function is null. The callee (or the call protocol) substitutes the callee's global object for the null value, so the value of this observed in the callee is the callee's global object.
If the callee is strict, however, the null is not converted to the callee's global object. Instead, if the callee evaluates the expression this when the value of this is null then a ReferenceError is thrown.
Since the null-ness of this value is never observable, either in strict or
loose mode, can we substitute in an equally unobservable undefined? Elsewhere, undefined is sometimes used as a data value representing the failure of an operation (such as a loose read), whereas null is only produced by a successful report of the absense of an object.
Since this is just an expository difference, I don't care that much. But it would make the spec more intuitive to me.
Writing to properties
If an assignment expression that is strict would write to a read-only property or variable then a ReferenceError is thrown.
Likewise for a top-level assignment in a strict eval operator?
Likewise for creating a property on a non-dynamic/sealed object?
Likewise for creating an overriding own property on an object that inherits a final method that would be shadowed?
Creating global variables
If an assignment expression that is strict would create a new property on the global object (regardless of whether the assignment is to a variable or to a property on an object that turns out to be the global object) then a ReferenceError is thrown.
Why call out "or to a property on an object that turns out to be the global object" as a special case? Either the global object is non-dynamic/sealed, in which case it's covered by the previous case, or it isn't, in which case we should allow explicit property creation.
Deleting properties
If a delete expression that is strict would delete a variable or property that is a fixture or that is marked as not removable, then a * ReferenceError* is thrown.
If a delete expression that is strict would delete a variable that is not in scope or a property that is not an own property on the object from which it were to be deleted, then a ReferenceError is thrown.
I would hope that a strict delete could not delete a variable, period. Why the "that is not in scope" qualification?
Arity checking
If a function that is strict is called with fewer or more arguments than it expects then a TypeError is thrown.
I just cannot figure out any way to reconcile this with ES3.1. Instead, I propose that
-
missing arguments turn into undefined, as in loose mode. To turn missing arguments into errors in ES4 (loose or strict), give the corresponding parameter variables types that reject undefined.
-
extra arguments to a strict function cause a thrown error, unless the invoked function mentions 'arguments'. (See below for more on 'arguments')
I know this is almost too ugly to stand, but I don't know what else to do. Suggestions appreciated!
arguments
If a function is strict then its arguments object does not share storage with the formal parameters of the function, and those properties of the arguments object that correspond to the formal parameters, as well as the length property of the arguments object, are neither writeable nor removable.
Once we have language elsewhere in the spec explaining what it means for an array to be non-writable and non-dynamic, we should reference that here.
If a function is strict and the implementation supports the ES1 style FunctionObject.arguments facility, then a ReferenceError is thrown if the arguments property is accessed on any instance of the function.
Likewise for caller? callee?
eval
If the eval operator is strict and attempts to introduce a new binding into its inherited variable binding object then a ReferenceError is thrown (even if that binding object is the global object).
If the eval operator is strict then the program it evaluates is also strict.
Yay!
NOTE The global eval function is not strict.
Should it have an optional strictness flag?
Candidates for inclusion
I'm uncertain about the following because they require levels of analysis that I don't want to burden lightweight implementations with. I believe they fit in better with an optional verifier (part of a tool chain). Lightweight variants can be defined but it's unclear how valuable those would be. Reference before definition
(Heavyweight variant) In a strict function it is a static error if the compiler cannot prove, by definite assignment analysis for example, that a variable has always been initialized at every point where it is referenced.
(Lightweight variant) In a strict function it is a static error if the compiler can prove, by simple forward attribute propagation for example, that a variable is never initialized at some point when it is referenced. Consider for example this:
function f(y) { let x; if (y == 1) return x; // clear error return x; // clear error while (true) { // backward edge kills analysis if (y == 1) return x; // not an error } }
In my opinion the heavyweight variant should not be mandated and the lightweight variant is not very interesting, but it can be interesting to discuss it.
I agree that neither of these is attractive. What about the obvious dynamic variant: If a variable is used before it's initialized, a ReferenceError is thrown. Perhaps this is covered elsewhere?
this never captures the global object
The utility of this is that functions don't gain access to the global object accidentally. Since functions can't introduce new bindings in the global object in strict mode the restriction is more defense-in-depth than anything
For the record: The ES3 this-capture makes JavaScript a language that's almost impossible to secure in the way several projects are currently trying to secure it -- by subsetting + taming legacy libraries (Caja, ADsafe, FBJS, Jacaranda, Capsol). The problem is that these (presumed innocent) legacy libraries can be misled into performing a privilege escalation on behalf of the attacker, by feeding one part of the innocent legacy into another in a way it wasn't built to expect. The reason several of us are so passionate to get this fixed is so that the global object can be successfully denied to code in a feasibly-enforced subset of JavaScript.
(after all, in ES4 the global object is available through the global variable global).
I didn't know that. Where can I read about the proposed standard global variable names?
arguments
The utility of making the arguments object read-only is that the aliasing with the formal parameters is unlikely to be desirable as the common case.
The utility of making access to FunctionObject.arguments throw an exception is that the mechanism, if unchecked, allows arbitrary functions to look at and modify the arguments and formal parameters of active methods. This is a privacy and security problem.
Good. Likewise with caller.
NOTE It's daring of us to standardize restrictions on the behavior of a feature that's not itself specified by the Standard.
On the ES3.1 phone call this morning, we talked about including a non-normative description of these widespread non-standard operations in an Annex. The ES3 Annex B already serves a similar function.
One suggestion that was made that I have not adopted in this draft is that
if a reference to arguments is visible in the code of a strict function then the formals should be made read-only so that the captured arguments array would always hold the same values as the formals. The problem with this, of course, is that eval can reference arguments without that reference being visible when the function is entered. So what should happen if arguments is referenced by eval code after one of the formals has been changed? Is the arguments object captured at that point (not on function entry), and are the formals made read-only at that point? It doesn't seem to be worth the bother to do this.
If you don't make the parameter variables read-only, then this eval behavior presents a different difficulty: Should eval('arguments') return a snapshot of the arguments array as of the time that the function was called, at the time that the eval operator is invoked, or at the time that the arguments expression is evaluated? The first is pointlessly expensive. The others are too irregular.
I propose that a strict eval operator can only evaluate a top-level 'arguments' expression if the containing (necessarily strict) function itself mentions arguments. Then we can statically know that we need to snapshot the arguments vector on entry.
On Tue, Apr 15, 2008 at 6:58 PM, Mark S. Miller <erights at google.com> wrote:
2008/4/10 Lars Hansen <lhansen at adobe.com>:
(after all, in ES4 the global object is available through the global variable global).
I didn't know that. Where can I read about the proposed standard global variable names?
Can anyone offer a quick answer to this specifically? Where can I read about the ES4 global variable named "global"? Thanks.
proposals:globals, proposals:globals , and there's
always the reference implementation. I think the 'global' variable has moved from intrinsic to ES4 but that's just a detail.
Compatibility syntax:
For ES3 compatibility it would be simpler to just use: use strict which parses as two expression statements in ES3.
|this| never captures the global object
In ES3, when a function is called "as a function" (that is, not as a method on some receiver object -- the notion is not syntactic, but can depend on the binding of the function) the value of |this| passed to the function is null. The callee (or the call protocol) substitutes the callee's global object for the null value, so the value of |this| observed in the callee is the callee's global object.
If the callee is strict, however, the null is not converted to the callee's global object. Instead, if the callee evaluates the expression |this| when the value of |this| is null then a ReferenceError is thrown.
FIXME We want to make that more precise but the current prose captures the intent well enough.
ES3 also special-cases <undefined>.
If I do Foo.call(undefiend, x, y)
and Foo tries to get the value of <this>, then it will throw an error instead of returning <undefined>?
1.5.1: I prefer the lightweight variant.
1.5.2: This can lead to trouble. For example, is the statement x && f(); provably effect-free? What if the compiler discovers that x is a constant with the value false?
What about the following? ... globaleval("make x have a getter with a side effect"); ... x;
NOTE It's daring of us to standardize restrictions on the behavior of a feature that's not itself specified by the Standard.
I agree. This does not belong in the standard. If we have a non-normative appendix, it might go there.
Waldemar
On Wed, Apr 16, 2008 at 10:11 AM, Lars Hansen <lhansen at adobe.com> wrote:
proposals:globals, and there's always the reference implementation. I think the 'global' variable has moved from intrinsic to ES4 but that's just a detail.
What is the significance of intrinsic vs ES4? I'm asking because I'm thinking of possibly proposing that "global" also name the global object in ES3.1. However, ES3.1 has no namespaces.
On Wed, Apr 16, 2008 at 10:11 AM, Lars Hansen <lhansen at adobe.com> wrote:
proposals:globals, and there's always the reference implementation. I think the 'global' variable has moved from intrinsic to ES4 but that's just a detail.
On 17/04/2008, Mark S. Miller <erights at google.com> wrote:
What is the significance of intrinsic vs ES4? I'm asking because I'm thinking of possibly proposing that "global" also name the global object in ES3.1. However, ES3.1 has no namespaces.
ES4 is per default open in ES4 mode but not in ES3 mode, it's a way to protect the ES3 namespace from being infected by names and functionality that could break live ES3 programs. intrinsic is on the other hand not open per default but has to be specified explicitly, either in implicit::identifier form or through a use implicit directive. Or that's my understanding of it, anyway.
On Wed, Apr 16, 2008 at 6:09 PM, liorean <liorean at gmail.com> wrote:
ES4 is per default open in ES4 mode but not in ES3 mode, it's a way to protect the ES3 namespace from being infected by names and functionality that could break live ES3 programs.
I see. I was wondering about the obvious compatibility problems of introducing a new global variable name. This answers that, and is obviously the right thing for ES4 to do. (And is not a detail!)
Now that I understand, I will not suggest adding 'global' to ES3.1. Thanks.
Here is the first draft of the spec for strict mode in ES4. Since ES3.1 will probably also incorporate a strict mode I have tried to be mindful of that in a couple of places (notably with respect to the syntax).