Summary
Passing a freshly boxed owned mixed value directly as a call argument, and consuming the
call's return value, corrupts refcounts in a loop: heap debug aborts with
Fatal error: heap debug detected bad refcount. Stdout up to that point is correct.
Found while working on #595 (PR #603); independent of that fix — the failure is byte-identical
with and without the #595 classification change (verified by reverting it and rebuilding).
Repro
<?php
function idv($x) { return $x; }
$c = 0;
for ($i = 0; $i < 20; $i++) { $r = idv($i + 1); $c = $c + $r; }
echo $c, "\n";
Compiled with --heap-debug (main 1da317551, macos-aarch64):
Fatal error: heap debug detected bad refcount
PHP 8.4 prints 210.
Bisection
- Passing through a local first (
$x = $i + 1; $r = idv($x);) → heap-clean, correct output.
- Discarding the call result (
idv($i + 1); without using the return) → clean.
- A constant argument (
idv(5)) → clean.
The trigger is specifically an owned boxed-Mixed argument temporary (here the boxed
$i + 1) whose call result is also consumed — some reference is dropped once too often
(or retained once too few) between the caller's argument-ownership handling and the return
path that forwards the same box (return $x; returns the parameter itself).
Related
https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L
Summary
Passing a freshly boxed owned
mixedvalue directly as a call argument, and consuming thecall's return value, corrupts refcounts in a loop: heap debug aborts with
Fatal error: heap debug detected bad refcount. Stdout up to that point is correct.Found while working on #595 (PR #603); independent of that fix — the failure is byte-identical
with and without the #595 classification change (verified by reverting it and rebuilding).
Repro
Compiled with
--heap-debug(main1da317551, macos-aarch64):PHP 8.4 prints
210.Bisection
$x = $i + 1; $r = idv($x);) → heap-clean, correct output.idv($i + 1);without using the return) → clean.idv(5)) → clean.The trigger is specifically an owned boxed-Mixed argument temporary (here the boxed
$i + 1) whose call result is also consumed — some reference is dropped once too often(or retained once too few) between the caller's argument-ownership handling and the return
path that forwards the same box (
return $x;returns the parameter itself).Related
sibling leak; this one is the over-release/corruption side).
https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L