Proxies invalidate one fundamental assumption of the current ES spec,
namely that (most) internal methods are effectively pure. That has a
couple of consequences which the current proxy proposal and semantics
seem to ignore, but which we need to address.


OBSERVABILITY & EFFICIENCY

In ES5, internal methods essentially are an implementation detail of
the spec. AFAICS, there is no way their interaction is actually
observable in user code. This gives JS implementations significant
leeway in implementing objects (and they make use of it).

This changes drastically with proxies. In particular, since most
internal methods may now invoke traps directly or indirectly, we can
suddenly observe many internal steps of property lookup and similar
operations through potential side effects of these traps. (Previously,
only the invocation of getters or setters was observable).

Take the following simple example:

----------------
var desc = {configurable: true, get: function() {return 8}, set:
function() {return true}}
var handler = {getPropertyDescriptor: function() {seq += "G"; return desc}}
var p = Proxy.create(handler)
var o = Object.create(p)

var seq = ""
o.x
var seq1 = seq
seq = ""
o.x = 0
var seq2 = seq
----------------

According to the proxy spec, we should see seq1=="G" and seq2=="GG".
In my local version of V8, I currently see seq1=="G" and seq2=="G".
In Firefox 7, I see seq1=="GG" and seq2=="GG".

Obviously, both implementations are unfaithful to the spec, albeit in
reverse ways. At least for V8, implementing the correct behaviour may
require significant changes.

Also, I wonder whether the current semantics forcing seq2=="GG" really
is what we want, given that it is unnecessarily inefficient (note that
it also involves converting the property descriptor twice, which in
turn can spawn numerous calls into user code). Optimizing this would
require purity analysis on trap functions, which seems difficult in
general.


HIDDEN ASSUMPTIONS

In a number of places, the ES5 spec makes hidden assumptions about the
purity of internal method calls, and derives certain invariants from
that, which break with proxies.

For example, in the spec of [[Put]] (8.12.5), step 5.a asserts that
desc.[[Set]] cannot be undefined. That is true in ES5, but no longer
with proxies. Unsurprisingly, both Firefox and V8 do funny things for
the following example:

----------------
var handler = {
  getPropertyDescriptor: function() {
    Object.defineProperty(o, "x", {get: function() { return 5 }})
    return {set: function() {}}
  }
}
var p = Proxy.create(handler)
var o = Object.create(p)
o.x = 4
----------------

Firefox 7: InternalError on line 1: too much recursion
V8: TypeError: Trap #<error> of proxy handler #<Object> returned
non-configurable descriptor for property x

More generally, there is no guarantee anymore that the result of
[[CanPut]] in step 1 of [[Put]] is in any way consistent with what we
see in later steps. In this light (and due to the efficiency reasons I
mentioned earlier), we might want to consider rethinking the
CanPut/Put split.

This is just one case. There may be other problematic places in other
operations. Most of them are probably more subtle, i.e. the spec still
prescribes some behaviour, but that does not necessarily make any
sense for certain cases (and would be hard to implement to the
letter). We probably need to check the whole spec very carefully.


FIXING PROXIES

A particularly worrisome side effect is fixing a proxy. The proxy
semantics contains a lot of places saying "If O is a trapping proxy,
do steps I-J." However, there generally is no guarantee that O remains
a trapping proxy through all of I-J!

Again, an example:

----------------
var handler = {
  get set() { Object.freeze(p); return undefined },
  fix: function() { return {} }
}
var p = Proxy.create(handler)
p.x
----------------

Firefox 7: TypeError on line 1: getPropertyDescriptor is not a function
V8: TypeError: Object #<Object> has no method 'getPropertyDescriptor'

The current proxy semantics has an (informal) restriction forbidding
reentrant fixing of the same object, but that is only a very special
case of the broader problem. Firefox 7 rejects fixing a proxy while
one of (most) its traps is executing (this seems to be a recent
change, and the above case probably is an oversight). But it is not
clear to me what the exact semantics is there, and whether it is
enough as a restriction. V8 currently even crashes on a few contorted
examples.

****

In summary, I'm slightly worried. The above all seems fixable, but is
that all? Ideally, I'd like to see a more thorough analysis of how the
addition of proxies affects properties of the language and its spec.
But given the state of the ES spec, that is probably too much to wish
for... :)

/Andreas
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to