From e56e4250fbc389ae174c21a6a904df5c0b08d4c0 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 26 Oct 2026 11:48:59 +0100 Subject: [PATCH 1/2] Assert that destroyed hash tables are not in GC buffer --- Zend/zend_hash.c | 3 +++ ext/standard/array.c | 3 +++ 2 files changed, 6 insertions(+) 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..ddc2f6e27b8d 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3740,8 +3740,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; From a536dac29275658145e7bcd81a533123f65bff17 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 27 Oct 2026 13:43:35 +0100 Subject: [PATCH 2/2] Handle array_splice() --- Zend/tests/gh24739.phpt | 23 +++++++++++++++++++++++ ext/standard/array.c | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 Zend/tests/gh24739.phpt 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/ext/standard/array.c b/ext/standard/array.c index ddc2f6e27b8d..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;