Parents: #474, #476 Related: #896, #480, #493, #888
Source finding
At 65dbb40, Interpreter.drainFinalizationCleanupJobs keeps registry and its callback in native locals across repeated callback invocations. It also calls registry.finRecordTakeReady() before the fallible callValue(cb, held) preparation. The shared cleanup queue roots the registry, but its relocation does not rewrite those native locals. The drain also uses a per-invocation queue index even though nested cleanup can clear/repopulate the same queue. These ownership boundaries require deterministic witnesses.
This is a source audit, not a measured use-after-free or failing-allocator result. #896 now separates unowned cleanup OOM from the host's original evaluation result and retains untouched records after a callback has actually run. That error-reporting boundary does not itself establish record admission or precise ownership inside the finalization drain.
Specification boundary
ECMA-262 CleanupFinalizationRegistry, steps 3.b–3.c removes a cell before invoking HostCallJobCallback. The commit point must respect that order. Drain-owned preparation that fails before an admitted Call should leave the selected cell owned; once Call is admitted, an abrupt result consumes that cell. In particular, do not replay a proxy apply getter, parameter initializer, native callback, or callback body that already performed effects. The original source audit did not establish a generic requirement to restore cells after every call-setup OOM.
Required work
- Produce deterministic allocation-failure witnesses distinguishing drain preparation before Call admission from an abrupt admitted Call, with exact ready/held-value ownership.
- Give each selected registry, callback and held value precise relocatable ownership across callback preparation, execution and nested cleanup. Reload relocated values before subsequent operations.
- Make the spec-aligned admission point explicit: retain work on failed drain preparation; never replay an admitted Call, including observable proxy/parameter preparation.
- Preserve remaining registry/record order and reentrant queue insertion without holding realm/object locks while invoking JS or allocating through recovery.
- Preserve exact release on success, ordinary callback throw, engine OOM, registry unregister and Context teardown. No disabling moving GC, arbitrary queue limits, conservative-stack dependence, emergency pool or swallowed failure.
Acceptance
Deterministic failing-allocator and actual nursery-relocation tests with native-stack scanning disabled; nested cleanup and callback-side unregister/new registration; exact callback counts and queue/held-value ownership before failure and after recovery; concurrent/no-GIL and suppression-free TSan; affected FinalizationRegistry/WeakRef Test262, full units and leak checks. Link implementation commits and keep source findings distinct from measured failures.
Security scope is engine state and lifetime integrity under script-controlled graph shape and memory pressure; no embedding permission or sandbox layer is required.