diff --git a/NEWS b/NEWS index 09c1ec9920e..185b491963c 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,8 @@ PHP NEWS binary const expr). (ilutov) . Fixed bug GH-19305 (Operands may be being released during comparison). (Arnaud) + . Fixed bug GH-19303 (Unpacking empty packed array into uninitialized array + causes assertion failure). (nielsdos) - FTP: . Fix theoretical issues with hrtime() not being available. (nielsdos) diff --git a/Zend/tests/array_unpack/gh19303.phpt b/Zend/tests/array_unpack/gh19303.phpt new file mode 100644 index 00000000000..af594c3740c --- /dev/null +++ b/Zend/tests/array_unpack/gh19303.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-19303 (Unpacking empty packed array into uninitialized array causes assertion failure) +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index d80496f3022..c40dd4cc8ed 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6262,17 +6262,22 @@ ZEND_VM_C_LABEL(add_unpack_again): zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a1c9831f8ce..a9f33224d82 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2721,17 +2721,22 @@ add_unpack_again: zval *val; if (HT_IS_PACKED(ht) && (zend_hash_num_elements(result_ht) == 0 || HT_IS_PACKED(result_ht))) { - zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); - ZEND_HASH_FILL_PACKED(result_ht) { - ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { - if (UNEXPECTED(Z_ISREF_P(val)) && - UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { - val = Z_REFVAL_P(val); - } - Z_TRY_ADDREF_P(val); - ZEND_HASH_FILL_ADD(val); - } ZEND_HASH_FOREACH_END(); - } ZEND_HASH_FILL_END(); + /* zend_hash_extend() skips initialization when the number of elements is 0, + * but the code below expects that result_ht is initialized as packed. + * We can just skip the work in that case. */ + if (result_ht->nNumUsed + zend_hash_num_elements(ht) > 0) { + zend_hash_extend(result_ht, result_ht->nNumUsed + zend_hash_num_elements(ht), 1); + ZEND_HASH_FILL_PACKED(result_ht) { + ZEND_HASH_PACKED_FOREACH_VAL(ht, val) { + if (UNEXPECTED(Z_ISREF_P(val)) && + UNEXPECTED(Z_REFCOUNT_P(val) == 1)) { + val = Z_REFVAL_P(val); + } + Z_TRY_ADDREF_P(val); + ZEND_HASH_FILL_ADD(val); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } } else { zend_string *key;