Jul 4, 2013 - Direct Proxies talk at ECOOP

Yesterday I gave a talk at the European Conference on Object-oriented Programming (ECOOP) on our paper on direct proxies in JavaScript. The slides are available online. The talk is basically recounting the story of how Mark and I refactored the original JavaScript Proxy API into the current direct proxies API because of the interaction between proxies and frozen objects. I have previously written about that interaction on my blog. The paper further focuses on membranes as a case study for proxies.

May 22, 2013 - Notification Proxies

Comments

At the May TC39 meeting I presented [pdf] an overview of Notification Proxies, as a possible alternative to Direct Proxies. This post briefly summarizes my talk, and the feedback I got from the committee. tl;dr: notification proxies are off the table, we’re sticking to direct proxies in ES6.

Notification Proxies were first proposed on the es-discuss mailing list by E. Dean Tribble as a way to circumvent the invariant checks required by Direct Proxies to ensure that proxies don’t violate the general JavaScript object invariants. Notification Proxies are simpler and easier to specify than Direct Proxies. The key idea behind notification proxies is that they turn traps into event notification callbacks, which can react to operations intercepted on the proxy, but cannot directly affect the outcome of the intercepted operation. Instead, the intercepted operation is always forwarded to the wrapped target object. Since the trap is still invoked before the intercepted operation is forwarded, the proxy handler can still update the target object before forwarding, so it can still indirectly influence the outcome of the intercepted operation.

Here’s a simple logger proxy that logs the outcome of all property accesses, written using direct proxies:

var target = {};
var handler = {
  get: function(target, name, receiver) {
    console.log("getting: " + name);
    var result = Reflect.get(target, name, receiver);
    console.log("got: " + result);
    return result;
  }
};
var proxy = new Proxy(target,handler);

And here is the same example, implemented using notification proxies:

var target = {};
var handler = {
  onGet: function(target, name, receiver) {
    console.log("getting: " + name);
    return function(target, name, receiver, result) {
      console.log("got: " + result);
    }
  }
};
var proxy = new Proxy(target,handler);

The onGet method is called when a property access is intercepted on the proxy, e.g. proxy.x. I renamed the get trap to onGet to make it clear that the trap is now a simple callback that doesn’t compute and return a result. Second, the onGet trap no longer manually forwards the operation and manually returns the result. The forwarding happens automatically. The onGet trap does get to return a function that will act as a “post-trap”: a hook that will be called after the operation was forwarded to the target. This “post-trap” accepts the same arguments as the onGet “pre-trap” except that it additionally takes the outcome of the operation. In the above example, when intercepting proxy.x, result will be whatever value was returned from target.x. The post-trap can inspect the result (and if the result is a mutable object, it could still mutate it), but it cannot change the outcome of the intercepted operation anymore. That is to say, proxy.x will return result regardless of what the post-trap does.

Because the notification proxy always returns values that were retrieved directly from the target object, rather than provided by the handler trap (as in the case for direct proxies), the notification proxy doesn’t need to verify whether the handler’s return value is consistent with the target object’s invariants. For example, if target is frozen, then target.x is a non-configurable, non-writable property. In that case, proxy.x must consistently return the same value that target.x denotes. With notification proxies, this is trivially enforced without any explicit assertions.

Direct and Notification Proxy Polyfills

I previously implemented direct proxies on top of original harmony:proxies so that one can already experiment with direct proxies on platforms that implement the original proxies, but not yet direct proxies (at the time of writing that would be Chrome and node.js. Firefox provides both original and direct proxies.). To similarly test notification proxies, I did a similar implementation of notification proxies on top of original proxies. Interestingly, the proxy handler logic for notification proxies took 850 LoC, compared to 312 LoC for notification proxies, testifying to the simplicity of notification proxies.

Implementing Membranes with Direct Proxies

