Commit a2486445bdb for php
commit a2486445bdb08851aff6c3dd48ab30a97db569de
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date: Tue Oct 27 16:28:34 2026 +0100
Destructor of regular arrays must be ZVAL_PTR_DTOR or NULL (#24727)
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 4c5d74b49e9..ba5768f0f5d 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -47,6 +47,10 @@ PHP 8.7 INTERNALS UPGRADE NOTES
- The INIT_CLASS_ENTRY_INIT_METHODS() macro, which was previously used as
part of handling disabled classes, was removed following the removal of
the `disable_classes` INI setting in PHP 8.5.
+- Using zend_array_destroy() on HashTables with a custom pDestructor is not
+ allowed anymore. User-facing HashTables must have pDestructor = ZVAL_PTR_DTOR
+ or NULL, and must be freed with zend_array_destroy(). Other HashTables can be
+ freed with zend_hash_destroy() + pefree().
========================
2. Build system changes
diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c
index 2c2003038a6..0b2ed1c8cbd 100644
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@ -2054,6 +2054,9 @@ ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht)
if (ht->nNumUsed) {
/* In some rare cases destructors of regular arrays may be changed */
if (UNEXPECTED(ht->pDestructor != ZVAL_PTR_DTOR)) {
+ if (UNEXPECTED(ht->pDestructor != NULL)) {
+ abort();
+ }
zend_hash_destroy(ht);
goto free_ht;
}
diff --git a/ext/dom/private_data.c b/ext/dom/private_data.c
index dd624a2c2e9..55d1fb5b43a 100644
--- a/ext/dom/private_data.c
+++ b/ext/dom/private_data.c
@@ -42,6 +42,14 @@ php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *p
return private_data == NULL ? NULL : &private_data->ns_mapper;
}
+static void php_dom_libxml_ns_mapper_prefix_map_dtor(zval *zv)
+{
+ HashTable *ht = Z_ARRVAL_P(zv);
+
+ zend_hash_destroy(ht);
+ efree(ht);
+}
+
php_dom_private_data *php_dom_private_data_create(void)
{
php_dom_private_data *private_data = emalloc(sizeof(*private_data));
@@ -49,7 +57,7 @@ php_dom_private_data *php_dom_private_data_create(void)
private_data->header.ns_hook = php_dom_libxml_private_data_ns_hook;
private_data->ns_mapper.html_ns = NULL;
private_data->ns_mapper.prefixless_xmlns_ns = NULL;
- zend_hash_init(&private_data->ns_mapper.uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false);
+ zend_hash_init(&private_data->ns_mapper.uri_to_prefix_map, 0, NULL, php_dom_libxml_ns_mapper_prefix_map_dtor, false);
private_data->template_fragments = NULL;
return private_data;
}
diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c
index 263b4561a67..986aac3d819 100644
--- a/ext/mbstring/mbstring.c
+++ b/ext/mbstring/mbstring.c
@@ -3570,8 +3570,7 @@ PHP_FUNCTION(mb_list_encodings)
/* Initialize shared array of supported encoding names
* This is done so that we can check if `mb_list_encodings()` is being
* passed to other mbstring functions using a cheap pointer equality check */
- HashTable *array = emalloc(sizeof(HashTable));
- zend_hash_init(array, 80, NULL, zval_ptr_dtor_str, false);
+ HashTable *array = zend_new_array(80);
for (const mbfl_encoding **encodings = mbfl_get_supported_encodings(); *encodings; encodings++) {
zval tmp;
ZVAL_STRING(&tmp, (*encodings)->name);