diff options
Diffstat (limited to 'ext/standard/array.c')
-rw-r--r-- | ext/standard/array.c | 602 |
1 files changed, 365 insertions, 237 deletions
diff --git a/ext/standard/array.c b/ext/standard/array.c index 538460bdcc..cb78cad39c 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -46,9 +46,8 @@ #include "php_string.h" #include "php_rand.h" #include "zend_smart_str.h" -#ifdef HAVE_SPL +#include "zend_bitset.h" #include "ext/spl/spl_array.h" -#endif /* {{{ defines */ #define EXTR_OVERWRITE 0 @@ -821,9 +820,7 @@ PHP_FUNCTION(count) RETURN_LONG(cnt); break; case IS_OBJECT: { -#ifdef HAVE_SPL zval retval; -#endif /* first, we check if the handler is defined */ if (Z_OBJ_HT_P(array)->count_elements) { RETVAL_LONG(1); @@ -831,7 +828,6 @@ PHP_FUNCTION(count) return; } } -#ifdef HAVE_SPL /* if not and the object implements Countable we call its count() method */ if (instanceof_function(Z_OBJCE_P(array), spl_ce_Countable)) { zend_call_method_with_0_params(array, NULL, NULL, "count", &retval); @@ -841,7 +837,6 @@ PHP_FUNCTION(count) } return; } -#endif } default: RETURN_LONG(1); @@ -1412,11 +1407,15 @@ PHP_FUNCTION(max) } /* }}} */ -static int php_array_walk(HashTable *target_hash, zval *userdata, int recursive) /* {{{ */ +static int php_array_walk(zval *array, zval *userdata, int recursive) /* {{{ */ { zval args[3], /* Arguments to userland function */ retval, /* Return value - unused */ *zv; + HashTable *target_hash = HASH_OF(array); + HashPosition pos; + uint32_t ht_iter; + int result = SUCCESS; /* Set up known arguments */ ZVAL_UNDEF(&args[1]); @@ -1429,89 +1428,112 @@ static int php_array_walk(HashTable *target_hash, zval *userdata, int recursive) BG(array_walk_fci).params = args; BG(array_walk_fci).no_separation = 0; + zend_hash_internal_pointer_reset_ex(target_hash, &pos); + ht_iter = zend_hash_iterator_add(target_hash, pos); + /* Iterate through hash */ - zend_hash_internal_pointer_reset(target_hash); - while (!EG(exception) && (zv = zend_hash_get_current_data(target_hash)) != NULL) { + do { + /* Retrieve value */ + zv = zend_hash_get_current_data_ex(target_hash, &pos); + if (zv == NULL) { + break; + } + + /* Skip undefined indirect elements */ if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); if (Z_TYPE_P(zv) == IS_UNDEF) { - zend_hash_move_forward(target_hash); + zend_hash_move_forward_ex(target_hash, &pos); continue; } } - if (recursive && - (Z_TYPE_P(zv) == IS_ARRAY || - (Z_ISREF_P(zv) && Z_TYPE_P(Z_REFVAL_P(zv)) == IS_ARRAY))) { + + /* Ensure the value is a reference. Otherwise the location of the value may be freed. */ + ZVAL_MAKE_REF(zv); + + /* Retrieve key */ + zend_hash_get_current_key_zval_ex(target_hash, &args[1], &pos); + + /* Move to next element already now -- this mirrors the approach used by foreach + * and ensures proper behavior with regard to modifications. */ + zend_hash_move_forward_ex(target_hash, &pos); + + /* Back up hash position, as it may change */ + EG(ht_iterators)[ht_iter].pos = pos; + + if (recursive && Z_TYPE_P(Z_REFVAL_P(zv)) == IS_ARRAY) { HashTable *thash; zend_fcall_info orig_array_walk_fci; zend_fcall_info_cache orig_array_walk_fci_cache; + zval ref; + ZVAL_COPY_VALUE(&ref, zv); ZVAL_DEREF(zv); SEPARATE_ARRAY(zv); thash = Z_ARRVAL_P(zv); if (thash->u.v.nApplyCount > 1) { php_error_docref(NULL, E_WARNING, "recursion detected"); - if (userdata) { - zval_ptr_dtor(&args[2]); - } - return 0; + result = FAILURE; + break; } /* backup the fcall info and cache */ orig_array_walk_fci = BG(array_walk_fci); orig_array_walk_fci_cache = BG(array_walk_fci_cache); + Z_ADDREF(ref); thash->u.v.nApplyCount++; - php_array_walk(thash, userdata, recursive); - thash->u.v.nApplyCount--; + result = php_array_walk(zv, userdata, recursive); + if (Z_TYPE_P(Z_REFVAL(ref)) == IS_ARRAY && thash == Z_ARRVAL_P(Z_REFVAL(ref))) { + /* If the hashtable changed in the meantime, we'll "leak" this apply count + * increment -- our reference to thash is no longer valid. */ + thash->u.v.nApplyCount--; + } + zval_ptr_dtor(&ref); /* restore the fcall info and cache */ BG(array_walk_fci) = orig_array_walk_fci; BG(array_walk_fci_cache) = orig_array_walk_fci_cache; } else { - int was_ref = Z_ISREF_P(zv); - ZVAL_COPY(&args[0], zv); - /* Allocate space for key */ - zend_hash_get_current_key_zval(target_hash, &args[1]); - /* Call the userland function */ - if (zend_call_function(&BG(array_walk_fci), &BG(array_walk_fci_cache)) == SUCCESS) { - if (!was_ref && Z_ISREF(args[0])) { - /* copy reference back */ - zval garbage; - if (Z_REFCOUNT(args[0]) == 1) { - ZVAL_UNREF(&args[0]); - } - ZVAL_COPY_VALUE(&garbage, zv); - ZVAL_COPY_VALUE(zv, &args[0]); - zval_ptr_dtor(&garbage); - } else { - zval_ptr_dtor(&args[0]); - } + result = zend_call_function(&BG(array_walk_fci), &BG(array_walk_fci_cache)); + if (result == SUCCESS) { zval_ptr_dtor(&retval); - } else { - zval_ptr_dtor(&args[0]); - if (Z_TYPE(args[1]) != IS_UNDEF) { - zval_ptr_dtor(&args[1]); - ZVAL_UNDEF(&args[1]); - } - break; } + + zval_ptr_dtor(&args[0]); } if (Z_TYPE(args[1]) != IS_UNDEF) { zval_ptr_dtor(&args[1]); ZVAL_UNDEF(&args[1]); } - zend_hash_move_forward(target_hash); - } + + if (result == FAILURE) { + break; + } + + /* Reload array and position -- both may have changed */ + if (Z_TYPE_P(array) == IS_ARRAY) { + pos = zend_hash_iterator_pos_ex(ht_iter, array); + target_hash = Z_ARRVAL_P(array); + } else if (Z_TYPE_P(array) == IS_OBJECT) { + target_hash = Z_OBJPROP_P(array); + pos = zend_hash_iterator_pos(ht_iter, target_hash); + } else { + php_error_docref(NULL, E_WARNING, "Iterated value is no longer an array or object"); + result = FAILURE; + break; + } + } while (!EG(exception)); if (userdata) { zval_ptr_dtor(&args[2]); } - return 0; + zend_hash_iterator_del(ht_iter); + return result; } /* }}} */ @@ -1519,7 +1541,7 @@ static int php_array_walk(HashTable *target_hash, zval *userdata, int recursive) Apply a user function to every member of an array */ PHP_FUNCTION(array_walk) { - HashTable *array; + zval *array; zval *userdata = NULL; zend_fcall_info orig_array_walk_fci; zend_fcall_info_cache orig_array_walk_fci_cache; @@ -1528,14 +1550,14 @@ PHP_FUNCTION(array_walk) orig_array_walk_fci_cache = BG(array_walk_fci_cache); #ifndef FAST_ZPP - if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/f|z/", &array, &BG(array_walk_fci), &BG(array_walk_fci_cache), &userdata) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "A/f|z/", &array, &BG(array_walk_fci), &BG(array_walk_fci_cache), &userdata) == FAILURE) { BG(array_walk_fci) = orig_array_walk_fci; BG(array_walk_fci_cache) = orig_array_walk_fci_cache; return; } #else ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1) + Z_PARAM_ARRAY_OR_OBJECT_EX(array, 0, 1) Z_PARAM_FUNC(BG(array_walk_fci), BG(array_walk_fci_cache)) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(userdata, 0, 1) @@ -1557,7 +1579,7 @@ PHP_FUNCTION(array_walk) Apply a user function recursively to every member of an array */ PHP_FUNCTION(array_walk_recursive) { - HashTable *array; + zval *array; zval *userdata = NULL; zend_fcall_info orig_array_walk_fci; zend_fcall_info_cache orig_array_walk_fci_cache; @@ -1565,7 +1587,7 @@ PHP_FUNCTION(array_walk_recursive) orig_array_walk_fci = BG(array_walk_fci); orig_array_walk_fci_cache = BG(array_walk_fci_cache); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/f|z/", &array, &BG(array_walk_fci), &BG(array_walk_fci_cache), &userdata) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "A/f|z/", &array, &BG(array_walk_fci), &BG(array_walk_fci_cache), &userdata) == FAILURE) { BG(array_walk_fci) = orig_array_walk_fci; BG(array_walk_fci_cache) = orig_array_walk_fci_cache; return; @@ -1621,7 +1643,6 @@ static inline void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior) } } ZEND_HASH_FOREACH_END(); } else { - ZVAL_DEREF(value); if (Z_TYPE_P(value) == IS_LONG) { ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) { if (fast_equal_check_long(value, entry)) { @@ -1773,6 +1794,7 @@ PHP_FUNCTION(extract) zend_ulong num_key; int var_exists, count = 0; int extract_refs = 0; + int exception_thrown = 0; zend_array *symbol_table; zval var_array; @@ -1813,6 +1835,10 @@ PHP_FUNCTION(extract) } } + if (zend_forbid_dynamic_call("extract()") == FAILURE) { + return; + } + symbol_table = zend_rebuild_symbol_table(); #if 0 if (!symbol_table) { @@ -1851,9 +1877,6 @@ PHP_FUNCTION(extract) if (var_exists && ZSTR_LEN(var_name) == sizeof("GLOBALS")-1 && !strcmp(ZSTR_VAL(var_name), "GLOBALS")) { break; } - if (var_exists && ZSTR_LEN(var_name) == sizeof("this")-1 && !strcmp(ZSTR_VAL(var_name), "this") && EG(scope) && ZSTR_LEN(EG(scope)->name) != 0) { - break; - } ZVAL_STR_COPY(&final_name, var_name); break; @@ -1894,6 +1917,15 @@ PHP_FUNCTION(extract) if (Z_TYPE(final_name) == IS_STRING && php_valid_var_name(Z_STRVAL(final_name), Z_STRLEN(final_name))) { zval *orig_var; + + if (Z_STRLEN(final_name) == sizeof("this")-1 && !strcmp(Z_STRVAL(final_name), "this")) { + if (!exception_thrown) { + exception_thrown = 1; + zend_throw_error(NULL, "Cannot re-assign $this"); + } + zval_dtor(&final_name); + continue; + } if (extract_refs) { ZVAL_MAKE_REF(entry); @@ -1974,8 +2006,11 @@ PHP_FUNCTION(compact) return; } - symbol_table = zend_rebuild_symbol_table(); + if (zend_forbid_dynamic_call("compact()") == FAILURE) { + return; + } + symbol_table = zend_rebuild_symbol_table(); if (UNEXPECTED(symbol_table == NULL)) { return; } @@ -2114,7 +2149,7 @@ PHP_FUNCTION(array_fill_keys) #define RANGE_CHECK_LONG_INIT_ARRAY(start, end) do { \ zend_ulong __calc_size = (start - end) / lstep; \ if (__calc_size >= HT_MAX_SIZE - 1) { \ - php_error_docref(NULL, E_WARNING, "The supplied range exceeds the maximum array size: start=%pd end=%pd", end, start); \ + php_error_docref(NULL, E_WARNING, "The supplied range exceeds the maximum array size: start=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT, end, start); \ RETURN_FALSE; \ } \ size = (uint32_t)(__calc_size + 1); \ @@ -2281,7 +2316,7 @@ long_str: Z_TYPE_INFO(tmp) = IS_LONG; if (low > high) { /* Negative steps */ - if (low - high < lstep) { + if ((zend_ulong)(low - high) < lstep) { err = 1; goto err; } @@ -2295,7 +2330,7 @@ long_str: } } ZEND_HASH_FILL_END(); } else if (high > low) { /* Positive steps */ - if (high - low < lstep) { + if ((zend_ulong)(high - low) < lstep) { err = 1; goto err; } @@ -2331,7 +2366,7 @@ static void php_array_data_shuffle(zval *array) /* {{{ */ Bucket *p, temp; HashTable *hash; zend_long rnd_idx; - uint32_t n_left; + zend_long n_left; n_elems = zend_hash_num_elements(Z_ARRVAL_P(array)); @@ -2390,7 +2425,6 @@ static void php_array_data_shuffle(zval *array) /* {{{ */ } } } - HANDLE_BLOCK_INTERRUPTIONS(); hash->nNumUsed = n_elems; hash->nInternalPointer = 0; @@ -2406,7 +2440,6 @@ static void php_array_data_shuffle(zval *array) /* {{{ */ if (!(hash->u.flags & HASH_FLAG_PACKED)) { zend_hash_to_packed(hash); } - HANDLE_UNBLOCK_INTERRUPTIONS(); } /* }}} */ @@ -2426,12 +2459,12 @@ PHP_FUNCTION(shuffle) } /* }}} */ -static void php_splice(HashTable *in_hash, int offset, int length, HashTable *replace, HashTable *removed) /* {{{ */ +static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, HashTable *replace, HashTable *removed) /* {{{ */ { HashTable out_hash; /* Output hashtable */ - int num_in, /* Number of entries in the input hashtable */ - pos; /* Current position in the hashtable */ - uint idx; + zend_long num_in; /* Number of entries in the input hashtable */ + zend_long pos; /* Current position in the hashtable */ + uint32_t idx; Bucket *p; /* Pointer to hash bucket */ zval *entry; /* Hash entry */ uint32_t iter_pos = zend_hash_iterators_lower_pos(in_hash, 0); @@ -2470,7 +2503,7 @@ static void php_splice(HashTable *in_hash, int offset, int length, HashTable *re zend_hash_add_new(&out_hash, p->key, entry); } if (idx == iter_pos) { - if (idx != pos) { + if ((zend_long)idx != pos) { zend_hash_iterators_update(in_hash, idx, pos); } iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos + 1); @@ -2540,7 +2573,7 @@ static void php_splice(HashTable *in_hash, int offset, int length, HashTable *re zend_hash_add_new(&out_hash, p->key, entry); } if (idx == iter_pos) { - if (idx != pos) { + if ((zend_long)idx != pos) { zend_hash_iterators_update(in_hash, idx, pos); } iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos + 1); @@ -2899,7 +2932,7 @@ PHP_FUNCTION(array_splice) } /* Perform splice */ - php_splice(Z_ARRVAL_P(array), (int)offset, (int)length, repl_array ? Z_ARRVAL_P(repl_array) : NULL, rem_hash); + php_splice(Z_ARRVAL_P(array), offset, length, repl_array ? Z_ARRVAL_P(repl_array) : NULL, rem_hash); } /* }}} */ @@ -2967,7 +3000,7 @@ PHP_FUNCTION(array_slice) /* Start at the beginning and go until we hit offset */ pos = 0; - if (!preserve_keys && (Z_ARRVAL_P(input)->u.flags & HASH_FLAG_PACKED)) { + if ((Z_ARRVAL_P(input)->u.flags & HASH_FLAG_PACKED) && !preserve_keys) { zend_hash_real_init(Z_ARRVAL_P(return_value), 1); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), entry) { @@ -2978,8 +3011,12 @@ PHP_FUNCTION(array_slice) if (pos > offset + length) { break; } + if (UNEXPECTED(Z_ISREF_P(entry)) && + UNEXPECTED(Z_REFCOUNT_P(entry) == 1)) { + ZVAL_UNREF(entry); + } + Z_TRY_ADDREF_P(entry); ZEND_HASH_FILL_ADD(entry); - zval_add_ref(entry); } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FILL_END(); } else { @@ -3044,7 +3081,7 @@ PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */ convert_to_array_ex(dest_zval); add_next_index_null(dest_zval); } else if (Z_TYPE_P(dest_zval) == IS_ARRAY) { - if (UNEXPECTED(Z_ARRVAL_P(dest_zval)->nNextFreeElement > Z_ARRVAL_P(dest_zval)->nNumUsed)) { + if (UNEXPECTED(Z_ARRVAL_P(dest_zval)->nNextFreeElement > (zend_long)Z_ARRVAL_P(dest_zval)->nNumUsed)) { Z_ARRVAL_P(dest_zval)->nNextFreeElement = Z_ARRVAL_P(dest_zval)->nNumUsed; } } else { @@ -3092,24 +3129,32 @@ PHPAPI int php_array_merge(HashTable *dest, HashTable *src) /* {{{ */ zval *src_entry; zend_string *string_key; - ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) { - if (Z_REFCOUNTED_P(src_entry)) { - if (UNEXPECTED(Z_ISREF_P(src_entry)) - && UNEXPECTED(Z_REFCOUNT_P(src_entry) == 1)) { - ZVAL_UNREF(src_entry); - if (Z_REFCOUNTED_P(src_entry)) { - Z_ADDREF_P(src_entry); + if ((dest->u.flags & HASH_FLAG_PACKED) && (src->u.flags & HASH_FLAG_PACKED)) { + zend_hash_extend(dest, zend_hash_num_elements(dest) + zend_hash_num_elements(src), 1); + ZEND_HASH_FILL_PACKED(dest) { + ZEND_HASH_FOREACH_VAL(src, src_entry) { + if (UNEXPECTED(Z_ISREF_P(src_entry)) && + UNEXPECTED(Z_REFCOUNT_P(src_entry) == 1)) { + ZVAL_UNREF(src_entry); } + Z_TRY_ADDREF_P(src_entry); + ZEND_HASH_FILL_ADD(src_entry); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } else { + ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) { + if (UNEXPECTED(Z_ISREF_P(src_entry) && + Z_REFCOUNT_P(src_entry) == 1)) { + ZVAL_UNREF(src_entry); + } + Z_TRY_ADDREF_P(src_entry); + if (string_key) { + zend_hash_update(dest, string_key, src_entry); } else { - Z_ADDREF_P(src_entry); + zend_hash_next_index_insert_new(dest, src_entry); } - } - if (string_key) { - zend_hash_update(dest, string_key, src_entry); - } else { - zend_hash_next_index_insert_new(dest, src_entry); - } - } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + } return 1; } /* }}} */ @@ -3185,7 +3230,7 @@ static inline void php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAMETE { zval *args = NULL; zval *arg; - int argc, i, init_size = 0; + int argc, i; #ifndef FAST_ZPP if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { @@ -3204,96 +3249,79 @@ static inline void php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAMETE if (Z_TYPE_P(arg) != IS_ARRAY) { php_error_docref(NULL, E_WARNING, "Argument #%d is not an array", i + 1); RETURN_NULL(); - } else { - int num = zend_hash_num_elements(Z_ARRVAL_P(arg)); - - if (num > init_size) { - init_size = num; - } } } - array_init_size(return_value, init_size); if (replace) { - zend_string *string_key; - zval *src_entry; - zend_ulong idx; - HashTable *src, *dest; + HashTable *dest; /* copy first array */ arg = args; ZVAL_DEREF(arg); - src = Z_ARRVAL_P(arg); - dest = Z_ARRVAL_P(return_value); - ZEND_HASH_FOREACH_KEY_VAL(src, idx, string_key, src_entry) { - if (UNEXPECTED(Z_ISREF_P(src_entry) && Z_REFCOUNT_P(src_entry) == 1)) { - src_entry = Z_REFVAL_P(src_entry); - } - if (string_key) { - if (Z_REFCOUNTED_P(src_entry)) { - Z_ADDREF_P(src_entry); - } - zend_hash_add_new(dest, string_key, src_entry); - } else { - if (Z_REFCOUNTED_P(src_entry)) { - Z_ADDREF_P(src_entry); - } - zend_hash_index_add_new(dest, idx, src_entry); - } - } ZEND_HASH_FOREACH_END(); - + dest = zend_array_dup(Z_ARRVAL_P(arg)); + ZVAL_ARR(return_value, dest); if (recursive) { for (i = 1; i < argc; i++) { arg = args + i; ZVAL_DEREF(arg); - php_array_replace_recursive(Z_ARRVAL_P(return_value), Z_ARRVAL_P(arg)); + php_array_replace_recursive(dest, Z_ARRVAL_P(arg)); } } else { for (i = 1; i < argc; i++) { arg = args + i; ZVAL_DEREF(arg); - zend_hash_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(arg), zval_add_ref, 1); + zend_hash_merge(dest, Z_ARRVAL_P(arg), zval_add_ref, 1); } } } else { - zend_string *string_key; zval *src_entry; HashTable *src, *dest; - /* copy first array */ arg = args; ZVAL_DEREF(arg); src = Z_ARRVAL_P(arg); + /* copy first array */ + array_init_size(return_value, zend_hash_num_elements(src)); dest = Z_ARRVAL_P(return_value); - ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) { - if (UNEXPECTED(Z_ISREF_P(src_entry) && Z_REFCOUNT_P(src_entry) == 1)) { - src_entry = Z_REFVAL_P(src_entry); - } - if (string_key) { - if (Z_REFCOUNTED_P(src_entry)) { - Z_ADDREF_P(src_entry); + if (src->u.flags & HASH_FLAG_PACKED) { + zend_hash_real_init(dest, 1); + ZEND_HASH_FILL_PACKED(dest) { + ZEND_HASH_FOREACH_VAL(src, src_entry) { + if (UNEXPECTED(Z_ISREF_P(src_entry) && + Z_REFCOUNT_P(src_entry) == 1)) { + ZVAL_UNREF(src_entry); + } + Z_TRY_ADDREF_P(src_entry); + ZEND_HASH_FILL_ADD(src_entry); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } else { + zend_string *string_key; + ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) { + if (UNEXPECTED(Z_ISREF_P(src_entry) && + Z_REFCOUNT_P(src_entry) == 1)) { + ZVAL_UNREF(src_entry); } - zend_hash_add_new(dest, string_key, src_entry); - } else { - if (Z_REFCOUNTED_P(src_entry)) { - Z_ADDREF_P(src_entry); + Z_TRY_ADDREF_P(src_entry); + if (string_key) { + zend_hash_add_new(dest, string_key, src_entry); + } else { + zend_hash_next_index_insert_new(dest, src_entry); } - zend_hash_next_index_insert_new(dest, src_entry); - } - } ZEND_HASH_FOREACH_END(); - + } ZEND_HASH_FOREACH_END(); + } if (recursive) { for (i = 1; i < argc; i++) { arg = args + i; ZVAL_DEREF(arg); - php_array_merge_recursive(Z_ARRVAL_P(return_value), Z_ARRVAL_P(arg)); + php_array_merge_recursive(dest, Z_ARRVAL_P(arg)); } } else { for (i = 1; i < argc; i++) { arg = args + i; ZVAL_DEREF(arg); - php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(arg)); + php_array_merge(dest, Z_ARRVAL_P(arg)); } } } @@ -3514,7 +3542,7 @@ zend_bool array_column_param_helper(zval *param, } /* }}} */ -static inline zval *array_column_fetch_prop(zval *data, zval *name, zval *rv) +static inline zval *array_column_fetch_prop(zval *data, zval *name, zval *rv) /* {{{ */ { zval *prop = NULL; @@ -3544,6 +3572,7 @@ static inline zval *array_column_fetch_prop(zval *data, zval *name, zval *rv) return prop; } +/* }}} */ /* {{{ proto array array_column(array input, mixed column_key[, mixed index_key]) Return the values from a single column in the input array, identified by the @@ -3563,42 +3592,62 @@ PHP_FUNCTION(array_column) RETURN_FALSE; } - array_init(return_value); - ZEND_HASH_FOREACH_VAL(arr_hash, data) { - ZVAL_DEREF(data); - - if (!zcolumn) { - zcolval = data; - } else if ((zcolval = array_column_fetch_prop(data, zcolumn, &rvc)) == NULL) { - continue; - } + array_init_size(return_value, zend_hash_num_elements(arr_hash)); + if (!zkey) { + zend_hash_real_init(Z_ARRVAL_P(return_value), 1); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + ZEND_HASH_FOREACH_VAL(arr_hash, data) { + ZVAL_DEREF(data); + if (!zcolumn) { + zcolval = data; + Z_TRY_ADDREF_P(zcolval); + } else if ((zcolval = array_column_fetch_prop(data, zcolumn, &rvc)) == NULL) { + continue; + } else if (zcolval != &rvc) { + Z_TRY_ADDREF_P(zcolval); + } + ZEND_HASH_FILL_ADD(zcolval); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } else { + ZEND_HASH_FOREACH_VAL(arr_hash, data) { + ZVAL_DEREF(data); - /* Failure will leave zkeyval alone which will land us on the final else block below - * which is to append the value as next_index - */ - if (zkey) { - zkeyval = array_column_fetch_prop(data, zkey, &rvk); - } + if (!zcolumn) { + zcolval = data; + Z_TRY_ADDREF_P(zcolval); + } else if ((zcolval = array_column_fetch_prop(data, zcolumn, &rvc)) == NULL) { + continue; + } else if (zcolval != &rvc) { + Z_TRY_ADDREF_P(zcolval); + } - Z_TRY_ADDREF_P(zcolval); - if (zkeyval && Z_TYPE_P(zkeyval) == IS_STRING) { - zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR_P(zkeyval), zcolval); - } else if (zkeyval && Z_TYPE_P(zkeyval) == IS_LONG) { - add_index_zval(return_value, Z_LVAL_P(zkeyval), zcolval); - } else if (zkeyval && Z_TYPE_P(zkeyval) == IS_OBJECT) { - zend_string *key = zval_get_string(zkeyval); - zend_symtable_update(Z_ARRVAL_P(return_value), key, zcolval); - zend_string_release(key); - } else { - add_next_index_zval(return_value, zcolval); - } - if (zcolval == &rvc) { - zval_ptr_dtor(&rvc); - } - if (zkeyval == &rvk) { - zval_ptr_dtor(&rvk); - } - } ZEND_HASH_FOREACH_END(); + /* Failure will leave zkeyval alone which will land us on the final else block below + * which is to append the value as next_index + */ + if (zkey) { + zkeyval = array_column_fetch_prop(data, zkey, &rvk); + } + if (zkeyval) { + if (Z_TYPE_P(zkeyval) == IS_STRING) { + zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR_P(zkeyval), zcolval); + } else if (Z_TYPE_P(zkeyval) == IS_LONG) { + add_index_zval(return_value, Z_LVAL_P(zkeyval), zcolval); + } else if (Z_TYPE_P(zkeyval) == IS_OBJECT) { + zend_string *key = zval_get_string(zkeyval); + zend_symtable_update(Z_ARRVAL_P(return_value), key, zcolval); + zend_string_release(key); + } else { + add_next_index_zval(return_value, zcolval); + } + if (zkeyval == &rvk) { + zval_ptr_dtor(&rvk); + } + } else { + add_next_index_zval(return_value, zcolval); + } + } ZEND_HASH_FOREACH_END(); + } } /* }}} */ @@ -3618,20 +3667,32 @@ PHP_FUNCTION(array_reverse) /* Initialize return array */ array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input))); - - ZEND_HASH_REVERSE_FOREACH_KEY_VAL(Z_ARRVAL_P(input), num_key, string_key, entry) { - if (string_key) { - entry = zend_hash_add_new(Z_ARRVAL_P(return_value), string_key, entry); - } else { - if (preserve_keys) { - entry = zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, entry); + if ((Z_ARRVAL_P(input)->u.flags & HASH_FLAG_PACKED) && !preserve_keys) { + zend_hash_real_init(Z_ARRVAL_P(return_value), 1); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + ZEND_HASH_REVERSE_FOREACH_VAL(Z_ARRVAL_P(input), entry) { + if (UNEXPECTED(Z_ISREF_P(entry) && + Z_REFCOUNT_P(entry) == 1)) { + ZVAL_UNREF(entry); + } + Z_TRY_ADDREF_P(entry); + ZEND_HASH_FILL_ADD(entry); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + } else { + ZEND_HASH_REVERSE_FOREACH_KEY_VAL(Z_ARRVAL_P(input), num_key, string_key, entry) { + if (string_key) { + entry = zend_hash_add_new(Z_ARRVAL_P(return_value), string_key, entry); } else { - entry = zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), entry); + if (preserve_keys) { + entry = zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, entry); + } else { + entry = zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), entry); + } } - } - - zval_add_ref(entry); - } ZEND_HASH_FOREACH_END(); + zval_add_ref(entry); + } ZEND_HASH_FOREACH_END(); + } } /* }}} */ @@ -3668,31 +3729,56 @@ PHP_FUNCTION(array_pad) } num_pads = pad_size_abs - input_size; - array_init_size(return_value, pad_size_abs); if (Z_REFCOUNTED_P(pad_value)) { GC_REFCOUNT(Z_COUNTED_P(pad_value)) += num_pads; } - if (pad_size < 0) { - for (i = 0; i < num_pads; i++) { - zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), pad_value); + array_init_size(return_value, pad_size_abs); + if (Z_ARRVAL_P(input)->u.flags & HASH_FLAG_PACKED) { + zend_hash_real_init(Z_ARRVAL_P(return_value), 1); + + if (pad_size < 0) { + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + for (i = 0; i < num_pads; i++) { + ZEND_HASH_FILL_ADD(pad_value); + } + } ZEND_HASH_FILL_END(); } - } - ZEND_HASH_FOREACH_STR_KEY_VAL_IND(Z_ARRVAL_P(input), key, value) { - if (Z_REFCOUNTED_P(value)) { - Z_ADDREF_P(value); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), value) { + Z_TRY_ADDREF_P(value); + ZEND_HASH_FILL_ADD(value); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + + if (pad_size > 0) { + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + for (i = 0; i < num_pads; i++) { + ZEND_HASH_FILL_ADD(pad_value); + } + } ZEND_HASH_FILL_END(); } - if (key) { - zend_hash_add_new(Z_ARRVAL_P(return_value), key, value); - } else { - zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), value); + } else { + if (pad_size < 0) { + for (i = 0; i < num_pads; i++) { + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), pad_value); + } } - } ZEND_HASH_FOREACH_END(); - if (pad_size > 0) { - for (i = 0; i < num_pads; i++) { - zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), pad_value); + ZEND_HASH_FOREACH_STR_KEY_VAL_IND(Z_ARRVAL_P(input), key, value) { + Z_TRY_ADDREF_P(value); + if (key) { + zend_hash_add_new(Z_ARRVAL_P(return_value), key, value); + } else { + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), value); + } + } ZEND_HASH_FOREACH_END(); + + if (pad_size > 0) { + for (i = 0; i < num_pads; i++) { + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), pad_value); + } } } } @@ -4939,7 +5025,7 @@ PHP_FUNCTION(array_multisort) /* Make sure the arrays are of the same size. */ array_size = zend_hash_num_elements(Z_ARRVAL_P(arrays[0])); for (i = 0; i < num_arrays; i++) { - if (zend_hash_num_elements(Z_ARRVAL_P(arrays[i])) != array_size) { + if (zend_hash_num_elements(Z_ARRVAL_P(arrays[i])) != (uint32_t)array_size) { php_error_docref(NULL, E_WARNING, "Array sizes are inconsistent"); MULTISORT_ABORT; } @@ -4974,10 +5060,9 @@ PHP_FUNCTION(array_multisort) } /* Do the actual sort magic - bada-bim, bada-boom. */ - zend_qsort(indirect, array_size, sizeof(Bucket *), php_multisort_compare, (swap_func_t)array_bucket_p_sawp); + zend_sort(indirect, array_size, sizeof(Bucket *), php_multisort_compare, (swap_func_t)array_bucket_p_sawp); /* Restructure the arrays based on sorted indirect - this is mostly taken from zend_hash_sort() function. */ - HANDLE_BLOCK_INTERRUPTIONS(); for (i = 0; i < num_arrays; i++) { int repack; @@ -5001,7 +5086,6 @@ PHP_FUNCTION(array_multisort) zend_hash_rehash(hash); } } - HANDLE_UNBLOCK_INTERRUPTIONS(); /* Clean up. */ for (i = 0; i < array_size; i++) { @@ -5019,10 +5103,14 @@ PHP_FUNCTION(array_multisort) PHP_FUNCTION(array_rand) { zval *input; - zend_long randval, num_req = 1; - int num_avail; + zend_long num_req = 1; zend_string *string_key; zend_ulong num_key; + int i; + int num_avail; + zend_bitset bitset; + int negative_bitset = 0; + uint32_t bitset_len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|l", &input, &num_req) == FAILURE) { return; @@ -5030,46 +5118,86 @@ PHP_FUNCTION(array_rand) num_avail = zend_hash_num_elements(Z_ARRVAL_P(input)); - if (ZEND_NUM_ARGS() > 1) { - if (num_req <= 0 || num_req > num_avail) { - php_error_docref(NULL, E_WARNING, "Second argument has to be between 1 and the number of elements in the array"); - return; + if (num_avail == 0) { + php_error_docref(NULL, E_WARNING, "Array is empty"); + return; + } + + if (num_req == 1) { + HashTable *ht = Z_ARRVAL_P(input); + + /* Compact the hashtable if less than 3/4 of elements are used */ + if (num_avail < ht->nNumUsed - (ht->nNumUsed>>2)) { + if (ht->u.flags & HASH_FLAG_PACKED) { + zend_hash_packed_to_hash(ht); + } else { + zend_hash_rehash(ht); + } } + + /* Sample random buckets until we hit one that is not empty. + * The worst case probability of hitting an empty element is 1-3/4. The worst case + * probability of hitting N empty elements in a row is (1-3/4)**N. + * For N=5 this becomes smaller than 0.1%. */ + do { + zend_long randval = php_mt_rand_range(0, ht->nNumUsed - 1); + Bucket *bucket = &ht->arData[randval]; + if (!Z_ISUNDEF(bucket->val)) { + if (bucket->key) { + RETURN_STR_COPY(bucket->key); + } else { + RETURN_LONG(bucket->h); + } + } + } while (1); + } + + if (num_req <= 0 || num_req > num_avail) { + php_error_docref(NULL, E_WARNING, "Second argument has to be between 1 and the number of elements in the array"); + return; } /* Make the return value an array only if we need to pass back more than one result. */ - if (num_req > 1) { - array_init_size(return_value, (uint32_t)num_req); + array_init_size(return_value, (uint32_t)num_req); + if (num_req > (num_avail >> 1)) { + negative_bitset = 1; + num_req = num_avail - num_req; } - /* We can't use zend_hash_index_find() because the array may have string keys or gaps. */ - ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) { - if (!num_req) { - break; - } + ALLOCA_FLAG(use_heap); + bitset_len = zend_bitset_len(num_avail); + bitset = ZEND_BITSET_ALLOCA(bitset_len, use_heap); + zend_bitset_clear(bitset, bitset_len); - randval = php_rand(); + i = num_req; + while (i) { + zend_long randval = php_mt_rand_range(0, num_avail - 1); + if (!zend_bitset_in(bitset, randval)) { + zend_bitset_incl(bitset, randval); + i--; + } + } + /* i = 0; */ - if ((double) (randval / (PHP_RAND_MAX + 1.0)) < (double) num_req / (double) num_avail) { - /* If we are returning a single result, just do it. */ - if (Z_TYPE_P(return_value) != IS_ARRAY) { - if (string_key) { - RETURN_STR_COPY(string_key); - } else { - RETURN_LONG(num_key); - } - } else { - /* Append the result to the return value. */ + zend_hash_real_init(Z_ARRVAL_P(return_value), 1); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + zval zv; + /* We can't use zend_hash_index_find() + * because the array may have string keys or gaps. */ + ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(input), num_key, string_key) { + if (zend_bitset_in(bitset, i) ^ negative_bitset) { if (string_key) { - add_next_index_str(return_value, zend_string_copy(string_key)); + ZVAL_STR_COPY(&zv, string_key); } else { - add_next_index_long(return_value, num_key); + ZVAL_LONG(&zv, num_key); } + ZEND_HASH_FILL_ADD(&zv); } - num_req--; - } - num_avail--; - } ZEND_HASH_FOREACH_END(); + i++; + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FILL_END(); + + free_alloca(bitset, use_heap); } /* }}} */ |