You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GC use-after-free: objects pushed to a module-level array from inside a function are freed under allocation churn + gc() #5459
On clean main (v0.5.1191), objects pushed to a module-level array from inside a function are wrongly collected under allocation churn + explicit gc(), causing a use-after-free / SIGSEGV. The same population done at module scope is fine. No weak references are involved — this was isolated while debugging WeakMap weakness and turns out to be a pre-existing, independent bug.
Minimal reproduction (deterministic)
declarefunctiongc(): void;functionchurn(n: number): void{letj: any[]=[];for(leti=0;i<n;i++){j.push({ i,p: "z".repeat(40)+i});if(j.length>128)j=[];}}conststrong: any[]=[];(functionsetup(){for(letn=0;n<50;n++)strong.push({id: n});})();// populate from INSIDE a fnfor(letc=0;c<12;c++){churn(80000);gc();}letalive=0;for(letn=0;n<50;n++)if(strong[n]&&strong[n].id===n)alive++;console.log("survived:",alive,"/ 50");
perry run (or compile + run) → SIGSEGV (exit 139), 5/5 runs. Some strong[] elements have been freed; reading them faults.
Isolation / narrowing
The IIFE is the trigger. Replacing (function setup(){ for (...) strong.push({id:n}); })(); with a module-scope for (let n=0;n<50;n++) strong.push({id:n}); → survives 50/50, no crash. That one wrapper is the only delta.
Not weak-collection-related. First seen with WeakMap/FinalizationRegistry/WeakRef, but the control above has no weak refs at all and still crashes. (FinalizationRegistry with live targets held in an array shows the same: live registered targets are lost / SIGSEGV.)
Not generational-GC-specific. Reproduces with PERRY_GEN_GC=0 (full mark-sweep) and with PERRY_WRITE_BARRIERS=0.
Requires allocation churn. No churn() (just a gc() loop) → 50/50. The churn profile matters: rapid small-array alloc + discard triggers it; building one large array per round does not.
Heap-layout sensitive (Heisenbug). Deterministic standalone, but runs clean under lldb (debugger heap layout masks the UAF), which has frustrated getting a faulting backtrace.
Suspected area
An object created in a nested function scope and stored only in a module-level array appears not to be reliably retained as a GC root — a sweep under allocation pressure frees it despite the live strong reference from the module-global array. Likely a root-scanning or codegen root-registration gap for module-level array elements written from within a callee frame. Needs an ASAN/sanitizer build or root-scan/codegen tracing to pin the exact site.
Impact
Silent memory corruption / crash for an ordinary pattern (populate a module-level collection from a helper function, then do allocation-heavy work with gc()). Also blocks making WeakMap/WeakSet genuinely weak (#2656), whose implementation exercises the same path.
Activity
fix(codegen): #5459 — store array head back to module-global root on …
fix(codegen): #5459 — store array head back to module-global root on …
fix(codegen): don't clobber the box pointer when pushing to a boxed+c…