Closed Bug 605711 Opened 14 years ago Closed 14 years ago

Harmony proxies: 'get' on child of proxy fails

Categories

(Core :: JavaScript Engine, defect, P2)

x86
macOS
defect

Tracking

()

RESOLVED FIXED

People

(Reporter: tomvc.be, Unassigned)

Details

When accessing a property on an object that delegates to a proxy object, the implementation seems to call 'getPropertyDescriptor' unexpectedly:

var p = Proxy.create({ get:function(){return 42; } });
var o = Object.create(p)
o.x === 42
typein:4: TypeError: getPropertyDescriptor is not a function
Priority: -- → P2
I think the flow is like ->
o.[[Get]] -> o.[[GetProperty]] -> o.[[GetOwnProperty]]
-> p = o.[[Prototype]] 
-> p.[[GetProperty]]
-> Error!

I think the error is correct, the harmony proposal says you need atleast getPropertyDescriptor and few others.
I'm aware that the proxy handler should strictly speaking provide a 'getPropertyDescriptor' trap, it's just that I wouldn't expect it to be called by executing 'o.x' (where o is a child of the proxy)

Why does the implementation need a property descriptor? My mental model was:
o.[[Get]]('x') -> property not found, so perform p = o.[[Prototype]]; p.[[Get]]('x')
Our implementation calls has() on the proxy, which does a getPropertyDescriptor on the proxy to see if the property exists at all before calling get().
Ok, I see, the following does work:

var p = Proxy.create({ has: function() { return true; }, get: function() { return 42; } });
var o = Object.create(p);
o.x === 42

I guess it makes sense, since you can't really tell whether the property was found on the proxy just by inspecting the result of the 'get' trap. If it returns undefined, that may be simply because the looked up property is bound to undefined.
Even then, I still don't understand the following behavior, and I suspect it is a bug:

> var h1 = {get: function() { return new TypeError('foo'); }};
> var h2 = Proxy.create(h1, null);
> var h3 = Object.create(h2);
> h3.x
TypeError on line 1: getPropertyDescriptor is not a function

I would have expected the 'foo' error to propagate instead. Is there a justified reason why this doesn't happen?
The same reason: we don't call the getter before we can confirm that the property exists. That is, in walking the prototype chain looking for the property, we call has(id) on each object in the prototype chain. The default implementation of has tries to use getPropertyDescriptor, which doesn't exist and throws. Only once has(id) returns true do we attempt to call the get hook (which will then propagate your error).

This is a result of the fundamental traps being required. From a brief skim of harmony:proxies, it appears that all it says is that they are required. That seems to imply that if you try to perform an (any?) operation on a proxy handler that doesn't implement all fundamental traps, we should throw, telling you that you're missing a required piece of your handler.
In reply to comment #5:

Mark, surely you meant "throw new TypeError" rather than "return new TypeError".
Also, you most probably meant to use double lifting, like so:
var h1 = Proxy.create(<your code>);

This does work as expected:
js> h1 = Proxy.create({ get: function() { throw new TypeError('foo'); } });
js> h2 = Proxy.create(h1, null)
js> h3 = Object.create(h2)
js> h3.x
typein:5: foo

In reply to comment #6:

Correct. Missing fundamental traps cause the proxy implementation to throw. It's tempting to provide a default for such traps, but this could hide subtle bugs, e.g. due to a misspelling of a trap name.
> Mark, surely you meant "throw new TypeError" rather than[...]

Indeed I did; my mistake. My code now works as I expect. Thanks.
Tom: is this bug INVALID as reported? I.e., it works as designed?

/be
The issue previously reported is indeed resolved by providing a "has" trap. It's not intuitive but it certainly is within the bounds of the Proxy spec.

However, while going through the example again, I just discover another bug relating to 'get' on proxies acting as prototypes of other objects: the first argument to the "get" trap (the "receiver") is always bound to the proxy itself, while it should be bound to the "child" object when used as a prototype. Here is a test case:

var delegator;
var p = Proxy.create({
  has: function(name) { return (name === 'testReceiver'); },
  get: function(receiver, name) { return (receiver === delegator); }
});
delegator = Object.create(p);
print(delegator.testReceiver); // should be true, but is false because receiver === proxy

(Let me know if you prefer to file this as a separate bug. The current title still seems to cover this bug.)
Hi Tom, I noticed the receiver parameter was not used, and independently so did Blake Kaplan, who fixed the bug you report in comment 10 as part of the patch for bug 596031. So this bug seems all used up now. Marking FIXED to avoid confusion. Thanks for following up.

/be
Status: NEW → RESOLVED
Closed: 14 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.