1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fix iterator position resetting

Previously, when an array was converted from packed to hashed, iterators would
not be correctly reset to 0. Similarly, removing the last element from an array
would decrease nNumUsed but not actually fix the iterator position, causing the
element to be skipped in the next iteration.

Some code was also removed that skips over IS_UNDEF elements for
nInternalPointer and iterator positions. This is unnecessary, as this already
happens during iteration.

Closes GH-13178
Closes GH-13188
This commit is contained in:
Ilija Tovilo
2024-01-18 15:47:59 +01:00
parent 6fa4286ba4
commit d653646841
8 changed files with 166 additions and 35 deletions

2
NEWS
View File

@@ -9,6 +9,8 @@ Core:
. Changed the type of PHP_DEBUG and PHP_ZTS constants to bool. (haszi)
. Fixed bug GH-13142 (Undefined variable name is shortened when contains \0).
(nielsdos)
. Fixed bug GH-13178 (Iterator positions incorrect when converting packed
array to hashed). (ilutov)
Curl:
. Deprecated the CURLOPT_BINARYTRANSFER constant. (divinity76)

26
Zend/tests/gh13178_1.phpt Normal file
View File

@@ -0,0 +1,26 @@
--TEST--
GH-13178: Packed to hash must reset iterator position
--FILE--
<?php
$array = ['foo'];
foreach ($array as $key => &$value) {
var_dump($key);
unset($array[$key]);
$array[] = 'foo';
if ($key === 10) {
break;
}
}
?>
--EXPECT--
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)

26
Zend/tests/gh13178_2.phpt Normal file
View File

@@ -0,0 +1,26 @@
--TEST--
GH-13178: Unsetting last offset must floor iterator position
--FILE--
<?php
$array = [100 => 'foo'];
foreach ($array as $key => &$value) {
var_dump($key);
unset($array[$key]);
$array[] = 'foo';
if ($key === 110) {
break;
}
}
?>
--EXPECT--
int(100)
int(101)
int(102)
int(103)
int(104)
int(105)
int(106)
int(107)
int(108)
int(109)
int(110)

24
Zend/tests/gh13178_3.phpt Normal file
View File

@@ -0,0 +1,24 @@
--TEST--
GH-13178: Unsetting last offset variation with hashed array
--FILE--
<?php
$data = ['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'];
foreach ($data as $key => &$value) {
var_dump($value);
if ($value === 'baz') {
unset($data['bar']);
unset($data['baz']);
$data['qux'] = 'qux';
$data['quux'] = 'quux';
}
}
?>
--EXPECT--
string(3) "foo"
string(3) "bar"
string(3) "baz"
string(3) "qux"
string(4) "quux"

29
Zend/tests/gh13178_4.phpt Normal file
View File

@@ -0,0 +1,29 @@
--TEST--
GH-13178: Packed to hash must reset nInternalPointer
--FILE--
<?php
$array = ['foo'];
reset($array);
while (true) {
$key = key($array);
next($array);
var_dump($key);
unset($array[$key]);
$array[] = 'foo';
if ($key === 10) {
break;
}
}
?>
--EXPECT--
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)

21
Zend/tests/gh13178_5.phpt Normal file
View File

@@ -0,0 +1,21 @@
--TEST--
GH-13178: Packed array with last elements removed must reset iterator positions
--FILE--
<?php
$array = [0, 1, 2];
foreach ($array as &$value) {
var_dump($value);
if ($value === 2) {
unset($array[2]);
unset($array[1]);
$array[1] = 3;
$array[2] = 4;
}
}
?>
--EXPECT--
int(0)
int(1)
int(2)
int(3)
int(4)

View File

