From d66f3d7e10863f76714c28dd3b744e435479e200 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 1 Sep 2026 12:49:22 +0800 Subject: [PATCH] fix foreach reference on dynamic variables --- src/Parser/ForeachTrait.php | 12 ++- .../loop/foreach-byref-dynamic-variable.phpt | 88 +++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/compiler/loop/foreach-byref-dynamic-variable.phpt diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index 235157de..47f3fead 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -194,9 +194,15 @@ trait ForeachTrait $name = $this->parseIdentifier($node->expr); if ($this->hasVar($name)) { $type = $this->getVarType($name); - if ($type === Type::ARRAY) { - return $this->parseForeachIterable($node, $name); - } elseif ($type === Type::OBJECT) { + // A by-reference foreach must operate on the original variable. + // Copying a dynamically typed iterable into a temporary triggers + // normal PHP array COW, so references would update only that + // temporary instead of the source variable. ForeachIterator + // performs the runtime array/object validation itself. + if ($type === Type::ARRAY + || $type === Type::OBJECT + || ($node->byRef && ($type === Type::VAR || $type === Type::REF)) + ) { return $this->parseForeachIterable($node, $name); } elseif ($this->isStdContainerType($type)) { return $this->parseForeachStdContainer($node); diff --git a/tests/compiler/loop/foreach-byref-dynamic-variable.phpt b/tests/compiler/loop/foreach-byref-dynamic-variable.phpt new file mode 100644 index 00000000..4ade05e4 --- /dev/null +++ b/tests/compiler/loop/foreach-byref-dynamic-variable.phpt @@ -0,0 +1,88 @@ +--TEST-- +Foreach by reference mutates the original dynamically typed variable without a COW temporary +--FILE-- + 1], ['value' => 10]])); + var_dump(mutateMixed([['value' => 1], ['value' => 10]])); + var_dump(mutateNullable([['value' => 1], ['value' => 10]])); + var_dump(mutateTyped([['value' => 1], ['value' => 10]])); +} +?> +--EXPECT-- +array(2) { + [0]=> + array(1) { + ["value"]=> + int(2) + } + [1]=> + array(1) { + ["value"]=> + int(11) + } +} +array(2) { + [0]=> + array(1) { + ["value"]=> + int(3) + } + [1]=> + array(1) { + ["value"]=> + int(12) + } +} +array(2) { + [0]=> + array(1) { + ["value"]=> + int(4) + } + [1]=> + array(1) { + ["value"]=> + int(13) + } +} +array(2) { + [0]=> + array(1) { + ["value"]=> + int(5) + } + [1]=> + array(1) { + ["value"]=> + int(14) + } +}