Commit 947c556ba2b for php
commit 947c556ba2bc7f400d57494ab54f76e8daf69f11
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date: Tue Oct 27 16:24:23 2026 +0100
Assert that destroyed hash tables are not in GC buffer (#24739)
diff --git a/Zend/tests/gh24739.phpt b/Zend/tests/gh24739.phpt
new file mode 100644
index 00000000000..a0504f8ac61
--- /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--
+<?php
+
+$a = [getenv('a'), getenv('a'), getenv('a')];
+$b = $a;
+unset($b); // add to GC buffer
+
+array_splice($a, 1, 1);
+
+var_dump($a);
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(1) "1"
+ [1]=>
+ string(1) "1"
+}
diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c
index dc4c7a6c91d..2c2003038a6 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 8cc40a71ab9..50b45a8f304 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;