Commit f045716288a for php.net
commit f045716288af47499fe5c87104e0d3db0570072d
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date: Mon Sep 15 22:07:38 2025 +0200
Fix incorrect HASH_FLAG_HAS_EMPTY_IND flag on userland array
Fixes GH-19839
Closes GH-19851
diff --git a/NEWS b/NEWS
index 028617dfae4..0891acce266 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,8 @@ PHP NEWS
exception are triggered). (nielsdos)
. Fixed bug GH-19653 (Closure named argument unpacking between temporary
closures can cause a crash). (nielsdos, Arnaud, Bob)
+ . Fixed bug GH-19839 (Incorrect HASH_FLAG_HAS_EMPTY_IND flag on userland
+ array). (ilutov)
- Date:
. Fixed GH-17159: "P" format for ::createFromFormat swallows string literals.
diff --git a/Zend/tests/gh19839.phpt b/Zend/tests/gh19839.phpt
new file mode 100644
index 00000000000..cc589ce0605
--- /dev/null
+++ b/Zend/tests/gh19839.phpt
@@ -0,0 +1,18 @@
+--TEST--
+GH-19839: Incorrect HASH_FLAG_HAS_EMPTY_IND flag on userland array
+--FILE--
+<?php
+
+const X = 'x';
+
+$x = null;
+unset(${X});
+
+$a = $GLOBALS;
+sort($a);
+serialize($a);
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c
index 07d5bed6d76..fdc05ff8cbf 100644
--- a/Zend/zend_hash.c
+++ b/Zend/zend_hash.c
@@ -2466,6 +2466,7 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
target->nTableSize = HT_MIN_SIZE;
HT_SET_DATA_ADDR(target, &uninitialized_bucket);
} else if (GC_FLAGS(source) & IS_ARRAY_IMMUTABLE) {
+ ZEND_ASSERT(!(HT_FLAGS(source) & HASH_FLAG_HAS_EMPTY_IND));
HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
target->nTableMask = source->nTableMask;
target->nNumUsed = source->nNumUsed;
@@ -2482,6 +2483,7 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
memcpy(HT_GET_DATA_ADDR(target), HT_GET_DATA_ADDR(source), HT_USED_SIZE(source));
}
} else if (HT_IS_PACKED(source)) {
+ ZEND_ASSERT(!(HT_FLAGS(source) & HASH_FLAG_HAS_EMPTY_IND));
HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
target->nTableMask = HT_MIN_MASK;
target->nNumUsed = source->nNumUsed;
@@ -2501,7 +2503,8 @@ ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source)
zend_array_dup_packed_elements(source, target, 1);
}
} else {
- HT_FLAGS(target) = HT_FLAGS(source) & HASH_FLAG_MASK;
+ /* Indirects are removed during duplication, remove HASH_FLAG_HAS_EMPTY_IND accordingly. */
+ HT_FLAGS(target) = HT_FLAGS(source) & (HASH_FLAG_MASK & ~HASH_FLAG_HAS_EMPTY_IND);
target->nTableMask = source->nTableMask;
target->nNextFreeElement = source->nNextFreeElement;
target->nInternalPointer =