From 664d562b5516da9487cc4a2f76d218ac00831950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8E=9F=E7=82=B9?= <467490186@qq.com> Date: Sat, 10 Oct 2026 09:28:44 +0800 Subject: [PATCH] fix(codegen): queue by-ref variadic cleanup before RefWrap commit 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. --- src/Generator/CallArgumentGenerator.php | 22 +++++---- .../variadic/by-reference-variadic-local.phpt | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 tests/compiler/variadic/by-reference-variadic-local.phpt diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index f7d68649..19876b35 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -314,6 +314,19 @@ protected function parseNativeCallArgs( // iteration instead of accumulating arguments. $variadicVar = $this->addTmpVar(Type::ARRAY); $this->context->beforeStmtLines[] = $variadicVar . '.unset();'; + if ($argInfo->byRef) { + // The aggregation array owns the second reference to + // every caller slot, so queue its release before the + // per-argument RefWrap::commit() lines are appended: + // committing while the second reference is alive + // trips the typed-reference escape check. The guard + // also releases during C++ exception unwinding into + // a PHP catch block. + $cleanupGuard = $this->genTmpVarName(); + $this->context->beforeStmtLines[] = 'php::ArrayCleanupGuard ' . $cleanupGuard + . '{' . $variadicVar . '};'; + $this->context->afterStmtLines[] = $cleanupGuard . '.cleanup();'; + } } if ($arg->unpack) { $method = $argInfo->byRef ? 'mergeReferences' : 'merge'; @@ -338,15 +351,6 @@ protected function parseNativeCallArgs( } if ($variadicVar !== null) { $resolvedArgs[$variadicArgIndex] = $variadicVar; - if ($functionDef->argInfoList[$variadicArgIndex]->byRef) { - // The aggregation array owns the second reference to every - // caller slot. Release it after the full PHP statement and - // also during C++ exception unwinding into a PHP catch block. - $cleanupGuard = $this->genTmpVarName(); - $this->context->beforeStmtLines[] = 'php::ArrayCleanupGuard ' . $cleanupGuard - . '{' . $variadicVar . '};'; - $this->context->afterStmtLines[] = $cleanupGuard . '.cleanup();'; - } } ksort($resolvedArgs); return implode(', ', $resolvedArgs); diff --git a/tests/compiler/variadic/by-reference-variadic-local.phpt b/tests/compiler/variadic/by-reference-variadic-local.phpt new file mode 100644 index 00000000..27200f94 --- /dev/null +++ b/tests/compiler/variadic/by-reference-variadic-local.phpt @@ -0,0 +1,45 @@ +--TEST-- +by-ref variadic writes back to local variable arguments +--FILE-- +bump($b->p); + var_dump($b->p); + $arr = [7]; + bump($arr[0]); + var_dump($arr[0]); +} +?> +--EXPECT-- +int(11) +int(41) +int(12) +int(51) +int(15) +int(17)