Skip to content

Proxy should get the handler's property descriptors during construction. #21

@erights

Description

@erights

Historical mistake

We made a mistake in the Proxy design (@tvcutsem agrees): The proxy does a [[Get]] on the handler's trap properties each time it wants to trap. Rather, the proxy should have treated the handler object much like an options object, doing all those [[Get]]s up front and remembering these methods internally.

Either way, any user can of course create a taxonomy of partial customizations, using inheritance or copying between partially or fully populated options/handler objects as they wish. This shift would have both pros and cons:

Cons:

  • When a proxy invokes one of these methods, what does it pass as the this-argument?
    • If it passes the options/handler object itself, then the traps can delegate among each other as normal. But if the options/handler is mutated, then these self sends will diverge from what the proxy is invoking.
    • If it passes anything else or nothing, then delegation between traps needs to invent some other pattern beside the normal inheritable self-sends. These other patterns may not be worse --- they may even be better --- but they will be less familiar.
  • Not compatible with existing proxies as shipped and as specced in prior specs.

Pros:

  • The trapping mechanism can avoid an extra runtime [[Get]] potentially being a bit faster.
  • The proxy knows up front which traps are absent, and so can heavily optimize those cases. For example, it can pass the operation directly through to the target without lifting and lowering. Doing these optimizations at proxy construction time amortizes the costs of the optimization itself.
  • We could resolve the inheritance-cycle issue Apple raised (need link) with much less loss of transparency.

Cycle checking

Let us say that a non-proxy inherits simply if its [[GetPrototype]] behavior is known to always immediately return its [[Prototype]] or nothing, and do nothing else. An object whose [[GetPrototype]] behavior is not known to do so inherits exotically. All non-exotic objects inherit simply. Only exotic objects can inherits exotically, but most exotic objects inherit simply. These definitions do not care about the behavior of an object's [[SetPrototype]] trap.

A proxy inherits simply iff its target inherits simply and its handler can never provide a getPrototype trap. If the traps were gotten from the handler only once at proxy construction time, this would be a useful distinction. Since both handler and target may be proxies, this definition is recursive on the target. If a proxy inherits simply, then its [[GetPrototype]] behavior either returns the leaf target's [[Prototype]], or nothing if any proxy in the target chain has been revoked. If a proxy inherits simply, then its [[GetPrototype]] behavior does nothing else.

With these definitions, we can change the cycle check to reliably prohibit an inheritance cycle among objects that inherit simply. The cycle check would operate atomically and with no side effects. It would cause no traps. A proxy that wishes to be truly transparent would need to inherit simply, which would not be a burden for almost all uses of proxies. A proxy that inherits exotically thereby opts out of full transparency. The cycle check can sometimes be used to reveal the presence of a proxy that inherits exotically, but it cannot reveal the presence of a proxy that inherits simply.

All Pros and no Cons

Fortunately, because of the invariants, a different change to the spec will give us all the pros, none of the cons, and will likely not break any existing code. We propose to have the proxy inspect the handler at proxy construction time. For a given handler and a given trap name, if any of the following conditions hold:

  • Doing [[GetOwnPropertyDescriptor]] on a that trap name of that handler reveals a non-configurable non-writable data property whose value is undefined.
  • All of
    • Doing [[GetOwnPropertyDescriptor]] reveals that the property is absent.
    • Doing [[IsExtensible]] reveals that the handler may not vary in is answers to [[GetPrototype]].
    • (optional: the handler inherits simply so `[[GetPrototype]] cannot have other effects).
    • Doing [[GetPrototype]] on the handler reveals that it inherits either from nothing or from an object that satisfies these same constraints,

then we know that a [[Get]] of that trap name on that handler will never return a trapping function. Under these circumstances, the proxy should remember that this trap is absent and not do those [[Get]]s at trapping time. In the case where the handler is a proxy (or possible other exotic objects), this has an observable difference: Doing the [[Get]] could cause other side effects which skipping the [[Get]] does not have. But in neither case could these side effects have caused the [[Get]] to return a trapping function. The absence of these effects under these circumstances likely does not break any existing code.

Cycle checking again

When the trap name in question is getPrototype then the above change becomes more than just a slightly non-transparent optimization. It allows us to distinguish proxies that inherit simply from those that inherit exotically, in order to make exactly the change to cycle detection explained above.

Possible remaining optimizations.

For properties that satisfy the relaxed requirement

  • Doing [[GetOwnPropertyDescriptor]] on a that trap name of that handler reveals a non-configurable non-writable data property.
  • All of ...same text...

then we know that whatever the current value reported for that trap name, it will always report the same value or nothing. If there are no revocable proxies in the handler chain, then we even know that it will always report the same value, period. As part of this overall change, we might want to allow the proxy to cache these trap functions as well. This isn't much of an optimization since these traps will still happen. But it would allow the proxy to avoid these [[Get]]s at trap time. For high speed operations through simple membranes, even this may be significant.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions