[php-src] PR #25432: Fix GH-17935: Separate ArrayObject and ArrayIterator storage on write
| From: | nicolas-grekas | Date: | Thu, 12 Nov 2026 19:06:22 +0000 |
| Subject: | [php-src] PR #25432: Fix GH-17935: Separate ArrayObject and ArrayIterator storage on write | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-40601@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/25432
Author: nicolas-grekas
Split from #23945.
ArrayObject and ArrayIterator assume that they're the only owner of their storage. But
__serialize(), __debugInfo(), var_export(),
json_encode() and the sort methods return or borrow this storage without a copy. A
write to the object then also changes the array that was returned, and debug builds fail the
refcount assertion of zend_hash.c. That's GH-17935.
This PR separates the storage before any write. When that happens during an iteration, the iterator
moves to the copy of its position that zend_array_dup() registers on the new table.
Object-backed storage isn't changed.
An error handler can now separate the storage by writing to the object. So read paths fetch the
table after the code that can call a handler. This also fixes use-after-frees when a handler calls
exchangeArray() during a read with a null offset, or while an element is created.
This is the delayed separation discussed in #17942. It overlaps with #20725, which separates at
three write sites, and it fixes the array-backed cases of #22921.
foreach over an ArrayObject needs about 3% more instructions (callgrind, 101 elements).
/cc @ndossche @iluuu1994 @alexandre-daubois @iliaal