Catch-all proposal based on proxies
brendan at mozilla.com
Thu Dec 10 13:21:28 PST 2009
On Dec 9, 2009, at 9:48 PM, Mark S. Miller wrote:
> In general, handler writers have to implement standard prototype-
> based delegation if it is desired. This is probably the right thing,
> but I wonder if you considered the alternative where prototype
> delegation is handled "by the spec" or "by the runtime" and the
> proxy is considered "flat"?
> We did think about it, but it seemed needlessly less flexible. If
> such flat-and-delegate handling is desired, an abstraction can be
> built on top of ours that emulates it as a convenience. The reverse
> emulation seems difficult at best.
Agreed, good point.
> Since the undefined may be the result of a bug as you say, it seems
> worse for the bug to silently result in fixing the proxy into an
> empty frozen object. We think the current "noisy" behavior better
> supports defensive programming.
Of course, the fix handler could throw and that would propagate too
but it's more concise to return undefined (or fall off the end? Still
a bit too implicit for my taste, but never mind). Agreed.
> In answer, <http://wiki.ecmascript.org/doku.php?id=strawman:proxies#an_identity-preserving_membrane
> > preserves === correspondence on each side of a membrane. Now that
> we have a concrete catchall proposal adequate to build membranes,
> we'd like to restart our discussions with Mozilla (JetPack, etc)
> about whether you could rebuild some of your C++ membranes in JS
> code using these primitives. We should follow up offlist.
We should, but I wanted to note that we're not rushing to self-host
our wrappers. They are part of the C++ TCB and take advantage of
certain privileges inherent there that would have to be reflected into
JS along with proxy support.
We may get there (my long term goal is to self-host much of what's now
native in our DOM code, and shrink the TCB) but efficiency as well as
complexity in pulling on a "need to reflect APIs X, Y and Z from C++
land" ball of string put the priority of this work lower than you seem
> To avoid some "climbing meta ladder" issues, we purposely
> distinguish between what we consider base-level operations, such as
> x.foo, and meta-level operations, such as Object.getOwnProperty(x,
> 'foo'). We attempt to be as fully transparent (leak free) as
> reasonably possible at virtualizing base level operations. We
> attempt to be fully non-transparent (leak like a firehose) to meta-
> level operations. Some of our classification may seem weird:
> Object.prototype.toString() is meta-level. It can be used to reveal
> that an object is a trapping proxy. Object.getOwnPropertyNames() is
> meta-level. Object.keys() is base level.
You're right, some of this is weird :-P. Any detailed rationale?
As noted, we've interposed wrappers where they are not expected and
their effects should not be observable to the wrapper-using code
except by preventing security exploits. So they want to be
transparent, even for such ES5 precursors as __defineGetter__,
This is an issue, since at least live.com "Ajax-style" JS uses these
SpiderMonkey extensions (which other browsers emulated) to extend the
more-standard DOMs in non-IE browsers to look like IE's DOM, and we
sometimes interpose a wrapper.
So I'm not sure our use cases want any non-transparency on a meta-
level basis. Blake might be able to say more.
> The properties of === that we feel need to be preserved:
> 1) "x === y" does not cause any user code to run.
> 2) "x === y" neither gives x access to y nor vice versa.
> 3) "typeof x !== 'number' && x === y" mean that x is operationally
> identical to y in all observable ways. Substituting the value for x
> with the value of y cannot change the meaning of a computation.
> 4) "x === y" implies that the Grant Matcher <http://erights.org/elib/equality/grant-matcher/
> > may safely send the money to either x or y.
> A wrapper is not identical to the object it is wrapping, or there
> wouldn't be any point in wrapping it. Thus, they can't be ===.
> Two independent wrappers on the same object may behave differently,
> depending on their definers. Thus they can't be ===.
> Except for two proxies with identical parts, such as two object
> proxies with identical handlers and supers. However, as shown by our
> example membrane, one can just use an Ephemeron table to avoid
> creating semantically identical duplicate proxies, preserving ===
> without magic.
This is probably a good long-term solution for our wrappers, as we
already have C++-only weak tables and the like to track and reuse
> 4. The [[Get]] versus [[Invoke]] rationale: indeed performance is a
> concern, but existing engines also specialize callee-computation
> distinctly from get-value, in order to optimize away Reference
> types. The ES specs so far do not, instead using the internal
> Reference type to delay GetValue so as to bind |this| to the
> Reference base when computing a callee and its receiver as part of
> evaluating a call expression.
> I think it is an open question whether a future spec, especially
> one using a definitional interpreter, will stick to References. If
> we end up making the distinction that all practical implementations
> already make, between get-as-part-of-callee-computation and all
> other get-value "gets", then I don't think this rationale is so
> In general over-coupling to ES5 may not help either a new Harmony-
> era proposal to "get in", or to be as complete or expressive as it
> should be. So a rationale based on choices or limitations of ES1-5
> seems weak to me.
> I would love to see the concept of References disappear, and to see
> the ([[Get]], [[Call]]) pairs in the spec that really mean "call
> this as a method" be rewritten as [[Invoke]]s. In that case, I would
> enthusiastically agree that this catchall proposal should be
> upgraded with an invoke() trap. Note how this would make our
> membrane code simpler and more efficient. Rather than a get() trap
> at the choke point that creates and returns a function, we'd simply
> have an invoke() trap whose body is that function's body.
One issue: ES specifies order of evaluation in detail, so array[index+
+].method(--j, ++k) really does evaluate left to right.
If invoking 'method' from the object at array[index] involves a
handler, the handler should be able to witness index++ having
happened, but not yet --j and --k. This is because without an invoke
handler, but with a get handler in array[index], the getter will run
in the left-to-right order.
So when would [[Invoke]] run?
> I'd like to understand better how we could get rid of References.
That's easy. Consider a toy grammar:
E ::= M '=' E
T ::= T '+' M
M ::= M '.' id
| '(' E ')'
The ES specs always make Reference types for M.id and id, do GetValue
on any the result of evaluating any expression not on the left of '=',
and do PutValue on the left of '='.
The alternative is to write the semantics "bottom-up" style (yacc,
bison, etc.) and in terms of ASTs or equivalent intermediate forms
built during reduction. Then the semantic action associated with M '='
E can crack M apart into its base (M if M.id else null) and
propertyName (id) parts, with early error throwing for invalid left-
hand sides (E in parentheses where E does not reduce to id or M.id).
formal bottom-up grammar requirement. It does require ASTs to delay
evaluation, but (see the AST discussion) those have other uses ;-).
More information about the es-discuss