To further compare notification proxies with direct proxies, I implemented the membrane abstraction using both. The goal of a membrane is to isolate two object graphs. Membranes serve as a “litmus test” for the expressiveness of a Proxy API, as they are quite tricky to implement: the membrane must keep the wrapped object graphs isomorphic, and must maintain invariants across the membrane, i.e. a frozen object inside the membrane must be represented as a frozen proxy object outside of the membrane.

Implementing membranes with direct proxies is relatively straightforward as long as you don’t care about invariants (i.e. frozen objects). This is the “try 1” approach from the slides. The basic idea is that a membrane proxy, in each of its intercepted operations, forwards the operation to its target object on the other side of the membrane by wrapping all non-primitive arguments to the operation and unwrapping the result. Note that in the slides, I use the convention of prefixing variables with a “wet” or “dry” prefix to describe whether they refer to values inside or outside of the membrane.

The straightforward membrane implementation of membranes with direct proxies breaks down when the direct proxy wraps a frozen object. In that case, the direct proxy invariant checks will prevent the proxy from returning wrappers for frozen properties, to guarantee immutability. To make matters more concrete, consider the following setup:

var wetB = {};
var wetA = Object.freeze({ x: wetB });
var dryA = wet2dry(wetA);
var dryB = dryA.x;

Visualised:

membranes and frozen objects</src>

However, when executing the above code, a problem occurs when accessing dryA.x:

dryA.x
// TypeError: cannot report inconsistent value for non-writable, non-configurable property ‘x’

What is going on here? Because wetA is frozen, wetA.x is a so-called “non-configurable non-writable” property. This means that wetA.x will forever refer to the wetB object. The dryA direct proxy for wetA will not allow the proxy to return any other object than wetB from its get trap for the “x” property. However, the membrane returns a wrapper (a proxy) for wetB, which causes the assertion inside the proxy to fail, resulting in the above TypeError.

The necessary workaround is to have the direct proxy wrap a dummy, “shadow target” that will store wrapped properties of the “real” target. The basic idea is that, when a frozen (immutable) property is accessed, the proxy handler defines a wrapped version of the frozen property on its shadow and returns the wrapped frozen property. The direct proxy will then find the same wrapped frozen property on the shadow target, so the assertion succeeds. The “shadow target” is the target object that the direct proxy refers to directly. The direct proxy doesn’t know about the “real target” directly. Only the proxy handler holds onto a reference to the “real target”. The technique is described in full in this paper (section 4).

Applying the shadow target technique to membranes, the key idea (due to Mark S. Miller) is to have the shadow and the real target sit on opposite sides of the membrane. For instance, for a dry-to-wet proxy, the “real target” is “wet” (i.e. inside the membrane), while the “shadow target” is dry (i.e. outside the membrane). Whenever the dry-to-wet membrane proxy intercepts an operation, it retrieves the wet target’s property and wraps it, defining a dry equivalent property on the shadow. Afterwards, it just forwards the intercepted operation to the dry shadow, which will at that point be correctly initialized.

membranes and shadow targets</src>

One “optimization” that membranes implemented using direct proxies can perform is to test whether the target object is frozen and if not, use the simple “try 1” approach of forwarding the operation to the target directly. If the target is frozen, the membrane can fall back on using the shadow target to define the wrapped property. In other words: as long as the target object is not actually frozen, the membrane does not need to copy properties onto the shadow target.

Here’s the full implementation of membranes using direct proxies.

Implementing Membranes with Notification Proxies

Implementing membranes using notification proxies is similar to implementing membranes using direct proxies. Just like direct proxies, notification proxies must make use of the “shadow target” technique. The big difference is that while direct proxies must use this technique only when dealing with frozen objects, notification proxies must always use this technique, even for non-frozen objects. This is because the notification proxy will always forward any intercepted operation to its (shadow) target, regardless of invariants, so the notification proxy handler must always make sure to “synchronize” the state of its real and shadow targets in the pre-trap.

Here’s the full implementation of membranes using notification proxies.

