diff --git a/NEWS b/NEWS index 056f0296b68a..7740192f9c8c 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ PHP NEWS - Core: . Fixed out-of-bounds reads during automatic UTF-16/32 encoding detection. (Yudai Takada) + . Fixed incorrect foreach iterator positions when compacting arrays with + holes. (Weilin Du) . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or next() call on the inner generator). (iliaal) . Fixed bug GH-23232 (lone namespace separator asks the autoloader for an diff --git a/Zend/tests/array_dup_multiple_iterators.phpt b/Zend/tests/array_dup_multiple_iterators.phpt new file mode 100644 index 000000000000..db7dcf74a77a --- /dev/null +++ b/Zend/tests/array_dup_multiple_iterators.phpt @@ -0,0 +1,36 @@ +--TEST-- +Array duplication updates iterators at both a hole and the next defined element +--FILE-- + &$outerValue) { + $outerVisits[] = "$outerKey=>$outerValue"; + if ($first) { + $first = false; + foreach ($values as $innerKey => &$innerValue) { + $innerVisits[] = "$innerKey=>$innerValue"; + if ($innerValue === 11) { + // The outer cursor is at a hole, the inner at the next value. + unset($values['a'], $values['b']); + $copy = $values; + // Trigger copy-on-write duplication, which compacts the holes. + $values['i'] = 18; + } + } + unset($innerValue); + } + } + unset($outerValue); + echo 'outer: ', implode(' ', $outerVisits), "\n"; + echo 'inner: ', implode(' ', $innerVisits), "\n"; +} + +test(['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, + 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17]); +?> +--EXPECT-- +outer: a=>10 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 +inner: a=>10 b=>11 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 diff --git a/Zend/tests/rehash_multiple_iterators.phpt b/Zend/tests/rehash_multiple_iterators.phpt new file mode 100644 index 000000000000..cbbfc75d703a --- /dev/null +++ b/Zend/tests/rehash_multiple_iterators.phpt @@ -0,0 +1,40 @@ +--TEST-- +Rehashing updates iterators at both a hole and the next defined element +--FILE-- + &$outerValue) { + $outerVisits[] = "$outerKey=>$outerValue"; + if ($first) { + $first = false; + foreach ($values as $innerKey => &$innerValue) { + $innerVisits[] = "$innerKey=>$innerValue"; + if ($innerValue === 11) { + // The outer cursor is at a hole, the inner at the next value. + unset($values[$firstKey], $values[$secondKey]); + $values[$newKey] = $newValue; + } + } + unset($innerValue); + } + } + unset($outerValue); + echo 'outer: ', implode(' ', $outerVisits), "\n"; + echo 'inner: ', implode(' ', $innerVisits), "\n"; +} + +// Adding a string key converts packed storage and compacts its holes. +test([10, 11, 12, 13, 14], 0, 1, 'new', 15); + +// Inserting into a full mixed table compacts its holes without growing it. +test(['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, + 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17], 'a', 'b', 'i', 18); +?> +--EXPECT-- +outer: 0=>10 2=>12 3=>13 4=>14 new=>15 +inner: 0=>10 1=>11 2=>12 3=>13 4=>14 new=>15 +outer: a=>10 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 +inner: a=>10 b=>11 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 82d0318428fa..27ea16f208ab 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1412,7 +1412,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) do { zend_hash_iterators_update(ht, iter_pos, j); iter_pos = zend_hash_iterators_lower_pos(ht, iter_pos + 1); - } while (iter_pos < i); + } while (iter_pos <= i); } q++; j++; @@ -2428,7 +2428,7 @@ static zend_always_inline uint32_t zend_array_dup_elements(HashTable *source, Ha do { zend_hash_iterators_update(target, iter_pos, target_idx); iter_pos = zend_hash_iterators_lower_pos(target, iter_pos + 1); - } while (iter_pos < idx); + } while (iter_pos <= idx); } target_idx++; q++; }