5

I'm using the V8 API to create JavaScript objects. Some of these objects support iteration by setting up a native (intercepted) function at the Symbol.iterator property.

Iterating such an object via for...of works perfectly. However, if I wrap it in a null proxy (e.g., let x = new Proxy(obj, {});), the resulting object is not iterable and throws a TypeError with the message "Illegal invocation" if an attempt is made to iterate over it.

Wrapping a standard array doesn't exhibit this issue. Is this a V8 bug?

2
  • What would you expect to iterate over if you've wrapped it in a null proxy ? Commented Dec 8, 2016 at 18:11
  • @Pogrindis I'd expect the proxy to iterate over its target; that's what happens if the target is a standard array. Commented Dec 8, 2016 at 18:12

1 Answer 1

8

Wrapping a standard array doesn't exhibit this issue.

Yes, that's how array iterators work. They don't care about the kind of the object they are iterating - they simply access its .length and indexed properties (which are routed normally through the proxy).

However, other standard exotic objects don't behave that nice either. If you try to invoke [Symbol​.iterator]() on a typed array, map or set that is wrapped in a proxy, they'll bitch about being invoked on the wrong object.

Is this a V8 bug?

No, it's a bug in the application. You've got three choices:

  • Create an iterator that does not depend on the internal slots of your custom objects, but rather uses their public (proxy-interceptable) property interface. Make sure your [Symbol.iterator] method does not typecheck its receiver.
  • Check the type of the receiver in your iterator method, and if it is a proxy (i.e. has a [[ProxyTarget]] internal slot) then use that value. I would strongly advise against this, as it does not match the standard behaviour and breaches the proxy when bypassing the handler.
  • Don't use a null proxy:

    let x = new Proxy(obj, {
        get(target, key, receiver) {
           if (key === Symbol.iterator)
               return target[Symbol.iterator].bind(target);
           else
               return Reflect.get(target, key, receiver);
        }
    });
    
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer, @Bergi. You're right; the problem is that my [Symbol.iterator] method expects its holder to have certain private fields, which aren't accessible when the holder is a proxy. Quick follow-up question: Do you know why proxy[Symbol.iterator]() throws an exception without invoking my interceptor, whereas it = proxy[Symbol.iterator]; it(); invokes the interceptor and crashes as expected?
Both of those should invoke the get handler if that is what you mean by "interceptor"? I can't imagine why it would not.
Sorry, let me try to clarify. I'm not specifying a custom get handler; the method is stored as a normal property on the target and appears to be retrieved correctly in both cases. What I don't understand is why the first syntax throws an exception without calling the method, while the second syntax calls the method and crashes as expected. The method itself is a native function whose invocation causes a callback into native (C++) code, but that should be irrelevant AFAICT.
The obvious difference is the receiver (proxy vs undefined), which would be the value of the this keyword in a js function, but I have no idea how native functions are affected by it. Apparently there's some guard that prevents your custom function from running, but I don't know about V8 details. Does it happen on non-proxy objects as well?
Right again; the difference appears to be somewhere deep within V8's function-call machinery. It looks like x.call(y) fails early if x is a native function and y is a proxy.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.