The conclusion of my little membrane experiment is that both direct and notification proxies can express membranes. Comparing lines of code, the direct proxy membrane implementation weighs in at 470 LoC, versus 402 LoC for notification proxy membranes. The direct proxy implementation does perform the optimization that if the target is not frozen, the shadow target is not consulted. The notification proxy implementation naively always updates the shadow for each intercepted operation. That explains the difference in LoC. In terms of overall complexity of the membrane implementation, I would say that direct and notification proxies are on-par.

Micro-benchmarks

In order to get some indication of the relative performance difference between direct and notification proxies, I ran some micro-benchmarks.

The basic setup is that we create a large data structure (a large array, and a large binary tree), which then gets wrapped in a membrane. The micro-benchmark then measures the time taken to traverse this large data structure from outside of the membrane. This requires each individual array element or tree node to cross the membrane. I ran these micro-benchmarks both for the case where the data structure is frozen (i.e. has strong invariants) vs. non-frozen (i.e. has no invariants). This matters because of the previously described “optimization” that direct proxies can do when they’re wrapping non-frozen objects.

I ran the benchmarks in the two browsers that currently provide support for proxies and weak maps: Firefox and Chrome. I believe this comparison between direct and notification proxies is apples-to-apples in that both are self-hosted implementations in JavaScript. Note that I am only looking at relative perf. I’m not interested in absolute numbers because my reflect.js shim is adding too much noise. Built-in proxy implementations will be orders-of-magnitude faster. Similarly I’m not interested in the difference in timing between Firefox and Chrome, or between frozen and non-frozen objects. I’m only interested in the relative perf difference from running the same traversal, in the same browser, with direct vs notification proxies.

As the results in the slide deck show, the results are very inconclusive. From these results one cannot say whether one API is faster than the other. My gut feeling is that either API can probably be made efficient. The key point is that notification proxies must always use the shadow target technique when they’re implementing “virtual object” wrappers such as membranes.

Summary

Eventually, TC39 decided to stick with direct proxies, for two (good) reasons. The first is that notification proxies, while they are simpler and easier to specify, put more burden on Proxy users because they require Proxy users to use the (admittedly complex) “shadow target” technique for all virtual object use cases. In other words, they make life easier for the spec but not necessarily for the developer. That’s optimizing for the wrong audience.

Notification proxies were also motivated by a fear that the invariant checks for direct proxies are hard to get right, and easy to overlook, thus allowing attackers to create proxy objects that deliberately violate JavaScript’s invariants to confuse other scripts. Such bugs in the specification have come up before. The counter-argument that was made is that such spec loopholes can be closed, and implementations can issue patches swiftly when security is at stake.

All in all, I think Notification Proxies provide an attractive, simpler alternative to Direct Proxies given JavaScript’s fairly complex object model. That said, the need to use the “shadow target” technique to express certain abstractions such as membranes or higher-order contracts, even for non-frozen objects, is pretty tedious. With three proxy designs now worked out in detail (original proxies, direct proxies and notification proxies) we have a solid overview of the design space. Language design always entails trade-offs, so every API will always have use cases that it supports better than the others. Overall though, I’m happy to settle on the direct proxies API.

Jan 21, 2013 - Distributed Resilient Secure ECMAScript (Dr. SES)

Comments

This post is about a project I’ve been working on together with Mark Miller (with the help of many others) which we call “Dr. SES”, for Distributed Resilient Secure ECMAScript”. Dr. SES is a JavaScript extension aimed at secure, distributed computing.

Dr. SES extends JavaScript with three features:

  • SES: Dr. SES builds upon SES, the capability-secure subset of JavaScript. SES enables the safe execution of mobile code.
  • Q: To engage in distributed computing, we need abstractions to send messages to remote objects, and the Promise abstraction to more easily manage asynchronous computations. Mark has previously proposed some extensions to ECMAScript to achieve precisely this. Luckily we do not have to wait for these extensions to land. Except for some syntactic sugar, we can get most of the functionality today via Kris Kowal’s Q library.
  • Ken: Ken is a middleware that provides a persistent heap and reliable messaging between such persistent heaps. By integrating Ken with an existing JavaScript engine, we can achieve a JavaScript platform that persists across failures. One of my students is currently working on integrating Ken with the v8 engine, leading to an engine we call v8-ken.

