On Jan 22, 2013, at 9:06 AM, Brandon Benvie wrote:
> Kevin Smith pointed out something I hadn't though about before but is obvious
> in retrospect. The hazard that proxies bring to the language in general:
> proxies make it possible to break the target object by inconsistently
> handling forwarding during the operation of methods.
>
> class Counter {
> constructor(){
> this.value = 0;
> }
> increment(){
> this.state = "working";
> this.value++;
> this.state = "idle";
> return this.value;
> }
> }
>
>
> const counter = new Counter;
>
> const counterfeit = new Proxy(counter, {
> get(target, key, receiver){
> if (key === 'value') {
> throw 'break target';
> }
> return Reflect.get(target, key, receiver);
> }
> });
>
>
> While this may not be common, and the example is contrived, it seems like it
> may be less uncommon when there's a trap that exists who's sole purpose is to
> throw or not.
>
> It is my opinion that the primary use case for private symbols is for
> properties that proxies expressly shouldn't be given a chance, in any manner,
> to corrupt or modify. They are likely used for sensitive internal state that
> will only be accessed by methods or friend classes created in service of the
> target.
>
> A membrane becomes less valuable if breaking the target is an easily
> accomplished accidental side effect. This is already visible in practice
> today when you attempt to use WeakMaps to create private state for objects
> and they are proxied, since the private state will be keyed on `this` in the
> constructor which won't match `this` in methods invoked on the proxy.
>
Right, more concretely:
let fooRegistry = new WeakMapl
class Foo {
constructor() {
fooRegistry.set(this,true); //should really be in a @@create method
to prevent counterfeiting
}
op() {
if (!fooRegisty.has (this)) throw Error("Foo called on non-Foo object");
}
}
let f = new Foo;
let pf = new Proxy(f, {});
f.op(); //no problem
pf.op(); //throws
This problem has nothing to do with private Symbols and is the same problem Tom
and I talked about in
https://mail.mozilla.org/pipermail/es-discuss/2012-December/027277.html
As that thread discussed, the root cause is that the lookup that retrieved a
method is a separate MOP level operation from the call of the method and there
currently is no mechanism to coordinate the this value between those MOP
operations.
On way to fix this would be to introduce a new MOP operation [[CallProperty]]
that combines [[Get]]/[[Call]]. I know Brendan has opposed this when it has
come up in the past (bad E4X experiences) but I'm not talking about any new
user level semantics (other than making Proxy work reasonably). The ordinary
object definition of [[CallProperty]] would have exactly the same semantics as
occurs for the [[Get]]/[[Call]] sequence that evaluates for expr.key()
Another way to address this issue avoids introducing a new MOP level operation.
Instead we would introduce a new kind of Reference value that is used for the
result of Proxy [[Get]] operations. The [[Call]] op would know about these
special Proxy References and adjust the this value accordingly before invoking
the function. Something similar to that is currently in the spec. to support
super based property accesses.
I think, [[CallProperty]] actually has less hair, but either one could be used
to make default Proxy behavior be consistent forwarding to the target with the
target value always substituted for this position proxy values. We would get
rid of the confusing and error prone combination of forwarding and delegation
that is now the default for Proxy.
Allen
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss