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:
2
NEWS
2
NEWS
@@ -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
26
Zend/tests/gh13178_1.phpt
Normal 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
26
Zend/tests/gh13178_2.phpt
Normal 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
24
Zend/tests/gh13178_3.phpt
Normal 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
29
Zend/tests/gh13178_4.phpt
Normal 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
21
Zend/tests/gh13178_5.phpt
Normal 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)
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user