Mark recently got invited as a speaker at ESOP 2013, and was given the opportunity to publish a paper to go along with the talk. The paper is probably the first written exposition of Dr. SES to date. It got published only recently, here’s a link to the pre-published version. The subject of the paper is not really about Dr. SES, but rather about what type of applications we can achieve on top of a platform such as Dr. SES. Nevertheless, Section 2 provides a good background on what Dr. SES ought to become. The paper is really a progress report, as Dr. SES is still very much a work in progress. Quoting the abstract of the paper:

Contracts enable mutually suspicious parties to cooperate safely through the exchange of rights. Smart contracts are programs whose behavior enforces the terms of the contract. This paper shows how such contracts can be specified elegantly and executed safely, given an appropriate distributed, secure, persistent, and ubiquitous computational fabric. JavaScript provides the ubiquity but must be significantly extended to deal with the other aspects. The first part of this paper is a progress report on our efforts to turn JavaScript into this fabric. To demonstrate the suitability of this design, we describe an escrow exchange contract implemented in 42 lines of JavaScript code.

The paper is based on two earlier talks of Mark:

  • Bringing Object-orientation to Security Programming (video, slides), which provides a good introduction to object-capabilities and the Purse example in particular.
  • Two-phase commit among strangers (slides, audio), which provides a general overview of Dr. SES (with a particular focus on Q) and of the escrow exchange contract, which is discussed in detail in the paper.

The source code discussed in the paper is available.

Jun 14, 2012 - A brief tour of JavaScript's object model

Comments

In this post I will give an overview of Javascript’s object model. To a first approximation, by “object model”, I mean the mental model that a developer has of an object’s structure and behavior. At the end, I will hint at how proxies can be used to implement (variations on) this object model in Javascript itself.

Objects as Maps

Let’s start with the simplest possible model most JS developers have of a Javascript object, and then refine as we go along. At its core, a Javascript object is nothing but a flexible bag of properties, which we can think of as a mapping from strings to values:

var point = {
  x: 0,
  y: 0
};

The point object maps the strings “x” and “y” to the value 0.

Javascript has a fairly large set of operations that one can apply to such maps, including the five basic operations applicable to most generic associative containers: property lookup (e.g. point.x), property addition (e.g. point.z = 0), property update (e.g. point.x = 1), property deletion (e.g. delete point.x;) and property query (e.g. “x” in point).

Method Properties

One of the beautiful parts of Javascript, I think, is that support for object “methods” doesn’t really require a change to this simple object model: in Javascript, a method is really nothing more than a property whose value happens to be a function. This is reflected in the syntax:

var point = {
  x: 0,
  y: 0,
  toString: function() { return this.x + "," + this.y; }
};

The method invocation point.toString() is essentially a lookup of the “toString” property in the object, followed by a function call that sets this to the receiver object and passes in the arguments: (point.toString).apply(point, []). So, we don’t need to adjust our object model to take methods and method invocations into account.

Prototype inheritance

The above object model is not complete: we know that Javascript objects also have a special “prototype” link that points to a parent object, from which the object may inherit additional properties. Objects created using the object literal syntax {…} inherit from the built-in Object.prototype object by default. For instance, our point object inherits from this object the built-in method hasOwnProperty, which allows us to ask whether the object defines a certain property:

point.hasOwnProperty("x") // true

The prototype link of an object can be obtained by calling Object.getPrototypeOf(point), although many browsers also simply represent the prototype link as a regular property of the object with the funny name proto.

I don’t like to think of the prototype link as a normal property because this link has a large influence on virtually every operation applied to a Javascript object. The prototype link is really special, and setting point.proto to another object has a very large effect on the subsequent behavior of the point object. So, I’d like to think of the prototype link as the next addition to our object model: a javascript object = a map of normal properties + a special prototype link.

