Skip to content

Commit f9a755d

Browse files
committed
Fix HT flags copying wrt iterator count
HT_FLAGS() includes the full flag word, including the iterator count. When we're fully reassigning it, we need to make sure that we either really do want to copy the iterator count (as in some cases in array.c) or we need to mask only the actual flag byte. Add an assert to hash_iterators_del() to make sure the iterator count is non-zero (which is how I ran into this) and make sure that the iterator count is correctly preserved during array splicing.
1 parent 5ae49c4 commit f9a755d

File tree

3 files changed

+10
-4
lines changed

3 files changed

+10
-4
lines changed

Zend/zend_hash.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_iterator_del(uint32_t idx)
481481

482482
if (EXPECTED(iter->ht) && EXPECTED(iter->ht != HT_POISONED_PTR)
483483
&& EXPECTED(!HT_ITERATORS_OVERFLOW(iter->ht))) {
484+
ZEND_ASSERT(HT_ITERATORS_COUNT(iter->ht) != 0);
484485
HT_DEC_ITERATORS_COUNT(iter->ht);
485486
}
486487
iter->ht = NULL;
@@ -1928,15 +1929,16 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
19281929
target->pDestructor = ZVAL_PTR_DTOR;
19291930

19301931
if (source->nNumOfElements == 0) {
1931-
HT_FLAGS(target) = (HT_FLAGS(source) & ~(HASH_FLAG_INITIALIZED|HASH_FLAG_PACKED)) | HASH_FLAG_STATIC_KEYS;
1932+
uint32_t mask = HASH_FLAG_MASK & ~(HASH_FLAG_INITIALIZED|HASH_FLAG_PACKED);
1933+
HT_FLAGS(target) = (HT_FLAGS(source) & mask) | HASH_FLAG_STATIC_KEYS;
19321934
target->nTableMask = HT_MIN_MASK;
19331935
target->nNumUsed = 0;
19341936
target->nNumOfElements = 0;
19351937
target->nNextFreeElement = 0;
19361938
target->nInternalPointer = 0;
19371939
HT_SET_DATA_ADDR(target, &uninitialized_bucket);
19381940
} else if (GC_FLAGS(source) & IS_ARRAY_IMMUTABLE) {
1939-
HT_FLAGS(target) = HT_FLAGS(source);
1941+
HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
19401942
target->nTableMask = source->nTableMask;
19411943
target->nNumUsed = source->nNumUsed;
19421944
target->nNumOfElements = source->nNumOfElements;
@@ -1945,7 +1947,7 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
19451947
target->nInternalPointer = source->nInternalPointer;
19461948
memcpy(HT_GET_DATA_ADDR(target), HT_GET_DATA_ADDR(source), HT_USED_SIZE(source));
19471949
} else if (HT_FLAGS(source) & HASH_FLAG_PACKED) {
1948-
HT_FLAGS(target) = HT_FLAGS(source);
1950+
HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
19491951
target->nTableMask = HT_MIN_MASK;
19501952
target->nNumUsed = source->nNumUsed;
19511953
target->nNumOfElements = source->nNumOfElements;
@@ -1963,7 +1965,7 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
19631965
zend_array_dup_packed_elements(source, target, 1);
19641966
}
19651967
} else {
1966-
HT_FLAGS(target) = HT_FLAGS(source);
1968+
HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
19671969
target->nTableMask = source->nTableMask;
19681970
target->nNextFreeElement = source->nNextFreeElement;
19691971
target->nInternalPointer =

Zend/zend_hash.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
#define HASH_FLAG_HAS_EMPTY_IND (1<<5)
4141
#define HASH_FLAG_ALLOW_COW_VIOLATION (1<<6)
4242

43+
/* Only the low byte are real flags */
44+
#define HASH_FLAG_MASK 0xff
45+
4346
#define HT_FLAGS(ht) (ht)->u.flags
4447

4548
#define HT_IS_PACKED(ht) \

ext/standard/array.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3138,6 +3138,7 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H
31383138
}
31393139

31403140
/* replace HashTable data */
3141+
HT_SET_ITERATORS_COUNT(&out_hash, HT_ITERATORS_COUNT(in_hash));
31413142
HT_SET_ITERATORS_COUNT(in_hash, 0);
31423143
in_hash->pDestructor = NULL;
31433144
zend_hash_destroy(in_hash);

0 commit comments

Comments
 (0)