diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index c2fdd2ba..f7d68649 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -305,7 +305,16 @@ protected function parseNativeCallArgs( } } - $variadicVar ??= $this->addTmpVar(Type::ARRAY); + if ($variadicVar === null) { + // Keep the aggregation array in the function preamble + // (addTmpVar) so a forward goto can never jump across a + // call-site C++ local declaration, and reset it instead. + // The reset runs on every evaluation of the call site, so a + // call inside a loop aggregates into an empty array every + // iteration instead of accumulating arguments. + $variadicVar = $this->addTmpVar(Type::ARRAY); + $this->context->beforeStmtLines[] = $variadicVar . '.unset();'; + } if ($arg->unpack) { $method = $argInfo->byRef ? 'mergeReferences' : 'merge'; $this->context->beforeStmtLines[] = $variadicVar . '.' . $method diff --git a/tests/compiler/variadic/loop-accumulation.phpt b/tests/compiler/variadic/loop-accumulation.phpt new file mode 100644 index 00000000..daec51ba --- /dev/null +++ b/tests/compiler/variadic/loop-accumulation.phpt @@ -0,0 +1,47 @@ +--TEST-- +Variadic arguments reset on every loop iteration instead of accumulating +--FILE-- + */ + public array $k = []; + public function add(...$els): void { + foreach ($els as $e) { $this->k[] = $e; } + } + public function cnt(): int { return count($this->k); } +} + +function main() +{ + $b = new Box(); + + // Positional variadic inside a for loop: 3 iterations x 2 args = 6. + for ($i = 0; $i < 3; $i++) { + $b->add(new Leaf(), new Leaf()); + } + + // Positional variadic inside a while loop: 2 iterations x 1 arg = 2. + $i = 0; + while ($i < 2) { + $b->add(new Leaf()); + $i++; + } + + // Unpacked variadic inside a for loop: 2 iterations x 2 args = 4. + $arr = [new Leaf(), new Leaf()]; + for ($i = 0; $i < 2; $i++) { + $b->add(...$arr); + } + + // Mixed positional + unpacked variadic enters the aggregation path + // (the single-unpack fast path does not): 2 iterations x 3 args = 6. + for ($i = 0; $i < 2; $i++) { + $b->add(new Leaf(), ...$arr); + } + + var_dump($b->cnt()); +} +?> +--EXPECT-- +int(18) diff --git a/tests/compiler/variadic/variadic-goto-skip.phpt b/tests/compiler/variadic/variadic-goto-skip.phpt new file mode 100644 index 00000000..e8a6c259 --- /dev/null +++ b/tests/compiler/variadic/variadic-goto-skip.phpt @@ -0,0 +1,19 @@ +--TEST-- +A forward goto may skip a by-value variadic call without crossing an initialization +--FILE-- + 0) { + goto finished; + } + count_values(1, 2); + finished: + echo "done\n"; +} +?> +--EXPECT-- +done