Property attributes

The object model just described (objects as maps of strings -> values + a prototype link) is sufficiently accurate to describe user-defined objects in Ecmascript 3. However, Ecmascript 5 extends the Javascript object model with a number of new features, most notably property attributes, non-extensible objects and accessor properties. John Resig has a nice blog post on the subject. I’ll only briefly summarize the most salient features here.

Let’s start with property attributes. Basically, in ES5, every Javascript property is associated with three attributes, which are simply boolean flags indicating whether the property is:

  • writable: the property can be updated
  • enumerable: the property shows up in for-in loops
  • configurable: the property’s attributes can be updated

Property attributes can be queried and updated using the wordy ES5 built-ins Object.getOwnPropertyDescriptor and Object.defineProperty. For example:

Object.getOwnPropertyDescriptor(point, "x")
// returns {
//   value: 0,
//   writable: true,
//   enumerable: true,
//   configurable: true }
Object.defineProperty(point, "x", {
  value: 1,
  writable: false,
  enumerable: false,
  configurable: false
});

This turns “x” into a non-writable, non-configurable property, which is basically like a “final” field in Java. The objects returned by getOwnPropertyDescriptor and passed into defineProperty are collectively called property descriptors, since they describe the properties of objects.

So, in ES5, we need to adjust our object model so that Javascript objects are no longer simple mappings from strings to values but rather from strings to property descriptors.

Non-extensible objects

ES5 adds the ability to make objects non-extensible: after calling Object.preventExtensions(point), any attempt to add non-existent properties to the point object will fail. This is sometimes useful to protect objects used as external interfaces from inadvertent modifications by client objects. Once an object is made non-extensible, it will forever remain non-extensible.

To model extensibility, we need to extend the Javascript object model with a boolean flag that models whether or not the object is extensible. So now, a Javascript object = a mapping from strings to property descriptors + a prototype link + an extensibility flag.

Accessor properties

ES5 standardized the notion of “getters” and “setters”, i.e. computed properties. For instance, a point whose “y” coordinate is always equal to its “x” coordinate can be defined as follows:

function makeDiagonalPoint(x) {
  return {
    get x() { return x; },
    set x(v) { x = v; },
    get y() { return x; },
    set y(v) { x = v; }
  };
};

var dpoint = makeDiagonalPoint(0);
dpoint.x // 0
dpoint.y = 1
dpoint.x // 1

ES5 calls properties implemented using getters/setters accessor properties. By contrast, regular properties are called data properties. This point object has two accessor properties “x” and “y”. The property descriptor for an accessor property has a slightly different layout than that for a data property:

Object.getOwnPropertyDescriptor(dpoint, "x")
// returns {
//   get: function() { return x; },
//   set: function(v) { x = v; },
//   enumerable: true,
//   configurable: true
// }

Accessor property descriptors don’t have “value” and “writable” attributes. Instead, they have “get” and “set” attributes pointing to the respective getter/setter functions that are invoked when the property is accessed/updated. If a getter/setter function is missing, the corresponding “get”/”set” attribute is set to undefined. Accessing/updating an accessor property with an undefined getter/setter fails.

To accommodate accessor properties, we don’t need to further extend our object model, other than by noting that a Javascript object maps strings to property descriptors, which can now be either data or accessor property descriptors.

So, in conclusion, a Javascript object =

  • a mapping of strings to data or accessor property descriptors
    • a prototype link
    • an extensibility flag

This is an accurate model of user-defined ES5 objects.

Implementing your own objects

The object model described above can be thought of as the interface to a Javascript object. With the introduction of proxies in ES6, Javascript developers actually gain the power to implement this interface, and define their own objects. Not only could you emulate Javascript’s default object model in Javascript, you could define small variations to explore different object models.

Examples of such variations include Javascript objects with lazy property initialization, non-extractable or bound-only methods, objects with infinite properties, objects with multiple prototype links supporting multiple-inheritance, etc. There’s already some inspiration to be had on github, thanks to David Bruant and Brandon Benvie.

