Summary
Rebinding a loop-grown array local to [] after pushing it into an outer array leaks exactly one 40-byte block per rebind (N outer iterations → N−1 leaked blocks). The output is correct; only memory is lost.
Environment
- elephc main
e63d5d337 (v0.26.0-158), macOS arm64 (Apple Silicon); PHP 8.4.23 as behavioral reference.
Repro
<?php
$a = [];
for ($i = 0; $i < 6; $i++) {
$mid = [];
for ($j = 0; $j < 3; $j++) { $mid[] = ['x', 'y']; }
$a[] = $mid;
}
echo count($a) . "\n";
elephc --heap-debug bisect_b.php && ./bisect_b
Observed
6
HEAP DEBUG: leak summary: live_blocks=5 live_bytes=200
Scaling law (measured): N outer iterations → N−1 leaked blocks, 40 bytes each. 3 iters → live_blocks=2 live_bytes=80; 6 → 5/200; 10 → allocs=154 frees=145 live_blocks=9 live_bytes=360.
What is required to trigger it (negative results, all clean on pristine main)
- a single outer iteration (no rebind): clean;
- an inner loop with exactly one push (
$mid = []; $mid[] = ['x','y'];): clean;
- assigning a literal instead of growing (
$mid = [['x','y']];): clean.
The leak needs all three of: an inner grow loop with ≥ ~3 pushes into $mid (enough to force a storage reallocation), then $a[] = $mid, then rebinding $mid = [] on the next outer iteration — one stale block survives per rebind event.
Expected
Leak-free: after $a[] = $mid transfers/retains the final storage, the $mid = [] reassignment should release everything the local still owns.
Notes
Summary
Rebinding a loop-grown array local to
[]after pushing it into an outer array leaks exactly one 40-byte block per rebind (N outer iterations → N−1 leaked blocks). The output is correct; only memory is lost.Environment
e63d5d337(v0.26.0-158), macOS arm64 (Apple Silicon); PHP 8.4.23 as behavioral reference.Repro
Observed
Scaling law (measured): N outer iterations → N−1 leaked blocks, 40 bytes each. 3 iters →
live_blocks=2 live_bytes=80; 6 →5/200; 10 →allocs=154 frees=145 live_blocks=9 live_bytes=360.What is required to trigger it (negative results, all clean on pristine main)
$mid = []; $mid[] = ['x','y'];): clean;$mid = [['x','y']];): clean.The leak needs all three of: an inner grow loop with ≥ ~3 pushes into
$mid(enough to force a storage reallocation), then$a[] = $mid, then rebinding$mid = []on the next outer iteration — one stale block survives per rebind event.Expected
Leak-free: after
$a[] = $midtransfers/retains the final storage, the$mid = []reassignment should release everything the local still owns.Notes
$midvia repeated[]=reallocates its storage; after the push retains the final pointer, the rebind's release path drops all but one of the blocks associated with the grown array — consistent with a pre-grow allocation or an ownership-transfer bookkeeping miss on the push+reassign sequence.