fix(ir_lower): release the nullable-access hidden temp after its consuming chained read#531
Open
mirchaemanuel wants to merge 1 commit into
Conversation
…g chained read A chained subscript read on a ?array receiver materializes the intermediate container through the hidden owned temp created by lower_nullable_array_access. take_owned_temp clears the backing slot without releasing, so when the loaded temp value became the receiver of the consuming read nothing ever dropped its reference, leaking the returned containers on every non-null call (issue illegalstudio#525). Release the receiver with an IR-level Op::Release immediately after the consuming read when it is a load of a one-shot owned hidden temp and the read went through the boxed Mixed runtime path, whose result is an owned, independent cell (string payloads persisted, child pointers retained), so the release cannot invalidate the result. Typed reads (ArrayGet, StrCharAt, ...) can return payloads borrowing the receiver's storage and keep it alive, and direct array_get/hash_get receivers are intentionally untouched. Covers both merge results exactly once: the populated container from the non-null path and the boxed Mixed null from the null path. Fixes illegalstudio#525 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 #525: a chained subscript read on a
?arrayreceiver leaks the intermediate container on every evaluation.Root cause (confirmed at source level)
lower_nullable_array_access(src/ir_lower/expr/mod.rs) materializes the intermediate through a hidden owned temp (declare_owned_hidden_temp→store_value_into_tempacquires +1) and returns it viatake_owned_temp, whichLoadLocals the value and clears the slot without releasing. Becausevalue_is_nullableis false forMixed, the consuming outer read does not re-enter the nullable path — it goes tolower_array_access_from_valuewith receiver = the owned-temp load, taking theOp::RuntimeCallarm (__rt_mixed_array_get).store_localknows how to release owned-temp loads; the read path had no analogous release, so the temp's reference leaked (~7 blocks per non-null call in the issue repro: outer array + inner array + string payloads).The fix (IR-level, all targets)
In
lower_array_access_from_value, after emitting the read: when the op is the boxed-Mixed runtime read and the receiver is an owned-temp load, emit a release through the existingrelease_if_ownedprimitive. Exactly once (the temp slot is already cleared, so this read is the last consuming use), covering both merge results — the populated container from the non-null path and the boxed Mixed null from the null path (__rt_decref_mixedis null-tolerant).Safety:
__rt_mixed_array_get's documented contract (src/codegen_support/runtime/objects/mixed_array_get.rs) is that every successful return is an owned Mixed — string payloads persisted, child heap pointers retained — so releasing the receiver cannot invalidate the just-extracted result. Typed reads (ArrayGet,StrCharAt, …) can return borrowing payloads and are deliberately untouched.Evidence (macOS arm64, base v0.26.1
93f576168; all outputs byte-identical pre/post and match PHP 8.4.23)$r[0][0][1]$r[0][0] . $r[0][1](temp used twice per iter)The post-fix residual in the raw repro decomposes exactly (arithmetic checked) into two pre-existing, independent leaks that reproduce on clean main with zero nullable chained reads: a
?array-return/store leak (~4 blocks/call) and the Mixed→string cast-source leak (fixed separately in the sibling PR for #527). Both are follow-up candidates.Tests
2 regression tests in
tests/codegen/runtime_gc/regressions.rs(using a?arrayparameter so the unrelated pre-existing leaks cannot mask the assertion; both exercise the null and non-null path every iteration). Proven fail-pre-fix (live_blocks=250/400via stashed-src build), pass post-fix. Focused suites all green: nullsafe 21, nullable 34, nested 119, runtime_gc 124, arrays 337, array_access 24, isset 20, coalesce 52, ternary 36, mixed 221.cargo build/--releasezero warnings; debug-profile IR validator clean.Notes for the maintainer
lower_array_access_from_value. The union is trivial and semantically safe — the two conditions are disjoint (a receiver's defining op is either an owned-temp load or a direct index read, never both). I merged fix(codegen): release the inner-container temporary of a chained subscript read #524+Chained subscript read on a ?array receiver leaks the returned containers (nullable-access hidden temp is never released) #525+foreach by-value over an array of arrays leaks two blocks per iteration (element copy never released) #527 locally to verify: union resolution compiles clean and all three issue repros stay fixed/leak-free.$r[0][1][0]— receiver is a direct RuntimeCall result, not a hidden temp);($r ?? [])[0][1]is a pre-existing compile error on main (runtime_call with receiver PHP type Void); the?array-return/store leak. Follow-up issue candidates.