I recently gave a talk at ECOOP that explores these ideas in more detail. The talk builds up by describing Javascript’s object model, ties this up to the literature on meta-object protocols, and finally shows how Javascript’s Proxy API makes the Javascript meta-object protocol explicit for the first time, allowing developers to implement their own objects. Prototype implementations of proxies are currently available in Firefox and Chrome. To experiment with the latest version of the specified ES6 Proxy API, I recommend using the reflect.js shim.

May 7, 2012 - Proxies and Frozen Objects

Comments

This post is about how frozen Javascript objects interact with the upcoming Proxy API.

Since ECMAScript 5th edition, Javascript objects can be frozen. John Resig has a good blog post on the matter. To quote from his post:

Freezing an object is the ultimate form of lock-down. Once an object has been frozen it cannot be unfrozen - nor can it be tampered in any manner. This is the best way to make sure that your objects will stay exactly as you left them, indefinitely.

Once you call Object.freeze(obj) on an object obj, you have basically established two invariants on that object, i.e. properties of the object that will hold forever after:

  • The object becomes non-extensible: once frozen, you (or anyone else) can’t add any more properties to the object, ever.
  • All properties of the object become non-configurable: they can no longer be deleted, updated or have their attributes (like enumerability) changed in any way, ever.

I should note at this point that ECMAScript 5 has additional primitives to apply these invariants to just a subset of an object’s properties (Object.defineProperty), and also introduces the weaker notions of “non-extensible” and “sealed” objects (Object.preventExtensions, Object.seal). See John’s post for more details. Freezing an object is simply the most extreme type of lock-down you can apply to an object, and consequently involves the strongest invariants, so we’ll only focus on frozen objects here.

If you can establish that an object is frozen, i.e. Object.isFrozen(obj) === true, then you can count on the above invariants. For instance, if obj.x isn’t a getter (an “accessor property”), then you can count on the fact that obj.x is effectively a “constant” property: you can read its current value and cache it, knowing the cached value will always be correct.

With the introduction of proxies, an obvious question is whether and how proxies affect these invariants. Can proxies masquerade as frozen objects? And if yes, can they violate the associated invariants? The short answers are yes, and no.

Proxies: brief recap

I wrote earlier about how the Proxy API has undergone several revisions. Here, I’ll briefly introduce the latest revision of the API, called “direct proxies”. The Proxy API allows you to create a proxy for an object (which we will consistently refer to as the proxy’s “target”), as follows:

1
2
3
var target = {x:0,y:0}; // some existing object
var handler = {}; // the handler's methods are called "traps"
var proxy = Proxy(target, handler);

Here, proxy is a special proxy object that can intercept a number of Javascript operations. For instance, the in operator, when applied to a proxy: “x” in proxy, will trigger a corresponding “trap” in the proxy handler, and calls handler.has(target, “x”), which is supposed to return a boolean indicating whether the proxy has a property named “x”. If the trap is undefined, as in the above example, the proxy by default delegates the operation to the target, i.e. “x” in proxy is then interpreted as “x” in target.

Below is a schematic representation that shows the relationship between a proxy, a target and its handler. Proxies can intercept many operations, such as property access and assignment, property lookup, enumeration, and so on. On the left, we show a couple of operations applied to the proxy, and how the proxy interprets them by dispatching to the handler.

Proxy API</img>

In short, the proxy can fully customize the way it advertises its own properties to clients. In general, a proxy is not even obliged to return results that are consistent with its own target:

1
2
3
4
handler.has = function(target, name) { return false; }
"x" in target // returns true
"x" in proxy // returns false
proxy.x // returns 0

In the extreme, one can even create a proxy that totally ignores its target object for all operations. This is sometimes useful, e.g. the target could just be a dummy placeholder, and the proxy might represent a remote object (on the server or in a different web worker) or an object stored in a remote database.

Frozen Proxies

Back to frozen objects and proxies. Is it possible to create a frozen proxy? Yes: just create a proxy whose target is frozen:

