Liveness barriers and finalization

The PR ticket you linked specifically calls out early dispatch and inlining as allowed. The specification doesn’t distinguish ‘internal’ and ‘external’ usage: all user code is equivalent, and an engine can perform transformations on it that would be otherwise impossible to express due to name resolution and scoping rules. That should make cross-module inlining allowed.

If you what you claim here by ‘the object's method call […] has to use this while reaching the private field’ is that private field accesses observe object identity, then even the original example cannot trigger a use-after-free. But the PrivateGet algorithm does not look like it observes object identity any more than OrdinaryGet does, so scalar replacement performed against one should be as valid as against the other.

In practice, I’d expect the biggest obstacle against such shenanigans to be the reason I had to add Object.freeze(Resource.prototype) to the second example: an engine must consider the fact that in the meantime, i.e. before the asynchronous job completes, someone may have monkey-patched Resource.prototype.use so that it observe the identity of this after all (whatever that may mean).

A particularly ironic interpretation of ‘observing object identity’

If we take ECMA-262 12th Ed. §9.9.2 Note 4 seriously, then it seems even the motivating example in that PR can fail:

const o = {};
const wr = new WeakRef(o);
await 1;
/* (A) */
assert(wr.deref() === o);

In a WeakRef-oblivious execution with respect to { o }, we have wr.deref() return undefined. The subsequent strict comparison between undefined and o therefore returns false because of a type mismatch. This does not constitute ‘observing a strict equality comparison between objects’ (note plural); you don’t need to know which object o is to know it is not undefined. Therefore, o is eligible for collection at (A)!

1 Like