@@ -1331,6 +1331,18 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht)
if (!(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) {
ht->nNumUsed = 0;
HT_HASH_RESET(ht);
/* Even if the array is empty, we still need to reset the iterator positions. */
ht->nInternalPointer = 0;
if (UNEXPECTED(HT_HAS_ITERATORS(ht))) {
HashTableIterator *iter = EG(ht_iterators);
HashTableIterator *end = iter + EG(ht_iterators_used);
while (iter != end) {
if (iter->ht == ht) {
iter->pos = 0;
}
iter++;
}
}
}
return;
}
@@ -1412,32 +1424,30 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht)
}
}
static zend_always_inline void zend_hash_iterators_clamp_max(HashTable *ht, uint32_t max)
{
if (UNEXPECTED(HT_HAS_ITERATORS(ht))) {
HashTableIterator *iter = EG(ht_iterators);
HashTableIterator *end = iter + EG(ht_iterators_used);
while (iter != end) {
if (iter->ht == ht) {
iter->pos = MIN(iter->pos, max);
}
iter++;
}
}
}
static zend_always_inline void _zend_hash_packed_del_val(HashTable *ht, uint32_t idx, zval *zv)
{
idx = HT_HASH_TO_IDX(idx);
ht->nNumOfElements--;
if (ht->nInternalPointer == idx || UNEXPECTED(HT_HAS_ITERATORS(ht))) {
uint32_t new_idx;
new_idx = idx;
while (1) {
new_idx++;
if (new_idx >= ht->nNumUsed) {
break;
} else if (Z_TYPE(ht->arPacked[new_idx]) != IS_UNDEF) {
break;
}
}
if (ht->nInternalPointer == idx) {
ht->nInternalPointer = new_idx;
}
zend_hash_iterators_update(ht, idx, new_idx);
}
if (ht->nNumUsed - 1 == idx) {
do {
ht->nNumUsed--;
} while (ht->nNumUsed > 0 && (UNEXPECTED(Z_TYPE(ht->arPacked[ht->nNumUsed-1]) == IS_UNDEF)));
ht->nInternalPointer = MIN(ht->nInternalPointer, ht->nNumUsed);
zend_hash_iterators_clamp_max(ht, ht->nNumUsed);
}
if (ht->pDestructor) {
zval tmp;
@@ -1458,28 +1468,12 @@ static zend_always_inline void _zend_hash_del_el_ex(HashTable *ht, uint32_t idx,
}
idx = HT_HASH_TO_IDX(idx);
ht->nNumOfElements--;
if (ht->nInternalPointer == idx || UNEXPECTED(HT_HAS_ITERATORS(ht))) {
uint32_t new_idx;
new_idx = idx;
while (1) {
new_idx++;
if (new_idx >= ht->nNumUsed) {
break;
} else if (Z_TYPE(ht->arData[new_idx].val) != IS_UNDEF) {
break;
}
}
if (ht->nInternalPointer == idx) {
ht->nInternalPointer = new_idx;
}
zend_hash_iterators_update(ht, idx, new_idx);
}
if (ht->nNumUsed - 1 == idx) {
do {
ht->nNumUsed--;
} while (ht->nNumUsed > 0 && (UNEXPECTED(Z_TYPE(ht->arData[ht->nNumUsed-1].val) == IS_UNDEF)));
ht->nInternalPointer = MIN(ht->nInternalPointer, ht->nNumUsed);
zend_hash_iterators_clamp_max(ht, ht->nNumUsed);
}
if (ht->pDestructor) {
zval tmp;

View File

@@ -3262,6 +3262,11 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
Z_TRY_ADDREF_P(entry);
zend_hash_next_index_insert_new(removed, entry);
zend_hash_packed_del_val(in_hash, entry);
/* Bump iterator positions to the element after replacement. */
if (idx == iter_pos) {
zend_hash_iterators_update(in_hash, idx, offset + length);
iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos + 1);
}
}
} else { /* otherwise just skip those entries */
int pos2 = pos;
@@ -3270,9 +3275,13 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
if (Z_TYPE_P(entry) == IS_UNDEF) continue;
pos2++;
zend_hash_packed_del_val(in_hash, entry);
/* Bump iterator positions to the element after replacement. */
if (idx == iter_pos) {
zend_hash_iterators_update(in_hash, idx, offset + length);
iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos + 1);
}
}
}
iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos);
/* If there are entries to insert.. */
if (replace) {