ES4 draft: strict mode

Mark S. Miller erights at google.com
Tue Apr 15 18:58:12 PDT 2008


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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080415/75ec1f9e/attachment-0002.html 


More information about the Es4-discuss mailing list