Repository navigation
Conversation
47ea5d5 to
eb27320
Compare
eb27320 to
f7b26a5
Compare
|
The shortcut is fixed, but a temporary first argument still keeps the old next index. The general path takes a refcount-1 array in place ( function holey() { $a = [1, 2, 3]; unset($a[2]); return $a; }
$r = array_merge(holey(), []);
$r[] = 'X';
var_dump(array_keys($r)); // 0, 1, 3 with this PR, 0, 1, 2 expectedThe same happens with Resetting it right after dest->nNextFreeElement = dest->nNumUsed; |
When one of the two arguments of array_merge() is empty, the other one is returned as is instead of being copied. The returned array is the argument itself, shared through copy-on-write, so it also kept the next free index of the argument. array_merge() renumbers integer keys from zero, so the next append to the result must use the element count. An argument whose next free index was stale, for example after unset() of its last element, made $result[] = $value use a wrong key. The shortcut now applies only when the argument already behaves like the result of the general code path: a packed array without holes, or an array with string keys only, whose next free index is the one the general path would produce. Everything else goes through the general path, which builds a fresh array. Clean arrays are still returned as is, so the copy is still avoided. The general path also takes a packed first argument in place when it is a temporary. It now resets the next free index there too, since a temporary with a stale index had the same problem. array_merge_recursive() shares the same code path and had the same bug.
f7b26a5 to
6be0d75
Compare
|
@ArtUkrainskiy Thanks for the precise review. Confirmed and fixed in 6be0d75: the in-place path now resets the next free index right after |
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 the next free index of the argument, whicharray_merge()is expected to reset because integer keys are renumbered from 0. The general code path does exactly that, and the shortcut is an optimization, so it is not supposed to be observable.array_values()had the same shortcut and got the same guard in cc96166 ("Don't optimize input arrays with suffix holes", 2017), for the same reason, in the same file, without an RFC.array_merge()just never got it.Today the same call site returns a differently shaped array depending on whether a variable happens to be empty at runtime:
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 one the general path would produce. Everything else goes through the general path, which builds a fresh array. 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:
array(3) { [0]=> int(1) [1]=> int(2) - [3]=> + [2]=> int(4) } array(2) { ["a"]=> int(1) - [1]=> + [0]=> int(3) }