Repository navigation
Conversation
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 two pieces of internal state that array_merge() is expected to reset. The next free index: 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 internal pointer: array_merge() returns an array whose pointer is on the first element. An argument whose pointer had been moved made current($result) return a wrong element. Both were observable and made array_merge([], $a) behave differently from array_merge($a, []). array_merge_recursive() shares the same code path and had the same two bugs. 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 element count (or unset) and whose internal pointer is on the first element. 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.
GromNaN
force-pushed
the
fix/array-merge-empty-array-result
branch
from
October 18, 2026 10:21
db7056e to
47ea5d5
Compare
Owner
Author
|
Superseded by php#24404 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
array_merge()returns the non-empty argument as is when the other argument is empty, instead of building a copy. This shortcut dates back to commit 05f3706.The shortcut was too broad. 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:array_merge()renumbers integer keys from zero, so the next append to the result must use the element count. An array whose next free index was stale, for example afterunset()of its last element, made$result[] = $valueuse a wrong key.array_merge()returns an array whose pointer is on the first element. An argument whose pointer was moved madecurrent($result)return a wrong element.Both were observable, and they made
array_merge([], $a)behave differently fromarray_merge($a, []):The shortcut now applies only when the argument already behaves like the result of the general code path:
Everything else goes through the general path, which builds a fresh array.
The change only affects arrays with unusual internal state. Clean arrays are still returned as is, and the copy is still avoided:
array_merge($list, [])array_merge([], $list)array_merge($map, [])array_merge([], $map)The general path column measures what the shortcut saves. The string keyed cases still walk the keys to check that they are strings, which is why they stay in the millisecond range. The shortcut also avoids allocating 33 MB for the list and 84 MB for the string keyed array. The two extra comparisons cost nothing measurable on the paths that take the shortcut.
array_merge_recursive()uses the same code path and gets the same fix.A regression test is added in
ext/standard/tests/array/array_merge_empty_array_result.phpt. It fails on the current master and passes with the fix.