[Harmony Proxies] Non-extensible, sealed and frozen Proxies
Tom Van Cutsem
tomvc.be at gmail.com
Thu Sep 1 08:40:12 PDT 2011
I promised to give an update on the work I've been doing to investigate to
what extent the Proxy API may support non-extensible objects.
In a nutshell: I now think it is possible for Proxies to emulate not just
non-extensible objects, but also sealed and frozen objects, given
appropriate invariant checks.
Before I continue, a brief history of how we got here:
<history>
When Mark and I originally designed the Proxy API, we took care that proxies
could not violate a number of invariants of ES5.1 objects, related to
non-configurable properties and object extensibility (cf. ES5.1 section
8.6.2).
The net result was that:
a) proxies could not emulate non-configurable properties (i.e. all property
descriptors returned by the get{Own}PropertyDescriptor trap must be
configurable:true)
b) Object.{preventExtensions,seal,freeze} would "fix" a proxy, after which
it could no longer trap.
While b) seemed a reasonable restriction, a) was controversial and discussed
both during earlier TC39 meetings and here on es-discuss (see a.o. <
https://mail.mozilla.org/pipermail/es-discuss/2011-June/015114.html>). That
discussion revealed that proxies really need to be able to emulate
non-configurable properties. One motivating use case was emulating Array's
non-configurable |length| property.
To alleviate restriction a), Mark and I looked for alternatives, recorded as
successive strawmen at <
http://wiki.ecmascript.org/doku.php?id=strawman:fixed_properties>.
A first strawman (the "short-circuiting approach" on the wiki page) was
based on having a proxy "cache" non-configurable properties, such that if
the proxy hit the cache, it would no longer trap the handler. That design
proved to be flawed, for several reasons, as pointed out by David Bruant,
IIRC.
Out of that discussion grew a second approach (the "trap-and-enforce
approach" on the wiki page). In that approach, the handler can fully emulate
non-configurable properties, but the handler is "monitored" for invariant
violations. Because FF4+ proxies support emulation of non-configurable
properties (without invariant checking), I was able to implement such a
"monitoring" proxy handler in Javascript itself. That was the FixedHandler I
proposed earlier: <
https://mail.mozilla.org/pipermail/es-discuss/2011-July/015686.html>.
That approach was well received, but it did not alleviate restriction b)
(see <https://mail.mozilla.org/pipermail/es-discuss/2011-July/015944.html>).
IIRC, the motivation here was again Array: emulating the magical |length|
property even if the Array is made non-extensible.
</history>
Over the past few weeks I have been working on an extension of the
FixedHandler that allows proxies to additionally emulate non-extensible
objects. These proxies keep track of previously exposed non-configurable
properties (as in the earlier FixedHandler design), but additionally have an
"isExtensible" flag. Once a proxy is fixed, the flag is set to false. Via
the return value of the fix() trap, the proxy knows the set of own property
names of the non-extensible object it needs to emulate. It then becomes easy
for e.g. the getOwnPropertyDescriptor trap to check that |undefined| is
returned for any |name| that is not in the set of own properties. Combined
with a number of other checks in a handful of other traps, that is how the
ES5.1 invariants w.r.t. [[Extensible]] are enforced.
As it turns out, once a proxy can emulate both non-configurable properties
and non-extensible objects, the ability to emulate sealed and frozen objects
falls out naturally.
The full implementation of such "non-extensible proxies" is here: <
http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/FixedTrappingProxy.js
>
The file's comments near the top contain a detailed description of the
invariants enforced by this type of proxy.
I (lightly) tested this implementation on FF6 (the implementation depends on
both proxies and WeakMaps). For those interested:
console-based test: <
http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.js
>
browser-based test: <
http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.html
>
Given the intricacy of the invariant checks, this code can use more
eyeballs. There's also a number of unresolved TODO's that merit discussion,
and some hints on a redesign of the fix() protocol. In particular, Mark and
I have been discussing a protocol where a handler can _either_ tell the
proxy that it wants to continue trapping, even after being fixed, or that it
wants to "become" a regular object as before, with no more overhead for
invariant checks. I think this gives handler writers the "best of both
worlds" in terms of flexibility and performance.
On a final note: while implementing this I realized that the FixedHandler is
a partial self-hosted implementation of the Proxy API in Javascript. That
prompted me to go "all the way" and additionally make the normalization on
the input arguments and return value of traps explicit, even though that
would not have been strictly necessary. My hope is that people may get a
better understanding of the interaction between proxies and handlers if they
can study a self-hosted implementation of the Proxy API.
Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110901/d7ad0adc/attachment.html>
More information about the es-discuss
mailing list