Repository navigation
Conversation
The aggregation php::Array holds a second reference to every caller slot, but the ArrayCleanupGuard was emitted after all per-argument RefWrap::commit() lines, so committing saw refcount 2 and raised 'A temporary typed reference cannot escape a dynamic call' for plain local-variable arguments. Emit the guard when the aggregation array is created so cleanup() is queued before the commit lines.
matyhtf
left a comment
There was a problem hiding this comment.
The change fixes the reported variadic-only case, but the cleanup ordering still misses a fixed by-reference parameter sharing the same caller variable with a by-reference variadic parameter. Please address this case before merging.
Reproducer tested against PR HEAD 664d562b5516da9487cc4a2f76d218ac00831950:
function bump(&$first, &...$rest): void {
$first += 10;
foreach ($rest as &$value) {
$value += 10;
}
}
function main(): void {
$a = 1;
bump($a, $a);
var_dump($a);
}PHP prints int(21), while the PR build still throws:
A temporary typed reference cannot escape a dynamic call
The fixed argument is lowered before the variadic aggregation array is created. Its RefWrap::commit() has therefore already been queued when the new code appends cleanup(). The variadic argument reuses the same bridge, and the generated C++ still has this order:
php_bump(wrapper.ref(), args);
wrapper.commit();
guard.cleanup();At commit(), the aggregation array still owns a reference, triggering the escape check. This case also fails on the base revision, so it is an uncovered case of the same root cause rather than a new regression.
Please:
- Arrange cleanup for each call so its reference aggregation array is released before all reference bridge commits belonging to that call, including bridges created for fixed parameters. Preserve argument evaluation order and exception cleanup.
- Add a PHPT regression for the shared-variable example above, alongside the existing loop and named-argument coverage.
Validation: all 14 variadic PHPTs and 107 related PHPUnit tests pass on this PR; the new PHPT fails on the base and passes on the PR. Additional repeated-alias and exception write-back probes also pass. The reproducer above is the remaining gap.
Thanks for the contribution and the regression test.
Body
Fixes a false-positive runtime error when calling a by-ref variadic function/method with plain local-variable arguments. Found while testing the follow-up to #153; master-only repro.
Symptom
Compiles on master, then fails at runtime:
Write-back should simply succeed (
int(11)). The error is a false positive: the caller's slot is a stable local, not a temporary.Root cause
By-ref variadic arguments are aggregated into a temporary
php::Arraywhose slots hold references back to the caller. The aggregation array therefore owns a second reference to each caller slot, so at commit time the refcount is 2 and the typed-ref escape check intypephp_helper.hrejects it.The
ArrayCleanupGuardthat drops the aggregation array's references before the per-argumentRefWrap::commit()lines was emitted too late: itscleanup()was appended after all commit lines, so every commit still observed refcount 2.Scope matrix (all verified): fails for local-variable arguments (loop / non-loop, function / method, typed / untyped, named arguments); unaffected are property and array-element arguments (different wrapping paths) and non-variadic by-ref; literal arguments are a compile error, same as PHP.
Fix
Emit the guard when the aggregation array is created (inside the
if ($variadicVar === null)block, after the per-statementunset()), so itscleanup()is queued inafterStmtLinesbefore anyRefWrap::commit()line, and drop the late emission at the statement tail. The guard stays per-call-site by design:ArrayCleanupGuard::cleanup()is one-shot (it clears its internal pointer; the destructor is only the exception-safety fallback).Tests
New
tests/compiler/variadic/by-reference-variadic-local.phptcovering, in one binary: typed local argument (non-loop and loop), named argument, untyped variadic, property argument, and array-element argument — with the expected output generated as ground truth against stock PHP 8.4.Verification
tests/compiler/variadic: 14/14 pass (including fix(codegen): rebuild variadic aggregation array per statement #153'sloop-accumulation.phptandvariadic-goto-skip.phpt)tests/compiler: 1272 pass / 16 fail — the 16 failures are identical on master (pre-existing, unrelated areas: static binding, heredoc, enum, foreach, bigfloat, …)Out of scope (pre-existing, separate)
A forward
gotothat skips a by-ref call fails C++ compilation on master today (crosses initialization of 'php::RefWrap<…>'/'php::ArrayCleanupGuard'— call-site declarations live in the function body scope). This PR does not change that behavior (same state before and after); a follow-up will be tracked separately.