What have we done about the mutable global object? (was Re: Es4-discuss Digest, Vol 8, Issue 44)
Brendan Eich
brendan at mozilla.org
Tue Oct 30 23:45:51 PDT 2007
On Oct 30, 2007, at 6:14 PM, Brendan Eich wrote:
> ES4 provides const, fixed typename bindings, lexical scope (let),
> program units, and optional static type checking -- all of which
> *do* make ES4 code easier to analyze and instrument to enforce
> security properties.
I left out the intrinsic namespace from the above litany, but Graydon
nicely pointed it out. I wonder if it's understood from the overview,
and from the proposal page:
http://wiki.ecmascript.org/doku.php?id=proposals:intrinsic_namespace
This message tries to summarize how intrinsic and related new ES4
facilities such as fixtures work together to improve integrity
against XSS threats. It's late, I hope I got everything right. My TG1
colleagues will correct me if not. You can test in the RI to see
intrinsic in action -- in particular, there's an intrinsic::print
method, it's the P in our REPL.
The intrinsic namespace evolved in part from the desire to support
opt-in early binding of standard methods, which is a feature of the
ECMA-262 Edition 3 Compact Profile (http://www.ecma-international.org/
publications/standards/Ecma-327.htm). Here's how it works:
* For each standard prototype method, there is a method with the same
unqualified (local) name, but in the intrinsic namespace. So
Array.prototype.slice is paired with a method named intrinsic::slice
in class Array.
* The prototype method typically calls the intrinsic method on its
dynamic |this|, e.g.
/* E262-3 15.6.4.3: Boolean.prototype.valueOf. */
prototype function valueOf(this: AnyBoolean)
this.intrinsic::valueOf();
from builtins/Boolean.es in the RI (note the expression closure
syntax), but for Array, String, and string generics, there's a static
generic method to delegate to from both the prototype and intrinsic
method:
prototype function slice(start, end)
Array.slice(this,
start === undefined ? 0 : Number(start),
end === undefined ? Infinity : Number(end));
intrinsic function slice(start: AnyNumber=0, end:
AnyNumber=Infinity): Array
Array.slice(this, start, end);
(Note the extra special logic in the prototype slice method required
by ES3.)
The upshot is that this pairing of prototype and intrinsic methods is
not expensive in terms of code footprint.
* The intrinsic methods are fixtures: DontDelete bindings searched
ahead of the prototype chain.
* These methods are type-annotated with more precise argument and
return types, e.g. while Array.prototype.slice has signature
function (start: *, end: *): *
(not spelled out, of course: (start, end) is enough for the formal
parameter list -- this isn't Java :-P), the Array intrinsic::slice
fixed method has type
function (start: AnyNumber, end: AnyNumber): Array
* Users opting into early binding use this pragma:
use namespace intrinsic;
at the top of a program unit or block. This opens the intrinsic
namespace so that the name slice in an expression a.slice(i, j),
where a has type Array, will resolve as a.intrinsic::slice(i, j).
* Independent from the intrinsic namespace (and optional for
implementations to support or treat as standard mode), but useful as
well, one can say:
use strict;
to run a type checker that also does basic lint-like name-binding and
other sanity checking.
There's still some debate over how much analysis strict mode should
do, but this mode is completely optional at the ES4 implementation's
discretion -- no need for it on cell phones. The only run-time effect
of strict mode is a change to eval, to prevent it from using var,
const, or function against its caller's variable object, while
allowing let. This prevents eval from clobbering caller bindings
(function can replace an existing binding in ES3), and it avoids
injection of novel eval-created bindings into the dynamic scope.
The model for strict mode other than the lint-like sanity checks is
partial evaluation, so there is no difference other than the above
eval restriction in runtime semantics if a program makes it past
strict mode, compared to runtime semantics in standard mode. Thus
strict mode simply prevents certain programs that might run (possibly
even run correctly) from reaching run-time.
* It isn't just prototype methods that have intrinsic-qualified fixed
method counterparts: class static methods such as Date.parse, the
Math object's methods, and familiar top-level functions such as
parseInt and hashcode, also have intrinsic counterparts. So too do
the built-in operator generic functions (a.k.a. multimethods), e.g.
intrinsic::=== which goes with intrinsic::hashcode like peas and
carrots: see the Map proposal at http://wiki.ecmascript.org/doku.php?
id=proposals:dictionary.
The benefits include faster method lookups in lightweight
implementations, better type error detection at compile time thanks
to the more precise intrinsic function types, and of course the main
point Graydon made: integrity. You can be sure nothing has replaced a
standard library binding in the mutable global object or a standard
prototype.
Beyond intrinsic, as I mentioned previously, there are tools for
library authors to tame the global object and other mutable object
hazards in the language:
* const and block scope (let) provide immutable bindings and block-
wise isolation/shadowing.
* The global properties Object, Array, etc. are constant in ES4.
* Type annotations on function definitions constrain the binding of
the function name to have a compatible type.
* const function can be used to prevent the value as well as the type
of the binding from being changed later.
* And of course, the static type checker available if an
implementation supports 'use strict' can find type errors, which can
point to security holes.
Verification is a linchpin for further analyses that I expect to be
researched (hybrid information flow, e.g.) on top of ES4, and where
the research yields solid results, we hope to incorporate that
knowledge into future editions of the standard. Reasoning about ES4
code is thus easier, or even possible in the first place, compared to
ES3 code which requires conservative static analysis or else hybrid
static and dynamic (runtime instrumentation) techniques.
All of these facilities were added for several good reasons,
verification but also integrity among them. This was done while
preserving backward compatibility of ye olde mutable global object
and standard prototypes. It may be that these facilities are simply
not well understood. I hope it's clear now that they address, as best
those of us working together in TG1 know how, the hazards of the
mutable global object -- while not breaking the web. Any questions?
/be
More information about the Es4-discuss
mailing list