Skip to content

Commit 269d547

Browse files
committed
PoC
1 parent 7de83e2 commit 269d547

File tree

1 file changed

+63
-14
lines changed

1 file changed

+63
-14
lines changed

ext/standard/array.c

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3864,6 +3864,38 @@ static zend_always_inline void php_array_replace_wrapper(INTERNAL_FUNCTION_PARAM
38643864
}
38653865
/* }}} */
38663866

3867+
static bool prepare_in_place_array_modify_if_possible(const zend_execute_data *execute_data, const zval *arg)
3868+
{
3869+
ZEND_ASSERT(HT_IS_PACKED(Z_ARRVAL_P(arg)));
3870+
3871+
/* 2 refs: the CV and the argument */
3872+
if (Z_REFCOUNT_P(arg) != 2) {
3873+
return false;
3874+
}
3875+
/* If it has holes, it might get sequentialized */
3876+
if (!HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(arg))) {
3877+
return false;
3878+
}
3879+
/* Immutable => no modification allowed */
3880+
if (GC_FLAGS(Z_ARRVAL_P(arg)) & IS_ARRAY_IMMUTABLE) {
3881+
return false;
3882+
}
3883+
3884+
const zend_op *call_opline = execute_data->prev_execute_data->opline;
3885+
const zend_op *next_opline = call_opline + 1;
3886+
zval *var = ZEND_CALL_VAR(execute_data->prev_execute_data, next_opline->op1.var);
3887+
3888+
/* Must be an assignment to the same array */
3889+
if (next_opline->opcode != ZEND_ASSIGN || next_opline->op2.var != call_opline->result.var || Z_ARRVAL_P(arg) != Z_ARRVAL_P(var)) {
3890+
return false;
3891+
}
3892+
3893+
/* Must set the CV to NULL so we don't destroy the array on assignment */
3894+
ZVAL_NULL(var);
3895+
3896+
return true;
3897+
}
3898+
38673899
static zend_always_inline void php_array_merge_wrapper(INTERNAL_FUNCTION_PARAMETERS, int recursive) /* {{{ */
38683900
{
38693901
zval *args = NULL;
@@ -3925,22 +3957,35 @@ static zend_always_inline void php_array_merge_wrapper(INTERNAL_FUNCTION_PARAMET
39253957

39263958
arg = args;
39273959
src = Z_ARRVAL_P(arg);
3928-
/* copy first array */
3929-
array_init_size(return_value, count);
3930-
dest = Z_ARRVAL_P(return_value);
3960+
bool add_ref = false;
3961+
/* copy first array if necessary */
39313962
if (HT_IS_PACKED(src)) {
3932-
zend_hash_real_init_packed(dest);
3933-
ZEND_HASH_FILL_PACKED(dest) {
3934-
ZEND_HASH_PACKED_FOREACH_VAL(src, src_entry) {
3935-
if (UNEXPECTED(Z_ISREF_P(src_entry) &&
3936-
Z_REFCOUNT_P(src_entry) == 1)) {
3937-
src_entry = Z_REFVAL_P(src_entry);
3938-
}
3939-
Z_TRY_ADDREF_P(src_entry);
3940-
ZEND_HASH_FILL_ADD(src_entry);
3941-
} ZEND_HASH_FOREACH_END();
3942-
} ZEND_HASH_FILL_END();
3963+
if (prepare_in_place_array_modify_if_possible(execute_data, arg)) {
3964+
/* Make RC 1 such that the array may be modified, add_ref will make sure the refcount gets back to 2 at the end */
3965+
GC_DELREF(src);
3966+
add_ref = true;
3967+
dest = src;
3968+
ZVAL_ARR(return_value, dest);
3969+
} else {
3970+
array_init_size(return_value, count);
3971+
dest = Z_ARRVAL_P(return_value);
3972+
3973+
zend_hash_real_init_packed(dest);
3974+
ZEND_HASH_FILL_PACKED(dest) {
3975+
ZEND_HASH_PACKED_FOREACH_VAL(src, src_entry) {
3976+
if (UNEXPECTED(Z_ISREF_P(src_entry) &&
3977+
Z_REFCOUNT_P(src_entry) == 1)) {
3978+
src_entry = Z_REFVAL_P(src_entry);
3979+
}
3980+
Z_TRY_ADDREF_P(src_entry);
3981+
ZEND_HASH_FILL_ADD(src_entry);
3982+
} ZEND_HASH_FOREACH_END();
3983+
} ZEND_HASH_FILL_END();
3984+
}
39433985
} else {
3986+
array_init_size(return_value, count);
3987+
dest = Z_ARRVAL_P(return_value);
3988+
39443989
zend_string *string_key;
39453990
zend_hash_real_init_mixed(dest);
39463991
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(src, string_key, src_entry) {
@@ -3967,6 +4012,10 @@ static zend_always_inline void php_array_merge_wrapper(INTERNAL_FUNCTION_PARAMET
39674012
php_array_merge(dest, Z_ARRVAL_P(arg));
39684013
}
39694014
}
4015+
4016+
if (add_ref) {
4017+
GC_ADDREF(src);
4018+
}
39704019
}
39714020
/* }}} */
39724021

0 commit comments

Comments
 (0)