Skip to content

Preserve array internal pointer during copy-on-write separation - #23620

Open
darkdi wants to merge 1 commit into
php:masterfrom
darkdi:fix/preserve-array-internal-pointer
Open

Preserve array internal pointer during copy-on-write separation#23620
darkdi wants to merge 1 commit into
php:masterfrom
darkdi:fix/preserve-array-internal-pointer

Conversation

@darkdi

@darkdi darkdi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fix array internal pointer reset during copy-on-write separation

When an array's internal pointer is past the last element, zend_array_dup() currently clamps it to zero for packed and mixed arrays. A subsequent write that separates a copy therefore makes key() and current() observe the first element, even though the source array was already past the end.

The immutable duplication path preserves nInternalPointer verbatim. Keep the same behavior in the packed and mixed mutable paths. Add a PHPT covering both representations.

The test reproduces the issue on the current PHP binary before the fix. git diff --check passes; a full source build is not available in this environment.

Fixes GH-23617.

Signed-off-by: Dmitry Rantovov <rantovov5@gmail.com>
@devnexen

devnexen commented Sep 8, 2026

Copy link
Copy Markdown
Member

Hi @darkdi thanks for the patch but I m afraid the fix is really incorrect, I ll let ilaal review tough.

@iliaal

iliaal commented Sep 8, 2026

Copy link
Copy Markdown
Member

Direction looks right: matching the immutable path lines up with the MIN(nInternalPointer, nNumUsed) clamp tail deletion already uses, and an exhausted cursor picking up a later append is what the same code does with no separation at all. Two problems though.

The .phpt expectation is wrong and the test fails on this branch. end(); next(); then an append gives "d" at refcount 1 with no copy involved, so NULL is not a behaviour being restored. The test body prints "d", 4, 3, 4 here.

And it is incomplete for mixed arrays with holes, where zend_array_dup_elements() compacts and target->nNumUsed ends up below the preserved pointer:

array no separation separated, this PR
['a'=>1,'b'=>2,'c'=>3] 'zz' 'zz'
same, two keys unset 'zz' NULL
8 keys, 3 interior unset NULL NULL

Setting target->nInternalPointer = idx fixes row 2 but breaks row 3, since a full table rehashes on the append and zend_hash_rehash() does not remap an exhausted pointer either. So no single copied value agrees with the middle column everywhere; normalising the exhausted cursor wherever compaction happens, rehash included, looks like the actual fix.

The NULL expectation came from my issue text, which was wrong. I have corrected GH-23617.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internal pointer past the end resets to the first element on copy-on-write separation

3 participants