1
2
3
4
var target = Object.freeze({ x: 0 });
var handler = {};
var proxy = Proxy(target, handler);
Object.isFrozen(proxy) // true!

However, now we’re in a potentially dangerous situation: depending on how the handler implements this proxy’s traps, it may return results that are inconsistent with its target, and may thus potentially violate the invariants of frozen objects. To prevent this, the Proxy API includes an invariant enforcement mechanism, which is simply a series of assertions on the behavior of a proxy handler. If one of the proxy handler’s traps returns a result that is inconsistent with the fact that its target is frozen, an assertion will fail and cause an exception to be thrown, thus notifying clients of the proxy’s misbehavior.

Continuing with the previous example, assume a client of the proxy observes the “x” property:

proxy.x // 0

The handler does not define a “get” trap, so the proxy simply forwards the property access to the (frozen) target, and returns 0. Now, assume that the handler is updated as follows:

handler.get = function(target, name) {
  return 1;
}

Let’s see what happens when we try to access properties on the proxy:

proxy.foo // returns 1, so far so good...
proxy.x // TypeError: cannot report inconsistent value for non-writable, non-configurable property 'x' 

When you encounter such an exception, what Javascript is really telling you is:

Son, this proxy is writing cheques its target can’t cash!

Seriously: proxy.x fails because the proxy knows that target is frozen, and has a property named “x” which is bound to the value 0. It cannot now let the proxy claim that proxy.x is 1, while it returned 0 just a while ago. After all, the proxy is supposedly frozen, and frozen objects wouldn’t exhibit such behavior. In this example, the “get” trap always returns 1, which on the surface seems OK for frozen objects, but in general the “get” trap may return a different value each time it’s called. The proxy instead detects such inconsistencies, and throws.

Since frozen objects are supposedly non-extensible, why is the proxy allowed to report a value for a “foo” property, while the frozen target doesn’t have such a property? The answer has to do with prototypal inheritance: a frozen object can still inherit from a non-frozen object, which may define a “foo”:

1
2
3
4
5
6
var parent = {} ; // note: parent isn't frozen
var child = Object.create(parent); // child inherits from parent
Object.freeze(child);
child.foo // undefined
parent.foo = 1;
child.foo // 1

In the above example, the expression child.foo returned different values even though child is frozen. This is why the above proxy was allowed to return a value for “foo”, even though that property wasn’t defined on the target. The proxy won’t verify whether the target actually inherits from a non-frozen object that has a “foo”, but just assumes that this is possible.

Matters are different if the proxy actually tries to advertise “foo” as an “own” property (i.e. a property defined on the proxy itself, not inherited from some other object):

1
2
3
4
Object.keys(proxy);  // [ "x" ]
handler.keys = function(target) { return [ "x", "foo" ]; };
Object.keys(target); // [ "x" ]
Object.keys(proxy)  // TypeError: keys trap cannot list a new property 'foo' on a non-extensible object

What’s going on here? Object.keys, new since ES5, lists all of an object’s own, enumerable properties. When applied to the proxy, it invokes the handler’s “keys” trap, which should return an array of strings. On line 1, the proxy handler doesn’t yet define a “keys” trap, so just forwards the call to Object.keys to the target, returning the array [“x”]. On line 2, the handler installs a “keys” trap that claims both “x” and “foo” as own properties of the (frozen) proxy. On line 4, the program throws an exception. It would be wrong for Object.keys(proxy) on line 4 to return “foo” in the list of own properties since doing so would violate the invariants of frozen objects. After all, on line 1, the “foo” property was not present, and proxy was supposedly frozen, so clients would be very surprised if later, they noticed proxy had a new property!

Try it yourself

At the time of writing, direct proxies are being prototyped in Firefox. I wrote a little shim that already implements the most recent API in terms of an older revision that is already available since Firefox 4. Just include this script in your page and you’re ready to play around with direct proxies (tested in Firefox 12). This is work-in-progress, so any feedback is more than welcome.