ajvincent / es-membrane

An ECMAScript implementation of a Membrane, allowing users to dynamically hide, override, or extend objects in JavaScript with controlled effects on the original objects.
ISC License
109 stars 13 forks source link

Use a priority-queue proxy handler to solve sealed cyclic references problem #171

Open ajvincent opened 6 years ago

ajvincent commented 6 years ago

From spec/non-membrane/lazyGetter.js:

        /* This is a difficult but possible problem:  in the Membrane
         * implementation, proxies are created only when needed, via the
         * ProxyNotify API.  If a "proxy listener" wants to seal many proxies,
         * and the underlying target indirectly refers to itself through a chain
         * of property names (a.b.c === a, for example), we can get stuck in a
         * situation where we're called to freeze a proxy for one proxy under
         * creation already.  The result is an identity chicken-and-egg paradox:
         * we can't seal any proxy under creation until all the properties are
         * known to the ObjectGraphHandler, and we can't look up all the
         * properties if a property lookup leads to a proxy creation.
         *
         * If one of the descriptors in the cyclic property reference chain is
         * an accessor descriptor instead of a data descriptor, we're fine.
         *
         * When all of them are data descriptors, that's when we run into
         * trouble, and we must break the chain somewhere.  This requires the
         * problem be broken down into three distinct parts:
         * (1) the proxy handler (where I recursively create and freeze proxies)
         * (2) the lazy getter definition
         * (3) a data structure to track targets of proxies under construction
         */

From a speech outline:

Chicken-and-egg problem with sealed cyclic references (5 minutes)

  • Creating a proxy for an object
  • Cyclic references (a.b.c.d === a)
  • All objects are sealed. (The properties must all be defined before returning the proxy)
  • Data descriptors for all those cyclic references on the unwrapped objects
  • Stack trace shows we’re creating proxies for a, a.b, a.b.c all simultaneously.
  • Result: I cannot use data descriptors on all the proxies, since at least one of the proxies is unique, but also under construction in the stack trace. One of the property descriptors must forever be an accessor descriptor, even though the value of that descriptor, once returned out of the proxy, is immutable.

I have a probable solution: we need to delay returning any of these proxies under construction until all the proxies have been appropriately created, their properties set, and the proxies sealed. This will require the actual proxies' handlers to use a priority queue, where no handler trap exits until the queue is completely cleared. The high priority operations will be in creating and populating the Proxy objects. The low priority operations will be in sealing the Proxy objects.

ajvincent commented 6 years ago

Branch name: priority-queue-0.9

To do:

ajvincent commented 6 years ago

It turns out the testcase is more complex than this, although where is not entirely clear.

ajvincent commented 6 years ago

Clarifications to the test steps:

ajvincent commented 6 years ago

Too complicated for this late in the 0.9 cycle. Besides, there's other major refactoring work to happen on that milestone.

ajvincent commented 5 years ago

I think that #179 may obsolete this entire problem... if the proxy is created around a forwarding proxy handler that can be retargeted to different secondary handlers on demand. Simply put, why would we need to replace the proxy in that case?