fix(ir_lower): release owned Mixed sources after string coercion (foreach element leak)#532
Open
mirchaemanuel wants to merge 1 commit into
Open
Conversation
A string coercion of an owned boxed Mixed temporary (cast Mixed -> Str)
never released the source box: release_stringified_source_if_owned only
covered Object/Array/AssocArray sources. Every owned Mixed temp flowing
into a string coercion leaked its box plus the payload the box owned --
most visibly 2 heap blocks per iteration for the element read in
'foreach ($rows as $row) { echo $row[1] . PHP_EOL; }' over an array
of arrays.
The backend lowers the cast through __rt_mixed_cast_string, which
detaches the result (persisted string copy / fresh itoa-ftoa buffers),
so the produced string never aliases the released cell and the release
is safe.
Fixes illegalstudio#527
Claude-Session: https://claude.ai/code/session_01Jfn7uqEH5Dog1nBzTM5DCW
This was referenced Jul 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #527 — with a corrected diagnosis: the foreach rebind machinery was innocent.
Root cause (differs from the issue's hypothesis — verified with controlled probes)
The by-value
foreachrebind on main is already balanced:IterCurrentValue/IterCurrentKeyare listed as owning temporaries, and the emitted IR shows the full release-old → acquire-new → store sequence per iteration. Probes prove it: a loop body of{},{ $t = $row[1]; }, or{ echo $row[1]; }is heap-clean. Only{ echo $row[1] . "\n"; }leaks.The real bug:
release_stringified_source_if_owned(src/ir_lower/expr/mod.rs) skipped boxedMixedsources.$row[1]on a Mixed row produces an owned fresh Mixed box; the implicit string coercion for the concat emitscast … Strand released the source only for Object/Array/AssocArray reprs — Mixed fell through to the no-op arm, leaking the box plus the persisted string payload it owns: exactly the 2 blocks/iteration in the issue. The same hole leaked every owned boxed-Mixed temporary flowing into a string coercion — explicit(string)casts and interpolation included, foreach or not.The fix
One functional line: add
PhpType::Mixedto the released-source match (Union codegen-reprs to Mixed, so it is covered too), with an explanatory comment. Safety: the backend lowerscast Mixed→Strvia__rt_mixed_cast_string, which detaches the result from the box (string payloads persisted into an independent copy; int/float/bool rendered into fresh buffers) — the produced string never aliases the released cell. Ownership is still gated byrelease_if_owned(borrowed/non-heap/moved sources untouched).Evidence (macOS arm64, base v0.26.1
93f576168; every output byte-identical pre/post and equal to PHP 8.4.23)$rowused after the loop (PHP semantics)breakmid-loop + use afteras &$rowincl. mutation visibilityas $k => $rowwith string keys(string)cast / interpolation / array-literal element probesTests
3 regression tests in
tests/codegen/runtime_gc/regressions.rs(issue repro; post-loop$rowsurvival + clean heap; assoc key+value form) — proven fail-pre-fix by running the identical fixtures through a preserved pre-fix compiler (8/2/6 leaked blocks), pass post-fix. Focused suites all green, 0 failures: foreach 106 (103 + 3 new), arrays 337, nested 119, runtime_gc 125, concat 59, cast 106, string 705, mixed 221, iterator 122, echo 63, optimizer 251. Zero warnings;--no-ir-optrun also clean.Notes for the maintainer
$sum += (int) $row[1];leaks 1 block/iteration on clean main;lower_castinvokes the release helper only forCastType::String. Follow-up issue candidate.