@@ -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+
38673899static 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