diff --git a/Zend/tests/gh24739.phpt b/Zend/tests/gh24739.phpt new file mode 100644 index 000000000000..a0504f8ac61d --- /dev/null +++ b/Zend/tests/gh24739.phpt @@ -0,0 +1,23 @@ +--TEST-- +array_splice() resets type info before destroying hash table +--ENV-- +a=1 +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(1) "1" + [1]=> + string(1) "1" +} diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index dc4c7a6c91dd..2c2003038a67 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1962,6 +1962,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_destroy(HashTable *ht) { IS_CONSISTENT(ht); HT_ASSERT(ht, GC_REFCOUNT(ht) <= 1); + ZEND_ASSERT(((GC_TYPE(ht) == IS_ARRAY && !(GC_TYPE_INFO(ht) & GC_INFO_MASK)) + || GC_TYPE(ht) == IS_NULL /* destroyed by GC */) + && "HashTables exposed to userland should be destroyed with zend_array_destroy()"); if (ht->nNumUsed) { if (HT_IS_PACKED(ht)) { diff --git a/ext/standard/array.c b/ext/standard/array.c index 39e23bf8738e..5fdf426472e7 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3456,7 +3456,11 @@ static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, H return; } + uint32_t type_info = GC_TYPE_INFO(in_hash); + GC_TYPE_INFO(in_hash) = GC_ARRAY; + zend_hash_destroy(in_hash); + GC_TYPE_INFO(in_hash) = type_info; HT_FLAGS(in_hash) = HT_FLAGS(&out_hash); in_hash->nTableSize = out_hash.nTableSize; @@ -3740,8 +3744,11 @@ PHP_FUNCTION(array_unshift) /* replace HashTable data */ Z_ARRVAL_P(stack)->pDestructor = NULL; + uint32_t type_info = GC_TYPE_INFO(Z_ARRVAL_P(stack)); + GC_TYPE_INFO(Z_ARRVAL_P(stack)) = GC_ARRAY; zend_hash_destroy(Z_ARRVAL_P(stack)); + GC_TYPE_INFO(Z_ARRVAL_P(stack)) = type_info; HT_FLAGS(Z_ARRVAL_P(stack)) = HT_FLAGS(&new_hash); Z_ARRVAL_P(stack)->nTableSize = new_hash.nTableSize; Z_ARRVAL_P(stack)->nTableMask = new_hash.nTableMask;