[php-src] PR #24404: ext/standard: Fix array_merge() with an empty array argument
| From: | GromNaN | Date: | Sun, 18 Oct 2026 10:21:22 +0000 |
| Subject: | [php-src] PR #24404: ext/standard: Fix array_merge() with an empty array argument | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39545@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24404
Author: GromNaN
array_merge() returns the non-empty argument as is when the other argument is empty.
The returned array is the argument itself, shared through copy-on-write, so it also kept two pieces
of internal state that array_merge() is expected to reset: the next free index, reset
because integer keys are renumbered from 0, and the internal pointer, rewound to the first element.
Both were observable:
```php
$a = [1, 2, 3];
unset($a[2]);
$r = array_merge($a, []);
$r[] = 4; // key 3 instead of 2
$c = [10, 20, 30];
next($c);
next($c);
current(array_merge([], $c)); // 30 instead of 10
```
The shortcut now applies only when the argument already behaves like the result of the general path:
a packed array without holes, or an array with string keys only, whose next free index is the
element count (or unset) and whose internal pointer is on the first element. Everything else goes
through the general path. Clean arrays are still returned as is, so the copy is still avoided.
array_merge_recursive() shares the code path and gets the same fix.
A regression test is added. Output before and after the change:
```diff
array(3) {
[0]=>
int(1)
[1]=>
int(2)
- [3]=>
+ [2]=>
int(4)
}
array(2) {
["a"]=>
int(1)
- [1]=>
+ [0]=>
int(3)
}
-int(30)
+int(10)
```