Permalink
Find file
5748 lines (5081 sloc) 158 KB
/*
+----------------------------------------------------------------------+
| PHP Version 7 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2016 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans <andi@zend.com> |
| Zeev Suraski <zeev@zend.com> |
| Rasmus Lerdorf <rasmus@php.net> |
| Andrei Zmievski <andrei@php.net> |
| Stig Venaas <venaas@php.net> |
| Jason Greene <jason@php.net> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#include "php.h"
#include "php_ini.h"
#include <stdarg.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef PHP_WIN32
#include "win32/unistd.h"
#endif
#include "zend_globals.h"
#include "zend_interfaces.h"
#include "php_globals.h"
#include "php_array.h"
#include "basic_functions.h"
#include "php_string.h"
#include "php_rand.h"
#include "php_math.h"
#include "zend_smart_str.h"
#include "zend_bitset.h"
#include "ext/spl/spl_array.h"
/* {{{ defines */
#define EXTR_OVERWRITE 0
#define EXTR_SKIP 1
#define EXTR_PREFIX_SAME 2
#define EXTR_PREFIX_ALL 3
#define EXTR_PREFIX_INVALID 4
#define EXTR_PREFIX_IF_EXISTS 5
#define EXTR_IF_EXISTS 6
#define EXTR_REFS 0x100
#define CASE_LOWER 0
#define CASE_UPPER 1
#define DIFF_NORMAL 1
#define DIFF_KEY 2
#define DIFF_ASSOC 6
#define DIFF_COMP_DATA_NONE -1
#define DIFF_COMP_DATA_INTERNAL 0
#define DIFF_COMP_DATA_USER 1
#define DIFF_COMP_KEY_INTERNAL 0
#define DIFF_COMP_KEY_USER 1
#define INTERSECT_NORMAL 1
#define INTERSECT_KEY 2
#define INTERSECT_ASSOC 6
#define INTERSECT_COMP_DATA_NONE -1
#define INTERSECT_COMP_DATA_INTERNAL 0
#define INTERSECT_COMP_DATA_USER 1
#define INTERSECT_COMP_KEY_INTERNAL 0
#define INTERSECT_COMP_KEY_USER 1
/* }}} */
ZEND_DECLARE_MODULE_GLOBALS(array)
/* {{{ php_array_init_globals
*/
static void php_array_init_globals(zend_array_globals *array_globals)
{
memset(array_globals, 0, sizeof(zend_array_globals));
}
/* }}} */
PHP_MINIT_FUNCTION(array) /* {{{ */
{
ZEND_INIT_MODULE_GLOBALS(array, php_array_init_globals, NULL);
REGISTER_LONG_CONSTANT("EXTR_OVERWRITE", EXTR_OVERWRITE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_SKIP", EXTR_SKIP, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_PREFIX_SAME", EXTR_PREFIX_SAME, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_PREFIX_ALL", EXTR_PREFIX_ALL, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_PREFIX_INVALID", EXTR_PREFIX_INVALID, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_PREFIX_IF_EXISTS", EXTR_PREFIX_IF_EXISTS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_IF_EXISTS", EXTR_IF_EXISTS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("EXTR_REFS", EXTR_REFS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_ASC", PHP_SORT_ASC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_DESC", PHP_SORT_DESC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_REGULAR", PHP_SORT_REGULAR, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_NUMERIC", PHP_SORT_NUMERIC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_STRING", PHP_SORT_STRING, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_LOCALE_STRING", PHP_SORT_LOCALE_STRING, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_NATURAL", PHP_SORT_NATURAL, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SORT_FLAG_CASE", PHP_SORT_FLAG_CASE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("CASE_LOWER", CASE_LOWER, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("CASE_UPPER", CASE_UPPER, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("COUNT_NORMAL", COUNT_NORMAL, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("COUNT_RECURSIVE", COUNT_RECURSIVE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("ARRAY_FILTER_USE_BOTH", ARRAY_FILTER_USE_BOTH, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("ARRAY_FILTER_USE_KEY", ARRAY_FILTER_USE_KEY, CONST_CS | CONST_PERSISTENT);
return SUCCESS;
}
/* }}} */
PHP_MSHUTDOWN_FUNCTION(array) /* {{{ */
{
#ifdef ZTS
ts_free_id(array_globals_id);
#endif
return SUCCESS;
}
/* }}} */
static int php_array_key_compare(const void *a, const void *b) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
zend_uchar t;
zend_long l1, l2;
double d;
if (f->key == NULL) {
if (s->key == NULL) {
return (zend_long)f->h > (zend_long)s->h ? 1 : -1;
} else {
l1 = (zend_long)f->h;
t = is_numeric_string(s->key->val, s->key->len, &l2, &d, 1);
if (t == IS_LONG) {
/* pass */
} else if (t == IS_DOUBLE) {
return ZEND_NORMALIZE_BOOL((double)l1 - d);
} else {
l2 = 0;
}
}
} else {
if (s->key) {
return zendi_smart_strcmp(f->key, s->key);
} else {
l2 = (zend_long)s->h;
t = is_numeric_string(f->key->val, f->key->len, &l1, &d, 1);
if (t == IS_LONG) {
/* pass */
} else if (t == IS_DOUBLE) {
return ZEND_NORMALIZE_BOOL(d - (double)l2);
} else {
l1 = 0;
}
}
}
return l1 > l2 ? 1 : (l1 < l2 ? -1 : 0);
}
/* }}} */
static int php_array_reverse_key_compare(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare(b, a);
}
/* }}} */
static int php_array_key_compare_numeric(const void *a, const void *b) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
if (f->key == NULL && s->key == NULL) {
return (zend_long)f->h > (zend_long)s->h ? 1 : -1;
} else {
double d1, d2;
if (f->key) {
d1 = zend_strtod(f->key->val, NULL);
} else {
d1 = (double)(zend_long)f->h;
}
if (s->key) {
d2 = zend_strtod(s->key->val, NULL);
} else {
d2 = (double)(zend_long)s->h;
}
return ZEND_NORMALIZE_BOOL(d1 - d2);
}
}
/* }}} */
static int php_array_reverse_key_compare_numeric(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_numeric(b, a);
}
/* }}} */
static int php_array_key_compare_string_case(const void *a, const void *b) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
char *s1, *s2;
size_t l1, l2;
char buf1[MAX_LENGTH_OF_LONG + 1];
char buf2[MAX_LENGTH_OF_LONG + 1];
if (f->key) {
s1 = f->key->val;
l1 = f->key->len;
} else {
s1 = zend_print_long_to_buf(buf1 + sizeof(buf1) - 1, f->h);
l1 = buf1 + sizeof(buf1) - 1 - s1;
}
if (s->key) {
s2 = s->key->val;
l2 = s->key->len;
} else {
s2 = zend_print_long_to_buf(buf2 + sizeof(buf2) - 1, s->h);
l2 = buf2 + sizeof(buf2) - 1 - s1;
}
return zend_binary_strcasecmp_l(s1, l1, s2, l2);
}
/* }}} */
static int php_array_reverse_key_compare_string_case(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_case(b, a);
}
/* }}} */
static int php_array_key_compare_string(const void *a, const void *b) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
char *s1, *s2;
size_t l1, l2;
char buf1[MAX_LENGTH_OF_LONG + 1];
char buf2[MAX_LENGTH_OF_LONG + 1];
if (f->key) {
s1 = f->key->val;
l1 = f->key->len;
} else {
s1 = zend_print_long_to_buf(buf1 + sizeof(buf1) - 1, f->h);
l1 = buf1 + sizeof(buf1) - 1 - s1;
}
if (s->key) {
s2 = s->key->val;
l2 = s->key->len;
} else {
s2 = zend_print_long_to_buf(buf2 + sizeof(buf2) - 1, s->h);
l2 = buf2 + sizeof(buf2) - 1 - s2;
}
return zend_binary_strcmp(s1, l1, s2, l2);
}
/* }}} */
static int php_array_reverse_key_compare_string(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string(b, a);
}
/* }}} */
static int php_array_key_compare_string_natural_general(const void *a, const void *b, int fold_case) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
char *s1, *s2;
size_t l1, l2;
char buf1[MAX_LENGTH_OF_LONG + 1];
char buf2[MAX_LENGTH_OF_LONG + 1];
if (f->key) {
s1 = f->key->val;
l1 = f->key->len;
} else {
s1 = zend_print_long_to_buf(buf1 + sizeof(buf1) - 1, f->h);
l1 = buf1 + sizeof(buf1) - 1 - s1;
}
if (s->key) {
s2 = s->key->val;
l2 = s->key->len;
} else {
s2 = zend_print_long_to_buf(buf2 + sizeof(buf2) - 1, s->h);
l2 = buf2 + sizeof(buf2) - 1 - s1;
}
return strnatcmp_ex(s1, l1, s2, l2, fold_case);
}
/* }}} */
static int php_array_key_compare_string_natural_case(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_natural_general(a, b, 1);
}
/* }}} */
static int php_array_reverse_key_compare_string_natural_case(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_natural_general(b, a, 1);
}
/* }}} */
static int php_array_key_compare_string_natural(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_natural_general(a, b, 0);
}
/* }}} */
static int php_array_reverse_key_compare_string_natural(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_natural_general(b, a, 0);
}
/* }}} */
#if HAVE_STRCOLL
static int php_array_key_compare_string_locale(const void *a, const void *b) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
char *s1, *s2;
char buf1[MAX_LENGTH_OF_LONG + 1];
char buf2[MAX_LENGTH_OF_LONG + 1];
if (f->key) {
s1 = f->key->val;
} else {
s1 = zend_print_long_to_buf(buf1 + sizeof(buf1) - 1, f->h);
}
if (s->key) {
s2 = s->key->val;
} else {
s2 = zend_print_long_to_buf(buf2 + sizeof(buf2) - 1, s->h);
}
return strcoll(s1, s2);
}
/* }}} */
static int php_array_reverse_key_compare_string_locale(const void *a, const void *b) /* {{{ */
{
return php_array_key_compare_string_locale(b, a);
}
/* }}} */
#endif
/* Numbers are always smaller than strings int this function as it
* anyway doesn't make much sense to compare two different data types.
* This keeps it consistent and simple.
*
* This is not correct any more, depends on what compare_func is set to.
*/
static int php_array_data_compare(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval result;
zval *first;
zval *second;
f = (Bucket *) a;
s = (Bucket *) b;
first = &f->val;
second = &s->val;
if (UNEXPECTED(Z_TYPE_P(first) == IS_INDIRECT)) {
first = Z_INDIRECT_P(first);
}
if (UNEXPECTED(Z_TYPE_P(second) == IS_INDIRECT)) {
second = Z_INDIRECT_P(second);
}
if (compare_function(&result, first, second) == FAILURE) {
return 0;
}
ZEND_ASSERT(Z_TYPE(result) == IS_LONG);
return Z_LVAL(result);
}
/* }}} */
static int php_array_reverse_data_compare(const void *a, const void *b) /* {{{ */
{
return php_array_data_compare(a, b) * -1;
}
/* }}} */
static int php_array_data_compare_numeric(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval *first;
zval *second;
f = (Bucket *) a;
s = (Bucket *) b;
first = &f->val;
second = &s->val;
if (UNEXPECTED(Z_TYPE_P(first) == IS_INDIRECT)) {
first = Z_INDIRECT_P(first);
}
if (UNEXPECTED(Z_TYPE_P(second) == IS_INDIRECT)) {
second = Z_INDIRECT_P(second);
}
return numeric_compare_function(first, second);
}
/* }}} */
static int php_array_reverse_data_compare_numeric(const void *a, const void *b) /* {{{ */
{
return php_array_data_compare_numeric(b, a);
}
/* }}} */
static int php_array_data_compare_string_case(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval *first;
zval *second;
f = (Bucket *) a;
s = (Bucket *) b;
first = &f->val;
second = &s->val;
if (UNEXPECTED(Z_TYPE_P(first) == IS_INDIRECT)) {
first = Z_INDIRECT_P(first);
}
if (UNEXPECTED(Z_TYPE_P(second) == IS_INDIRECT)) {
second = Z_INDIRECT_P(second);
}
return string_case_compare_function(first, second);
}
/* }}} */
static int php_array_reverse_data_compare_string_case(const void *a, const void *b) /* {{{ */
{
return php_array_data_compare_string_case(b, a);
}
/* }}} */
static int php_array_data_compare_string(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval *first;
zval *second;
f = (Bucket *) a;
s = (Bucket *) b;
first = &f->val;
second = &s->val;
if (UNEXPECTED(Z_TYPE_P(first) == IS_INDIRECT)) {
first = Z_INDIRECT_P(first);
}
if (UNEXPECTED(Z_TYPE_P(second) == IS_INDIRECT)) {
second = Z_INDIRECT_P(second);
}
return string_compare_function(first, second);
}
/* }}} */
static int php_array_reverse_data_compare_string(const void *a, const void *b) /* {{{ */
{
return php_array_data_compare_string(b, a);
}
/* }}} */
static int php_array_natural_general_compare(const void *a, const void *b, int fold_case) /* {{{ */
{
Bucket *f = (Bucket *) a;
Bucket *s = (Bucket *) b;
zend_string *str1 = zval_get_string(&f->val);
zend_string *str2 = zval_get_string(&s->val);
int result = strnatcmp_ex(ZSTR_VAL(str1), ZSTR_LEN(str1), ZSTR_VAL(str2), ZSTR_LEN(str2), fold_case);
zend_string_release(str1);
zend_string_release(str2);
return result;
}
/* }}} */
static int php_array_natural_compare(const void *a, const void *b) /* {{{ */
{
return php_array_natural_general_compare(a, b, 0);
}
/* }}} */
static int php_array_reverse_natural_compare(const void *a, const void *b) /* {{{ */
{
return php_array_natural_general_compare(b, a, 0);
}
/* }}} */
static int php_array_natural_case_compare(const void *a, const void *b) /* {{{ */
{
return php_array_natural_general_compare(a, b, 1);
}
/* }}} */
static int php_array_reverse_natural_case_compare(const void *a, const void *b) /* {{{ */
{
return php_array_natural_general_compare(b, a, 1);
}
/* }}} */
#if HAVE_STRCOLL
static int php_array_data_compare_string_locale(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval *first;
zval *second;
f = (Bucket *) a;
s = (Bucket *) b;
first = &f->val;
second = &s->val;
if (UNEXPECTED(Z_TYPE_P(first) == IS_INDIRECT)) {
first = Z_INDIRECT_P(first);
}
if (UNEXPECTED(Z_TYPE_P(second) == IS_INDIRECT)) {
second = Z_INDIRECT_P(second);
}
return string_locale_compare_function(first, second);
}
/* }}} */
static int php_array_reverse_data_compare_string_locale(const void *a, const void *b) /* {{{ */
{
return php_array_data_compare_string_locale(b, a);
}
/* }}} */
#endif
static compare_func_t php_get_key_compare_func(zend_long sort_type, int reverse) /* {{{ */
{
switch (sort_type & ~PHP_SORT_FLAG_CASE) {
case PHP_SORT_NUMERIC:
if (reverse) {
return php_array_reverse_key_compare_numeric;
} else {
return php_array_key_compare_numeric;
}
break;
case PHP_SORT_STRING:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_key_compare_string_case;
} else {
return php_array_key_compare_string_case;
}
} else {
if (reverse) {
return php_array_reverse_key_compare_string;
} else {
return php_array_key_compare_string;
}
}
break;
case PHP_SORT_NATURAL:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_key_compare_string_natural_case;
} else {
return php_array_key_compare_string_natural_case;
}
} else {
if (reverse) {
return php_array_reverse_key_compare_string_natural;
} else {
return php_array_key_compare_string_natural;
}
}
break;
#if HAVE_STRCOLL
case PHP_SORT_LOCALE_STRING:
if (reverse) {
return php_array_reverse_key_compare_string_locale;
} else {
return php_array_key_compare_string_locale;
}
break;
#endif
case PHP_SORT_REGULAR:
default:
if (reverse) {
return php_array_reverse_key_compare;
} else {
return php_array_key_compare;
}
break;
}
return NULL;
}
/* }}} */
static compare_func_t php_get_data_compare_func(zend_long sort_type, int reverse) /* {{{ */
{
switch (sort_type & ~PHP_SORT_FLAG_CASE) {
case PHP_SORT_NUMERIC:
if (reverse) {
return php_array_reverse_data_compare_numeric;
} else {
return php_array_data_compare_numeric;
}
break;
case PHP_SORT_STRING:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_data_compare_string_case;
} else {
return php_array_data_compare_string_case;
}
} else {
if (reverse) {
return php_array_reverse_data_compare_string;
} else {
return php_array_data_compare_string;
}
}
break;
case PHP_SORT_NATURAL:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_natural_case_compare;
} else {
return php_array_natural_case_compare;
}
} else {
if (reverse) {
return php_array_reverse_natural_compare;
} else {
return php_array_natural_compare;
}
}
break;
#if HAVE_STRCOLL
case PHP_SORT_LOCALE_STRING:
if (reverse) {
return php_array_reverse_data_compare_string_locale;
} else {
return php_array_data_compare_string_locale;
}
break;
#endif
case PHP_SORT_REGULAR:
default:
if (reverse) {
return php_array_reverse_data_compare;
} else {
return php_array_data_compare;
}
break;
}
return NULL;
}
/* }}} */
/* {{{ proto bool krsort(array &array_arg [, int sort_flags])
Sort an array by key value in reverse order */
PHP_FUNCTION(krsort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ARRAY_EX(array, 0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(sort_type)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
#endif
cmp = php_get_key_compare_func(sort_type, 1);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto bool ksort(array &array_arg [, int sort_flags])
Sort an array by key */
PHP_FUNCTION(ksort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ARRAY_EX(array, 0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(sort_type)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
#endif
cmp = php_get_key_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
PHPAPI zend_long php_count_recursive(zval *array, zend_long mode) /* {{{ */
{
zend_long cnt = 0;
zval *element;
if (Z_TYPE_P(array) == IS_ARRAY) {
if (Z_ARRVAL_P(array)->u.v.nApplyCount > 1) {
php_error_docref(NULL, E_WARNING, "recursion detected");
return 0;
}
cnt = zend_array_count(Z_ARRVAL_P(array));
if (mode == COUNT_RECURSIVE) {
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(array))) {
Z_ARRVAL_P(array)->u.v.nApplyCount++;
}
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), element) {
ZVAL_DEREF(element);
cnt += php_count_recursive(element, COUNT_RECURSIVE);
} ZEND_HASH_FOREACH_END();
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(array))) {
Z_ARRVAL_P(array)->u.v.nApplyCount--;
}
}
}
return cnt;
}
/* }}} */
/* {{{ proto int count(mixed var [, int mode])
Count the number of elements in a variable (usually an array) */
PHP_FUNCTION(count)
{
zval *array;
zend_long mode = COUNT_NORMAL;
zend_long cnt;
zval *element;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|l", &array, &mode) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ZVAL(array)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(mode)
ZEND_PARSE_PARAMETERS_END();
#endif
switch (Z_TYPE_P(array)) {
case IS_NULL:
RETURN_LONG(0);
break;
case IS_ARRAY:
cnt = zend_array_count(Z_ARRVAL_P(array));
if (mode == COUNT_RECURSIVE) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), element) {
ZVAL_DEREF(element);
cnt += php_count_recursive(element, COUNT_RECURSIVE);
} ZEND_HASH_FOREACH_END();
}
RETURN_LONG(cnt);
break;
case IS_OBJECT: {
zval retval;
/* first, we check if the handler is defined */
if (Z_OBJ_HT_P(array)->count_elements) {
RETVAL_LONG(1);
if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value))) {
return;
}
}
/* 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);
if (Z_TYPE(retval) != IS_UNDEF) {
RETVAL_LONG(zval_get_long(&retval));
zval_ptr_dtor(&retval);
}
return;
}
}
default:
RETURN_LONG(1);
break;
}
}
/* }}} */
static void php_natsort(INTERNAL_FUNCTION_PARAMETERS, int fold_case) /* {{{ */
{
zval *array;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &array) == FAILURE) {
return;
}
if (fold_case) {
if (zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_case_compare, 0) == FAILURE) {
return;
}
} else {
if (zend_hash_sort(Z_ARRVAL_P(array), php_array_natural_compare, 0) == FAILURE) {
return;
}
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto void natsort(array &array_arg)
Sort an array using natural sort */
PHP_FUNCTION(natsort)
{
php_natsort(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
/* {{{ proto void natcasesort(array &array_arg)
Sort an array using case-insensitive natural sort */
PHP_FUNCTION(natcasesort)
{
php_natsort(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
/* {{{ proto bool asort(array &array_arg [, int sort_flags])
Sort an array and maintain index association */
PHP_FUNCTION(asort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
cmp = php_get_data_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto bool arsort(array &array_arg [, int sort_flags])
Sort an array in reverse order and maintain index association */
PHP_FUNCTION(arsort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
cmp = php_get_data_compare_func(sort_type, 1);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto bool sort(array &array_arg [, int sort_flags])
Sort an array */
PHP_FUNCTION(sort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
cmp = php_get_data_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 1) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto bool rsort(array &array_arg [, int sort_flags])
Sort an array in reverse order */
PHP_FUNCTION(rsort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
cmp = php_get_data_compare_func(sort_type, 1);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 1) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
/* }}} */
static int php_array_user_compare(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval args[2];
zval retval;
f = (Bucket *) a;
s = (Bucket *) b;
ZVAL_COPY(&args[0], &f->val);
ZVAL_COPY(&args[1], &s->val);
BG(user_compare_fci).param_count = 2;
BG(user_compare_fci).params = args;
BG(user_compare_fci).retval = &retval;
BG(user_compare_fci).no_separation = 0;
if (zend_call_function(&BG(user_compare_fci), &BG(user_compare_fci_cache)) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
zend_long ret = zval_get_long(&retval);
zval_ptr_dtor(&retval);
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[0]);
return ret < 0 ? -1 : ret > 0 ? 1 : 0;
} else {
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[0]);
return 0;
}
}
/* }}} */
/* check if comparison function is valid */
#define PHP_ARRAY_CMP_FUNC_CHECK(func_name) \
if (!zend_is_callable(*func_name, 0, NULL)) { \
php_error_docref(NULL, E_WARNING, "Invalid comparison function"); \
BG(user_compare_fci) = old_user_compare_fci; \
BG(user_compare_fci_cache) = old_user_compare_fci_cache; \
RETURN_FALSE; \
} \
/* Clear FCI cache otherwise : for example the same or other array with
* (partly) the same key values has been sorted with uasort() or
* other sorting function the comparison is cached, however the name
* of the function for comparison is not respected. see bug #28739 AND #33295
*
* Following defines will assist in backup / restore values. */
#define PHP_ARRAY_CMP_FUNC_VARS \
zend_fcall_info old_user_compare_fci; \
zend_fcall_info_cache old_user_compare_fci_cache \
#define PHP_ARRAY_CMP_FUNC_BACKUP() \
old_user_compare_fci = BG(user_compare_fci); \
old_user_compare_fci_cache = BG(user_compare_fci_cache); \
BG(user_compare_fci_cache) = empty_fcall_info_cache; \
#define PHP_ARRAY_CMP_FUNC_RESTORE() \
BG(user_compare_fci) = old_user_compare_fci; \
BG(user_compare_fci_cache) = old_user_compare_fci_cache; \
static void php_usort(INTERNAL_FUNCTION_PARAMETERS, compare_func_t compare_func, zend_bool renumber) /* {{{ */
{
zval *array;
zend_array *arr;
zend_bool retval;
PHP_ARRAY_CMP_FUNC_VARS;
PHP_ARRAY_CMP_FUNC_BACKUP();
if (zend_parse_parameters(ZEND_NUM_ARGS(), "af", &array, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) {
PHP_ARRAY_CMP_FUNC_RESTORE();
return;
}
arr = Z_ARR_P(array);
if (zend_hash_num_elements(arr) == 0) {
PHP_ARRAY_CMP_FUNC_RESTORE();
RETURN_TRUE;
}
/* Copy array, so the in-place modifications will not be visible to the callback function */
arr = zend_array_dup(arr);
retval = zend_hash_sort(arr, compare_func, renumber) != FAILURE;
zval_ptr_dtor(array);
ZVAL_ARR(array, arr);
PHP_ARRAY_CMP_FUNC_RESTORE();
RETURN_BOOL(retval);
}
/* }}} */
/* {{{ proto bool usort(array array_arg, string cmp_function)
Sort an array by values using a user-defined comparison function */
PHP_FUNCTION(usort)
{
php_usort(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_array_user_compare, 1);
}
/* }}} */
/* {{{ proto bool uasort(array array_arg, string cmp_function)
Sort an array with a user-defined comparison function and maintain index association */
PHP_FUNCTION(uasort)
{
php_usort(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_array_user_compare, 0);
}
/* }}} */
static int php_array_user_key_compare(const void *a, const void *b) /* {{{ */
{
Bucket *f;
Bucket *s;
zval args[2];
zval retval;
zend_long result;
ZVAL_NULL(&args[0]);
ZVAL_NULL(&args[1]);
f = (Bucket *) a;
s = (Bucket *) b;
if (f->key == NULL) {
ZVAL_LONG(&args[0], f->h);
} else {
ZVAL_STR_COPY(&args[0], f->key);
}
if (s->key == NULL) {
ZVAL_LONG(&args[1], s->h);
} else {
ZVAL_STR_COPY(&args[1], s->key);
}
BG(user_compare_fci).param_count = 2;
BG(user_compare_fci).params = args;
BG(user_compare_fci).retval = &retval;
BG(user_compare_fci).no_separation = 0;
if (zend_call_function(&BG(user_compare_fci), &BG(user_compare_fci_cache)) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
result = zval_get_long(&retval);
zval_ptr_dtor(&retval);
} else {
result = 0;
}
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
return result < 0 ? -1 : result > 0 ? 1 : 0;
}
/* }}} */
/* {{{ proto bool uksort(array array_arg, string cmp_function)
Sort an array by keys using a user-defined comparison function */
PHP_FUNCTION(uksort)
{
php_usort(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_array_user_key_compare, 0);
}
/* }}} */
/* {{{ proto mixed end(array array_arg)
Advances array argument's internal pointer to the last element and return it */
PHP_FUNCTION(end)
{
HashTable *array;
zval *entry;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
zend_hash_internal_pointer_end(array);
if (USED_RET()) {
if ((entry = zend_hash_get_current_data(array)) == NULL) {
RETURN_FALSE;
}
if (Z_TYPE_P(entry) == IS_INDIRECT) {
entry = Z_INDIRECT_P(entry);
}
ZVAL_DEREF(entry);
ZVAL_COPY(return_value, entry);
}
}
/* }}} */
/* {{{ proto mixed prev(array array_arg)
Move array argument's internal pointer to the previous element and return it */
PHP_FUNCTION(prev)
{
HashTable *array;
zval *entry;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
zend_hash_move_backwards(array);
if (USED_RET()) {
if ((entry = zend_hash_get_current_data(array)) == NULL) {
RETURN_FALSE;
}
if (Z_TYPE_P(entry) == IS_INDIRECT) {
entry = Z_INDIRECT_P(entry);
}
ZVAL_DEREF(entry);
ZVAL_COPY(return_value, entry);
}
}
/* }}} */
/* {{{ proto mixed next(array array_arg)
Move array argument's internal pointer to the next element and return it */
PHP_FUNCTION(next)
{
HashTable *array;
zval *entry;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
zend_hash_move_forward(array);
if (USED_RET()) {
if ((entry = zend_hash_get_current_data(array)) == NULL) {
RETURN_FALSE;
}
if (Z_TYPE_P(entry) == IS_INDIRECT) {
entry = Z_INDIRECT_P(entry);
}
ZVAL_DEREF(entry);
ZVAL_COPY(return_value, entry);
}
}
/* }}} */
/* {{{ proto mixed reset(array array_arg)
Set array argument's internal pointer to the first element and return it */
PHP_FUNCTION(reset)
{
HashTable *array;
zval *entry;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H/", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT_EX(array, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
zend_hash_internal_pointer_reset(array);
if (USED_RET()) {
if ((entry = zend_hash_get_current_data(array)) == NULL) {
RETURN_FALSE;
}
if (Z_TYPE_P(entry) == IS_INDIRECT) {
entry = Z_INDIRECT_P(entry);
}
ZVAL_DEREF(entry);
ZVAL_COPY(return_value, entry);
}
}
/* }}} */
/* {{{ proto mixed current(array array_arg)
Return the element currently pointed to by the internal array pointer */
PHP_FUNCTION(current)
{
HashTable *array;
zval *entry;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT(array)
ZEND_PARSE_PARAMETERS_END();
#endif
if ((entry = zend_hash_get_current_data(array)) == NULL) {
RETURN_FALSE;
}
if (Z_TYPE_P(entry) == IS_INDIRECT) {
entry = Z_INDIRECT_P(entry);
}
ZVAL_DEREF(entry);
ZVAL_COPY(return_value, entry);
}
/* }}} */
/* {{{ proto mixed key(array array_arg)
Return the key of the element currently pointed to by the internal array pointer */
PHP_FUNCTION(key)
{
HashTable *array;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "H", &array) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_OR_OBJECT_HT(array)
ZEND_PARSE_PARAMETERS_END();
#endif
zend_hash_get_current_key_zval(array, return_value);
}
/* }}} */
/* {{{ proto mixed min(mixed arg1 [, mixed arg2 [, mixed ...]])
Return the lowest value in an array or a series of arguments */
PHP_FUNCTION(min)
{
int argc;
zval *args = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
return;
}
/* mixed min ( array $values ) */
if (argc == 1) {
zval *result;
if (Z_TYPE(args[0]) != IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "When only one parameter is given, it must be an array");
RETVAL_NULL();
} else {
if ((result = zend_hash_minmax(Z_ARRVAL(args[0]), php_array_data_compare, 0)) != NULL) {
ZVAL_DEREF(result);
ZVAL_COPY(return_value, result);
} else {
php_error_docref(NULL, E_WARNING, "Array must contain at least one element");
RETVAL_FALSE;
}
}
} else {
/* mixed min ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */
zval *min, result;
int i;
min = &args[0];
for (i = 1; i < argc; i++) {
is_smaller_function(&result, &args[i], min);
if (Z_TYPE(result) == IS_TRUE) {
min = &args[i];
}
}
ZVAL_DEREF(min);
ZVAL_COPY(return_value, min);
}
}
/* }}} */
/* {{{ proto mixed max(mixed arg1 [, mixed arg2 [, mixed ...]])
Return the highest value in an array or a series of arguments */
PHP_FUNCTION(max)
{
zval *args = NULL;
int argc;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
return;
}
/* mixed max ( array $values ) */
if (argc == 1) {
zval *result;
if (Z_TYPE(args[0]) != IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "When only one parameter is given, it must be an array");
RETVAL_NULL();
} else {
if ((result = zend_hash_minmax(Z_ARRVAL(args[0]), php_array_data_compare, 1)) != NULL) {
ZVAL_DEREF(result);
ZVAL_COPY(return_value, result);
} else {
php_error_docref(NULL, E_WARNING, "Array must contain at least one element");
RETVAL_FALSE;
}
}
} else {
/* mixed max ( mixed $value1 , mixed $value2 [, mixed $value3... ] ) */
zval *max, result;
int i;
max = &args[0];
for (i = 1; i < argc; i++) {
is_smaller_or_equal_function(&result, &args[i], max);
if (Z_TYPE(result) == IS_FALSE) {
max = &args[i];
}
}
ZVAL_DEREF(max);
ZVAL_COPY(return_value, max);
}
}
/* }}} */
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]);
if (userdata) {
ZVAL_COPY(&args[2], userdata);
}
BG(array_walk_fci).retval = &retval;
BG(array_walk_fci).param_count = userdata ? 3 : 2;
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 */
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_ex(target_hash, &pos);
continue;
}
}
/* 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");
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++;
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 {
ZVAL_COPY(&args[0], zv);
/* Call the userland function */
result = zend_call_function(&BG(array_walk_fci), &BG(array_walk_fci_cache));
if (result == SUCCESS) {
zval_ptr_dtor(&retval);
}
zval_ptr_dtor(&args[0]);
}
if (Z_TYPE(args[1]) != IS_UNDEF) {
zval_ptr_dtor(&args[1]);
ZVAL_UNDEF(&args[1]);
}
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]);
}
zend_hash_iterator_del(ht_iter);
return result;
}
/* }}} */
/* {{{ proto bool array_walk(array input, string funcname [, mixed userdata])
Apply a user function to every member of an array */
PHP_FUNCTION(array_walk)
{
zval *array;
zval *userdata = NULL;
zend_fcall_info orig_array_walk_fci;
zend_fcall_info_cache orig_array_walk_fci_cache;
orig_array_walk_fci = BG(array_walk_fci);
orig_array_walk_fci_cache = BG(array_walk_fci_cache);
#ifndef FAST_ZPP
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_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)
ZEND_PARSE_PARAMETERS_END_EX(
BG(array_walk_fci) = orig_array_walk_fci;
BG(array_walk_fci_cache) = orig_array_walk_fci_cache;
return
);
#endif
php_array_walk(array, userdata, 0);
BG(array_walk_fci) = orig_array_walk_fci;
BG(array_walk_fci_cache) = orig_array_walk_fci_cache;
RETURN_TRUE;
}
/* }}} */
/* {{{ proto bool array_walk_recursive(array input, string funcname [, mixed userdata])
Apply a user function recursively to every member of an array */
PHP_FUNCTION(array_walk_recursive)
{
zval *array;
zval *userdata = NULL;
zend_fcall_info orig_array_walk_fci;
zend_fcall_info_cache orig_array_walk_fci_cache;
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(), "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;
}
php_array_walk(array, userdata, 1);
BG(array_walk_fci) = orig_array_walk_fci;
BG(array_walk_fci_cache) = orig_array_walk_fci_cache;
RETURN_TRUE;
}
/* }}} */
/* void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior)
* 0 = return boolean
* 1 = return key
*/
static inline void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */
{
zval *value, /* value to check for */
*array, /* array to check in */
*entry; /* pointer to array entry */
zend_ulong num_idx;
zend_string *str_idx;
zend_bool strict = 0; /* strict comparison or not */
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "za|b", &value, &array, &strict) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_ZVAL(value)
Z_PARAM_ARRAY(array)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(strict)
ZEND_PARSE_PARAMETERS_END();
#endif
if (strict) {
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
ZVAL_DEREF(entry);
if (fast_is_identical_function(value, entry)) {
if (behavior == 0) {
RETURN_TRUE;
} else {
if (str_idx) {
RETVAL_STR_COPY(str_idx);
} else {
RETVAL_LONG(num_idx);
}
return;
}
}
} ZEND_HASH_FOREACH_END();
} else {
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)) {
if (behavior == 0) {
RETURN_TRUE;
} else {
if (str_idx) {
RETVAL_STR_COPY(str_idx);
} else {
RETVAL_LONG(num_idx);
}
return;
}
}
} ZEND_HASH_FOREACH_END();
} else if (Z_TYPE_P(value) == IS_STRING) {
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
if (fast_equal_check_string(value, entry)) {
if (behavior == 0) {
RETURN_TRUE;
} else {
if (str_idx) {
RETVAL_STR_COPY(str_idx);
} else {
RETVAL_LONG(num_idx);
}
return;
}
}
} ZEND_HASH_FOREACH_END();
} else {
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
if (fast_equal_check_function(value, entry)) {
if (behavior == 0) {
RETURN_TRUE;
} else {
if (str_idx) {
RETVAL_STR_COPY(str_idx);
} else {
RETVAL_LONG(num_idx);
}
return;
}
}
} ZEND_HASH_FOREACH_END();
}
}
RETURN_FALSE;
}
/* }}} */
/* {{{ proto bool in_array(mixed needle, array haystack [, bool strict])
Checks if the given value exists in the array */
PHP_FUNCTION(in_array)
{
php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
/* {{{ proto mixed array_search(mixed needle, array haystack [, bool strict])
Searches the array for a given value and returns the corresponding key if successful */
PHP_FUNCTION(array_search)
{
php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
static zend_always_inline int php_valid_var_name(char *var_name, size_t var_name_len) /* {{{ */
{
#if 1
/* first 256 bits for first character, and second 256 bits for the next */
static const uint32_t charset[16] = {
/* 31 0 63 32 95 64 127 96 */
0x00000000, 0x00000000, 0x87fffffe, 0x07fffffe,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
/* 31 0 63 32 95 64 127 96 */
0x00000000, 0x03ff0000, 0x87fffffe, 0x07fffffe,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
};
#endif
size_t i;
uint32_t ch;
if (UNEXPECTED(!var_name_len)) {
return 0;
}
/* These are allowed as first char: [a-zA-Z_\x7f-\xff] */
ch = (uint32_t)((unsigned char *)var_name)[0];
#if 1
if (UNEXPECTED(!(charset[ch >> 5] & (1 << (ch & 0x1f))))) {
#else
if (var_name[0] != '_' &&
(ch < 65 /* A */ || /* Z */ ch > 90) &&
(ch < 97 /* a */ || /* z */ ch > 122) &&
(ch < 127 /* 0x7f */ || /* 0xff */ ch > 255)
) {
#endif
return 0;
}
/* And these as the rest: [a-zA-Z0-9_\x7f-\xff] */
if (var_name_len > 1) {
i = 1;
do {
ch = (uint32_t)((unsigned char *)var_name)[i];
#if 1
if (UNEXPECTED(!(charset[8 + (ch >> 5)] & (1 << (ch & 0x1f))))) {
#else
if (var_name[i] != '_' &&
(ch < 48 /* 0 */ || /* 9 */ ch > 57) &&
(ch < 65 /* A */ || /* Z */ ch > 90) &&
(ch < 97 /* a */ || /* z */ ch > 122) &&
(ch < 127 /* 0x7f */ || /* 0xff */ ch > 255)
) {
#endif
return 0;
}
} while (++i < var_name_len);
}
return 1;
}
/* }}} */
PHPAPI int php_prefix_varname(zval *result, zval *prefix, char *var_name, size_t var_name_len, zend_bool add_underscore) /* {{{ */
{
ZVAL_NEW_STR(result, zend_string_alloc(Z_STRLEN_P(prefix) + (add_underscore ? 1 : 0) + var_name_len, 0));
memcpy(Z_STRVAL_P(result), Z_STRVAL_P(prefix), Z_STRLEN_P(prefix));
if (add_underscore) {
Z_STRVAL_P(result)[Z_STRLEN_P(prefix)] = '_';
}
memcpy(Z_STRVAL_P(result) + Z_STRLEN_P(prefix) + (add_underscore ? 1 : 0), var_name, var_name_len + 1);
return SUCCESS;
}
/* }}} */
/* {{{ proto int extract(array var_array [, int extract_type [, string prefix]])
Imports variables into symbol table from an array */
PHP_FUNCTION(extract)
{
zval *var_array_param, *prefix = NULL;
zend_long extract_type = EXTR_OVERWRITE;
zval *entry;
zend_string *var_name;
zend_ulong num_key;
int var_exists, count = 0;
int extract_refs = 0;
int exception_thrown = 0;
zend_array *symbol_table;
zval var_array;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|lz/", &var_array_param, &extract_type, &prefix) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_ARRAY(var_array_param)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(extract_type)
Z_PARAM_ZVAL_EX(prefix, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
extract_refs = (extract_type & EXTR_REFS);
if (extract_refs) {
SEPARATE_ZVAL(var_array_param);
}
extract_type &= 0xff;
if (extract_type < EXTR_OVERWRITE || extract_type > EXTR_IF_EXISTS) {
php_error_docref(NULL, E_WARNING, "Invalid extract type");
return;
}
if (extract_type > EXTR_SKIP && extract_type <= EXTR_PREFIX_IF_EXISTS && ZEND_NUM_ARGS() < 3) {
php_error_docref(NULL, E_WARNING, "specified extract type requires the prefix parameter");
return;
}
if (prefix) {
convert_to_string(prefix);
if (Z_STRLEN_P(prefix) && !php_valid_var_name(Z_STRVAL_P(prefix), Z_STRLEN_P(prefix))) {
php_error_docref(NULL, E_WARNING, "prefix is not a valid identifier");
return;
}
}
if (zend_forbid_dynamic_call("extract()") == FAILURE) {
return;
}
symbol_table = zend_rebuild_symbol_table();
#if 0
if (!symbol_table) {
php_error_docref(NULL, E_WARNING, "failed to build symbol table");
return;
}
#endif
/* The array might be stored in a local variable that will be overwritten. To avoid losing the
* reference in that case we work on a copy. */
ZVAL_COPY(&var_array, var_array_param);
ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL(var_array), num_key, var_name, entry) {
zval final_name;
ZVAL_NULL(&final_name);
var_exists = 0;
if (var_name) {
var_exists = zend_hash_exists_ind(symbol_table, var_name);
} else if (extract_type == EXTR_PREFIX_ALL || extract_type == EXTR_PREFIX_INVALID) {
zend_string *str = zend_long_to_str(num_key);
php_prefix_varname(&final_name, prefix, ZSTR_VAL(str), ZSTR_LEN(str), 1);
zend_string_release(str);
} else {
continue;
}
switch (extract_type) {
case EXTR_IF_EXISTS:
if (!var_exists) break;
/* break omitted intentionally */
case EXTR_OVERWRITE:
/* GLOBALS protection */
if (var_exists && ZSTR_LEN(var_name) == sizeof("GLOBALS")-1 && !strcmp(ZSTR_VAL(var_name), "GLOBALS")) {
break;
}
ZVAL_STR_COPY(&final_name, var_name);
break;
case EXTR_PREFIX_IF_EXISTS:
if (var_exists) {
php_prefix_varname(&final_name, prefix, ZSTR_VAL(var_name), ZSTR_LEN(var_name), 1);
}
break;
case EXTR_PREFIX_SAME:
if (!var_exists && ZSTR_LEN(var_name) != 0) {
ZVAL_STR_COPY(&final_name, var_name);
}
/* break omitted intentionally */
case EXTR_PREFIX_ALL:
if (Z_TYPE(final_name) == IS_NULL && ZSTR_LEN(var_name) != 0) {
php_prefix_varname(&final_name, prefix, ZSTR_VAL(var_name), ZSTR_LEN(var_name), 1);
}
break;
case EXTR_PREFIX_INVALID:
if (Z_TYPE(final_name) == IS_NULL) {
if (!php_valid_var_name(ZSTR_VAL(var_name), ZSTR_LEN(var_name))) {
php_prefix_varname(&final_name, prefix, ZSTR_VAL(var_name), ZSTR_LEN(var_name), 1);
} else {
ZVAL_STR_COPY(&final_name, var_name);
}
}
break;
default:
if (!var_exists) {
ZVAL_STR_COPY(&final_name, var_name);
}
break;
}
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);
Z_ADDREF_P(entry);
if ((orig_var = zend_hash_find(symbol_table, Z_STR(final_name))) != NULL) {
if (Z_TYPE_P(orig_var) == IS_INDIRECT) {
orig_var = Z_INDIRECT_P(orig_var);
}
zval_ptr_dtor(orig_var);
ZVAL_COPY_VALUE(orig_var, entry);
} else {
zend_hash_update(symbol_table, Z_STR(final_name), entry);
}
} else {
ZVAL_DEREF(entry);
if (Z_REFCOUNTED_P(entry)) Z_ADDREF_P(entry);
if ((orig_var = zend_hash_find(symbol_table, Z_STR(final_name))) != NULL) {
if (Z_TYPE_P(orig_var) == IS_INDIRECT) {
orig_var = Z_INDIRECT_P(orig_var);
}
ZVAL_DEREF(orig_var);
zval_ptr_dtor(orig_var);
ZVAL_COPY_VALUE(orig_var, entry);
} else {
zend_hash_update(symbol_table, Z_STR(final_name), entry);
}
}
count++;
}
zval_dtor(&final_name);
} ZEND_HASH_FOREACH_END();
zval_ptr_dtor(&var_array);
RETURN_LONG(count);
}
/* }}} */
static void php_compact_var(HashTable *eg_active_symbol_table, zval *return_value, zval *entry) /* {{{ */
{
zval *value_ptr, data;
ZVAL_DEREF(entry);
if (Z_TYPE_P(entry) == IS_STRING) {
if ((value_ptr = zend_hash_find_ind(eg_active_symbol_table, Z_STR_P(entry))) != NULL) {
ZVAL_DEREF(value_ptr);
ZVAL_COPY(&data, value_ptr);
zend_hash_update(Z_ARRVAL_P(return_value), Z_STR_P(entry), &data);
}
} else if (Z_TYPE_P(entry) == IS_ARRAY) {
if ((Z_ARRVAL_P(entry)->u.v.nApplyCount > 1)) {
php_error_docref(NULL, E_WARNING, "recursion detected");
return;
}
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(entry))) {
Z_ARRVAL_P(entry)->u.v.nApplyCount++;
}
ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL_P(entry), value_ptr) {
php_compact_var(eg_active_symbol_table, return_value, value_ptr);
} ZEND_HASH_FOREACH_END();
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(entry))) {
Z_ARRVAL_P(entry)->u.v.nApplyCount--;
}
}
}
/* }}} */
/* {{{ proto array compact(mixed var_names [, mixed ...])
Creates a hash containing variables and their values */
PHP_FUNCTION(compact)
{
zval *args = NULL; /* function arguments array */
uint32_t num_args, i;
zend_array *symbol_table;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
return;
}
if (zend_forbid_dynamic_call("compact()") == FAILURE) {
return;
}
symbol_table = zend_rebuild_symbol_table();
if (UNEXPECTED(symbol_table == NULL)) {
return;
}
/* compact() is probably most used with a single array of var_names
or multiple string names, rather than a combination of both.
So quickly guess a minimum result size based on that */
if (ZEND_NUM_ARGS() == 1 && Z_TYPE(args[0]) == IS_ARRAY) {
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL(args[0])));
} else {
array_init_size(return_value, ZEND_NUM_ARGS());
}
for (i=0; i<ZEND_NUM_ARGS(); i++) {
php_compact_var(symbol_table, return_value, &args[i]);
}
}
/* }}} */
/* {{{ proto array array_fill(int start_key, int num, mixed val)
Create an array containing num elements starting with index start_key each initialized to val */
PHP_FUNCTION(array_fill)
{
zval *val;
zend_long start_key, num;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz", &start_key, &num, &val) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_LONG(start_key)
Z_PARAM_LONG(num)
Z_PARAM_ZVAL(val)
ZEND_PARSE_PARAMETERS_END();
#endif
if (EXPECTED(num > 0)) {
if (sizeof(num) > 4 && UNEXPECTED(EXPECTED(num > 0x7fffffff))) {
php_error_docref(NULL, E_WARNING, "Too many elements");
RETURN_FALSE;
} else if (UNEXPECTED(start_key > ZEND_LONG_MAX - num + 1)) {
php_error_docref(NULL, E_WARNING, "Cannot add element to the array as the next element is already occupied");
RETURN_FALSE;
} else if (EXPECTED(start_key >= 0) && EXPECTED(start_key < num)) {
/* create packed array */
Bucket *p;
zend_long n;
array_init_size(return_value, (uint32_t)(start_key + num));
zend_hash_real_init(Z_ARRVAL_P(return_value), 1);
Z_ARRVAL_P(return_value)->nNumUsed = start_key + num;
Z_ARRVAL_P(return_value)->nNumOfElements = num;
Z_ARRVAL_P(return_value)->nInternalPointer = start_key;
Z_ARRVAL_P(return_value)->nNextFreeElement = start_key + num;
if (Z_REFCOUNTED_P(val)) {
GC_REFCOUNT(Z_COUNTED_P(val)) += num;
}
p = Z_ARRVAL_P(return_value)->arData;
n = start_key;
while (start_key--) {
ZVAL_UNDEF(&p->val);
p++;
}
while (num--) {
ZVAL_COPY_VALUE(&p->val, val);
p->h = n++;
p->key = NULL;
p++;
}
} else {
/* create hash */
array_init_size(return_value, (uint32_t)num);
zend_hash_real_init(Z_ARRVAL_P(return_value), 0);
if (Z_REFCOUNTED_P(val)) {
GC_REFCOUNT(Z_COUNTED_P(val)) += num;
}
zend_hash_index_add_new(Z_ARRVAL_P(return_value), start_key, val);
while (--num) {
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), val);
start_key++;
}
}
} else if (EXPECTED(num == 0)) {
array_init(return_value);
return;
} else {
php_error_docref(NULL, E_WARNING, "Number of elements can't be negative");
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto array array_fill_keys(array keys, mixed val)
Create an array using the elements of the first parameter as keys each initialized to val */
PHP_FUNCTION(array_fill_keys)
{
zval *keys, *val, *entry;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "az", &keys, &val) == FAILURE) {
return;
}
/* Initialize return array */
array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(keys)));
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(keys), entry) {
ZVAL_DEREF(entry);
Z_TRY_ADDREF_P(val);
if (Z_TYPE_P(entry) == IS_LONG) {
zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry), val);
} else {
zend_string *key = zval_get_string(entry);
zend_symtable_update(Z_ARRVAL_P(return_value), key, val);
zend_string_release(key);
}
} ZEND_HASH_FOREACH_END();
}
/* }}} */
#define RANGE_CHECK_DOUBLE_INIT_ARRAY(start, end) do { \
double __calc_size = ((start - end) / step) + 1; \
if (__calc_size >= (double)HT_MAX_SIZE) { \
php_error_docref(NULL, E_WARNING, "The supplied range exceeds the maximum array size: start=%0.0f end=%0.0f", end, start); \
RETURN_FALSE; \
} \
size = (uint32_t)_php_math_round(__calc_size, 0, PHP_ROUND_HALF_UP); \
array_init_size(return_value, size); \
zend_hash_real_init(Z_ARRVAL_P(return_value), 1); \
} while (0)
#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=" ZEND_LONG_FMT " end=" ZEND_LONG_FMT, end, start); \
RETURN_FALSE; \
} \
size = (uint32_t)(__calc_size + 1); \
array_init_size(return_value, size); \
zend_hash_real_init(Z_ARRVAL_P(return_value), 1); \
} while (0)
/* {{{ proto array range(mixed low, mixed high[, int step])
Create an array containing the range of integers or characters from low to high (inclusive) */
PHP_FUNCTION(range)
{
zval *zlow, *zhigh, *zstep = NULL, tmp;
int err = 0, is_step_double = 0;
double step = 1.0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|z", &zlow, &zhigh, &zstep) == FAILURE) {
RETURN_FALSE;
}
if (zstep) {
if (Z_TYPE_P(zstep) == IS_DOUBLE ||
(Z_TYPE_P(zstep) == IS_STRING && is_numeric_string(Z_STRVAL_P(zstep), Z_STRLEN_P(zstep), NULL, NULL, 0) == IS_DOUBLE)
) {
is_step_double = 1;
}
step = zval_get_double(zstep);
/* We only want positive step values. */
if (step < 0.0) {
step *= -1;
}
}
/* If the range is given as strings, generate an array of characters. */
if (Z_TYPE_P(zlow) == IS_STRING && Z_TYPE_P(zhigh) == IS_STRING && Z_STRLEN_P(zlow) >= 1 && Z_STRLEN_P(zhigh) >= 1) {
int type1, type2;
unsigned char low, high;
zend_long lstep = (zend_long) step;
type1 = is_numeric_string(Z_STRVAL_P(zlow), Z_STRLEN_P(zlow), NULL, NULL, 0);
type2 = is_numeric_string(Z_STRVAL_P(zhigh), Z_STRLEN_P(zhigh), NULL, NULL, 0);
if (type1 == IS_DOUBLE || type2 == IS_DOUBLE || is_step_double) {
goto double_str;
} else if (type1 == IS_LONG || type2 == IS_LONG) {
goto long_str;
}
low = (unsigned char)Z_STRVAL_P(zlow)[0];
high = (unsigned char)Z_STRVAL_P(zhigh)[0];
if (low > high) { /* Negative Steps */
if (lstep <= 0) {
err = 1;
goto err;
}
/* Initialize the return_value as an array. */
array_init_size(return_value, (uint32_t)(((low - high) / lstep) + 1));
zend_hash_real_init(Z_ARRVAL_P(return_value), 1);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (; low >= high; low -= (unsigned int)lstep) {
if (CG(one_char_string)[low]) {
ZVAL_INTERNED_STR(&tmp, CG(one_char_string)[low]);
} else {
ZVAL_STRINGL(&tmp, (char*)&low, 1);
}
ZEND_HASH_FILL_ADD(&tmp);
if (((signed int)low - lstep) < 0) {
break;
}
}
} ZEND_HASH_FILL_END();
} else if (high > low) { /* Positive Steps */
if (lstep <= 0) {
err = 1;
goto err;
}
array_init_size(return_value, (uint32_t)(((high - low) / lstep) + 1));
zend_hash_real_init(Z_ARRVAL_P(return_value), 1);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (; low <= high; low += (unsigned int)lstep) {
if (CG(one_char_string)[low]) {
ZVAL_INTERNED_STR(&tmp, CG(one_char_string)[low]);
} else {
ZVAL_STRINGL(&tmp, (char*)&low, 1);
}
ZEND_HASH_FILL_ADD(&tmp);
if (((signed int)low + lstep) > 255) {
break;
}
}
} ZEND_HASH_FILL_END();
} else {
array_init(return_value);
if (CG(one_char_string)[low]) {
ZVAL_INTERNED_STR(&tmp, CG(one_char_string)[low]);
} else {
ZVAL_STRINGL(&tmp, (char*)&low, 1);
}
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
}
} else if (Z_TYPE_P(zlow) == IS_DOUBLE || Z_TYPE_P(zhigh) == IS_DOUBLE || is_step_double) {
double low, high, element;
uint32_t i, size;
double_str:
low = zval_get_double(zlow);
high = zval_get_double(zhigh);
if (zend_isinf(high) || zend_isinf(low)) {
php_error_docref(NULL, E_WARNING, "Invalid range supplied: start=%0.0f end=%0.0f", low, high);
RETURN_FALSE;
}
Z_TYPE_INFO(tmp) = IS_DOUBLE;
if (low > high) { /* Negative steps */
if (low - high < step || step <= 0) {
err = 1;
goto err;
}
RANGE_CHECK_DOUBLE_INIT_ARRAY(low, high);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (i = 0, element = low; i < size && element >= high; ++i, element = low - (i * step)) {
Z_DVAL(tmp) = element;
ZEND_HASH_FILL_ADD(&tmp);
}
} ZEND_HASH_FILL_END();
} else if (high > low) { /* Positive steps */
if (high - low < step || step <= 0) {
err = 1;
goto err;
}
RANGE_CHECK_DOUBLE_INIT_ARRAY(high, low);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (i = 0, element = low; i < size && element <= high; ++i, element = low + (i * step)) {
Z_DVAL(tmp) = element;
ZEND_HASH_FILL_ADD(&tmp);
}
} ZEND_HASH_FILL_END();
} else {
array_init(return_value);
Z_DVAL(tmp) = low;
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
}
} else {
zend_long low, high;
/* lstep is a ulong so that comparisons to it don't overflow, i.e. low - high < lstep */
zend_ulong lstep;
uint32_t i, size;
long_str:
low = zval_get_long(zlow);
high = zval_get_long(zhigh);
if (step <= 0) {
err = 1;
goto err;
}
lstep = step;
Z_TYPE_INFO(tmp) = IS_LONG;
if (low > high) { /* Negative steps */
if ((zend_ulong)(low - high) < lstep) {
err = 1;
goto err;
}
RANGE_CHECK_LONG_INIT_ARRAY(low, high);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (i = 0; i < size; ++i) {
Z_LVAL(tmp) = low - (i * lstep);
ZEND_HASH_FILL_ADD(&tmp);
}
} ZEND_HASH_FILL_END();
} else if (high > low) { /* Positive steps */
if ((zend_ulong)(high - low) < lstep) {
err = 1;
goto err;
}
RANGE_CHECK_LONG_INIT_ARRAY(high, low);
ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) {
for (i = 0; i < size; ++i) {
Z_LVAL(tmp) = low + (i * lstep);
ZEND_HASH_FILL_ADD(&tmp);
}
} ZEND_HASH_FILL_END();
} else {
array_init(return_value);
Z_LVAL(tmp) = low;
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp);
}
}
err:
if (err) {
php_error_docref(NULL, E_WARNING, "step exceeds the specified range");
RETURN_FALSE;
}
}
/* }}} */
#undef RANGE_CHECK_DOUBLE_INIT_ARRAY
#undef RANGE_CHECK_LONG_INIT_ARRAY
static void php_array_data_shuffle(zval *array) /* {{{ */
{
uint32_t idx, j, n_elems;
Bucket *p, temp;
HashTable *hash;
zend_long rnd_idx;
zend_long n_left;
n_elems = zend_hash_num_elements(Z_ARRVAL_P(array));
if (n_elems < 1) {
return;
}
hash = Z_ARRVAL_P(array);
n_left = n_elems;
if (EXPECTED(hash->u.v.nIteratorsCount == 0)) {
if (hash->nNumUsed != hash->nNumOfElements) {
for (j = 0, idx = 0; idx < hash->nNumUsed; idx++) {
p = hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
if (j != idx) {
hash->arData[j] = *p;
}
j++;
}
}
while (--n_left) {
rnd_idx = php_rand();
RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
if (rnd_idx != n_left) {
temp = hash->arData[n_left];
hash->arData[n_left] = hash->arData[rnd_idx];
hash->arData[rnd_idx] = temp;
}
}
} else {
uint32_t iter_pos = zend_hash_iterators_lower_pos(hash, 0);
if (hash->nNumUsed != hash->nNumOfElements) {
for (j = 0, idx = 0; idx < hash->nNumUsed; idx++) {
p = hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
if (j != idx) {
hash->arData[j] = *p;
if (idx == iter_pos) {
zend_hash_iterators_update(hash, idx, j);
iter_pos = zend_hash_iterators_lower_pos(hash, iter_pos + 1);
}
}
j++;
}
}
while (--n_left) {
rnd_idx = php_rand();
RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
if (rnd_idx != n_left) {
temp = hash->arData[n_left];
hash->arData[n_left] = hash->arData[rnd_idx];
hash->arData[rnd_idx] = temp;
zend_hash_iterators_update(hash, (uint32_t)rnd_idx, n_left);
}
}
}
hash->nNumUsed = n_elems;
hash->nInternalPointer = 0;
for (j = 0; j < n_elems; j++) {
p = hash->arData + j;
if (p->key) {
zend_string_release(p->key);
}
p->h = j;
p->key = NULL;
}
hash->nNextFreeElement = n_elems;
if (!(hash->u.flags & HASH_FLAG_PACKED)) {
zend_hash_to_packed(hash);
}
}
/* }}} */
/* {{{ proto bool shuffle(array array_arg)
Randomly shuffle the contents of an array */
PHP_FUNCTION(shuffle)
{
zval *array;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &array) == FAILURE) {
RETURN_FALSE;
}
php_array_data_shuffle(array);
RETURN_TRUE;
}
/* }}} */
static void php_splice(HashTable *in_hash, zend_long offset, zend_long length, HashTable *replace, HashTable *removed) /* {{{ */
{
HashTable out_hash; /* Output hashtable */
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);
/* Get number of entries in the input hash */
num_in = zend_hash_num_elements(in_hash);
/* Clamp the offset.. */
if (offset > num_in) {
offset = num_in;
} else if (offset < 0 && (offset = (num_in + offset)) < 0) {
offset = 0;
}
/* ..and the length */
if (length < 0) {
length = num_in - offset + length;
} else if (((unsigned)offset + (unsigned)length) > (unsigned)num_in) {
length = num_in - offset;
}
/* Create and initialize output hash */
zend_hash_init(&out_hash, (length > 0 ? num_in - length : 0) + (replace ? zend_hash_num_elements(replace) : 0), NULL, ZVAL_PTR_DTOR, 0);
/* Start at the beginning of the input hash and copy entries to output hash until offset is reached */
for (pos = 0, idx = 0; pos < offset && idx < in_hash->nNumUsed; idx++) {
p = in_hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
/* Get entry and increase reference count */
entry = &p->val;
/* Update output hash depending on key type */
if (p->key == NULL) {
zend_hash_next_index_insert_new(&out_hash, entry);
} else {
zend_hash_add_new(&out_hash, p->key, entry);
}
if (idx == iter_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);
}
pos++;
}
/* If hash for removed entries exists, go until offset+length and copy the entries to it */
if (removed != NULL) {
for ( ; pos < offset + length && idx < in_hash->nNumUsed; idx++) {
p = in_hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
pos++;
entry = &p->val;
if (Z_REFCOUNTED_P(entry)) {
Z_ADDREF_P(entry);
}
if (p->key == NULL) {
zend_hash_next_index_insert_new(removed, entry);
zend_hash_index_del(in_hash, p->h);
} else {
zend_hash_add_new(removed, p->key, entry);
if (in_hash == &EG(symbol_table)) {
zend_delete_global_variable(p->key);
} else {
zend_hash_del(in_hash, p->key);
}
}
}
} else { /* otherwise just skip those entries */
int pos2 = pos;
for ( ; pos2 < offset + length && idx < in_hash->nNumUsed; idx++) {
p = in_hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
pos2++;
if (p->key == NULL) {
zend_hash_index_del(in_hash, p->h);
} else {
if (in_hash == &EG(symbol_table)) {
zend_delete_global_variable(p->key);
} else {
zend_hash_del(in_hash, p->key);
}
}
}
}
iter_pos = zend_hash_iterators_lower_pos(in_hash, iter_pos);
/* If there are entries to insert.. */
if (replace) {
ZEND_HASH_FOREACH_VAL_IND(replace, entry) {
if (Z_REFCOUNTED_P(entry)) Z_ADDREF_P(entry);
zend_hash_next_index_insert_new(&out_hash, entry);
pos++;
} ZEND_HASH_FOREACH_END();
}
/* Copy the remaining input hash entries to the output hash */
for ( ; idx < in_hash->nNumUsed ; idx++) {
p = in_hash->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
entry = &p->val;
if (p->key == NULL) {
zend_hash_next_index_insert_new(&out_hash, entry);
} else {
zend_hash_add_new(&out_hash, p->key, entry);
}
if (idx == iter_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);
}
pos++;
}
/* replace HashTable data */
in_hash->u.v.nIteratorsCount = 0;
in_hash->pDestructor = NULL;
zend_hash_destroy(in_hash);
in_hash->u.v.flags = out_hash.u.v.flags;
in_hash->nTableSize = out_hash.nTableSize;
in_hash->nTableMask = out_hash.nTableMask;
in_hash->nNumUsed = out_hash.nNumUsed;
in_hash->nNumOfElements = out_hash.nNumOfElements;
in_hash->nNextFreeElement = out_hash.nNextFreeElement;
in_hash->arData = out_hash.arData;
in_hash->pDestructor = out_hash.pDestructor;
zend_hash_internal_pointer_reset(in_hash);
}
/* }}} */
/* {{{ proto int array_push(array stack, mixed var [, mixed ...])
Pushes elements onto the end of the array */
PHP_FUNCTION(array_push)
{
zval *args, /* Function arguments array */
*stack, /* Input array */
new_var; /* Variable to be pushed */
int i, /* Loop counter */
argc; /* Number of function arguments */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/+", &stack, &args, &argc) == FAILURE) {
return;
}
/* For each subsequent argument, make it a reference, increase refcount, and add it to the end of the array */
for (i = 0; i < argc; i++) {
ZVAL_COPY(&new_var, &args[i]);
if (zend_hash_next_index_insert(Z_ARRVAL_P(stack), &new_var) == NULL) {
if (Z_REFCOUNTED(new_var)) Z_DELREF(new_var);
php_error_docref(NULL, E_WARNING, "Cannot add element to the array as the next element is already occupied");
RETURN_FALSE;
}
}
/* Clean up and return the number of values in the stack */
RETVAL_LONG(zend_hash_num_elements(Z_ARRVAL_P(stack)));
}
/* }}} */
/* {{{ proto mixed array_pop(array stack)
Pops an element off the end of the array */
PHP_FUNCTION(array_pop)
{
zval *stack, /* Input stack */
*val; /* Value to be popped */
uint32_t idx;
Bucket *p;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &stack) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX(stack, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
if (zend_hash_num_elements(Z_ARRVAL_P(stack)) == 0) {
return;
}
/* Get the last value and copy it into the return value */
idx = Z_ARRVAL_P(stack)->nNumUsed;
while (1) {
if (idx == 0) {
return;
}
idx--;
p = Z_ARRVAL_P(stack)->arData + idx;
val = &p->val;
if (Z_TYPE_P(val) == IS_INDIRECT) {
val = Z_INDIRECT_P(val);
}
if (Z_TYPE_P(val) != IS_UNDEF) {
break;
}
}
ZVAL_DEREF(val);
ZVAL_COPY(return_value, val);
if (!p->key && Z_ARRVAL_P(stack)->nNextFreeElement > 0 && p->h >= (zend_ulong)(Z_ARRVAL_P(stack)->nNextFreeElement - 1)) {
Z_ARRVAL_P(stack)->nNextFreeElement = Z_ARRVAL_P(stack)->nNextFreeElement - 1;
}
/* Delete the last value */
if (p->key) {
if (Z_ARRVAL_P(stack) == &EG(symbol_table)) {
zend_delete_global_variable(p->key);
} else {
zend_hash_del(Z_ARRVAL_P(stack), p->key);
}
} else {
zend_hash_index_del(Z_ARRVAL_P(stack), p->h);
}
zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
}
/* }}} */
/* {{{ proto mixed array_shift(array stack)
Pops an element off the beginning of the array */
PHP_FUNCTION(array_shift)
{
zval *stack, /* Input stack */
*val; /* Value to be popped */
uint32_t idx;
Bucket *p;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &stack) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX(stack, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
if (zend_hash_num_elements(Z_ARRVAL_P(stack)) == 0) {
return;
}
/* Get the first value and copy it into the return value */
idx = 0;
while (1) {
if (idx == Z_ARRVAL_P(stack)->nNumUsed) {
return;
}
p = Z_ARRVAL_P(stack)->arData + idx;
val = &p->val;
if (Z_TYPE_P(val) == IS_INDIRECT) {
val = Z_INDIRECT_P(val);
}
if (Z_TYPE_P(val) != IS_UNDEF) {
break;
}
idx++;
}
ZVAL_DEREF(val);
ZVAL_COPY(return_value, val);
/* Delete the first value */
if (p->key) {
if (Z_ARRVAL_P(stack) == &EG(symbol_table)) {
zend_delete_global_variable(p->key);
} else {
zend_hash_del(Z_ARRVAL_P(stack), p->key);
}
} else {
zend_hash_index_del(Z_ARRVAL_P(stack), p->h);
}
/* re-index like it did before */
if (Z_ARRVAL_P(stack)->u.flags & HASH_FLAG_PACKED) {
uint32_t k = 0;
if (EXPECTED(Z_ARRVAL_P(stack)->u.v.nIteratorsCount == 0)) {
for (idx = 0; idx < Z_ARRVAL_P(stack)->nNumUsed; idx++) {
p = Z_ARRVAL_P(stack)->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
if (idx != k) {
Bucket *q = Z_ARRVAL_P(stack)->arData + k;
q->h = k;
q->key = NULL;
ZVAL_COPY_VALUE(&q->val, &p->val);
ZVAL_UNDEF(&p->val);
}
k++;
}
} else {
uint32_t iter_pos = zend_hash_iterators_lower_pos(Z_ARRVAL_P(stack), 0);
for (idx = 0; idx < Z_ARRVAL_P(stack)->nNumUsed; idx++) {
p = Z_ARRVAL_P(stack)->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
if (idx != k) {
Bucket *q = Z_ARRVAL_P(stack)->arData + k;
q->h = k;
q->key = NULL;
ZVAL_COPY_VALUE(&q->val, &p->val);
ZVAL_UNDEF(&p->val);
if (idx == iter_pos) {
zend_hash_iterators_update(Z_ARRVAL_P(stack), idx, k);
iter_pos = zend_hash_iterators_lower_pos(Z_ARRVAL_P(stack), iter_pos + 1);
}
}
k++;
}
}
Z_ARRVAL_P(stack)->nNumUsed = k;
Z_ARRVAL_P(stack)->nNextFreeElement = k;
} else {
uint32_t k = 0;
int should_rehash = 0;
for (idx = 0; idx < Z_ARRVAL_P(stack)->nNumUsed; idx++) {
p = Z_ARRVAL_P(stack)->arData + idx;
if (Z_TYPE(p->val) == IS_UNDEF) continue;
if (p->key == NULL) {
if (p->h != k) {
p->h = k++;
should_rehash = 1;
} else {
k++;
}
}
}
Z_ARRVAL_P(stack)->nNextFreeElement = k;
if (should_rehash) {
zend_hash_rehash(Z_ARRVAL_P(stack));
}
}
zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
}
/* }}} */
/* {{{ proto int array_unshift(array stack, mixed var [, mixed ...])
Pushes elements onto the beginning of the array */
PHP_FUNCTION(array_unshift)
{
zval *args, /* Function arguments array */
*stack; /* Input stack */
HashTable new_hash; /* New hashtable for the stack */
int argc; /* Number of function arguments */
int i;
zend_string *key;
zval *value;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/+", &stack, &args, &argc) == FAILURE) {
return;
}
zend_hash_init(&new_hash, zend_hash_num_elements(Z_ARRVAL_P(stack)) + argc, NULL, ZVAL_PTR_DTOR, 0);
for (i = 0; i < argc; i++) {
if (Z_REFCOUNTED(args[i])) {
Z_ADDREF(args[i]);
}
zend_hash_next_index_insert_new(&new_hash, &args[i]);
}
if (EXPECTED(Z_ARRVAL_P(stack)->u.v.nIteratorsCount == 0)) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(stack), key, value) {
if (key) {
zend_hash_add_new(&new_hash, key, value);
} else {
zend_hash_next_index_insert_new(&new_hash, value);
}
} ZEND_HASH_FOREACH_END();
} else {
uint32_t old_idx;
uint32_t new_idx = i;
uint32_t iter_pos = zend_hash_iterators_lower_pos(Z_ARRVAL_P(stack), 0);
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(stack), key, value) {
if (key) {
zend_hash_add_new(&new_hash, key, value);
} else {
zend_hash_next_index_insert_new(&new_hash, value);
}
old_idx = (Bucket*)value - Z_ARRVAL_P(stack)->arData;
if (old_idx == iter_pos) {
zend_hash_iterators_update(Z_ARRVAL_P(stack), old_idx, new_idx);
iter_pos = zend_hash_iterators_lower_pos(Z_ARRVAL_P(stack), iter_pos + 1);
}
new_idx++;
} ZEND_HASH_FOREACH_END();
}
/* replace HashTable data */
Z_ARRVAL_P(stack)->u.v.nIteratorsCount = 0;
Z_ARRVAL_P(stack)->pDestructor = NULL;
zend_hash_destroy(Z_ARRVAL_P(stack));
Z_ARRVAL_P(stack)->u.v.flags = new_hash.u.v.flags;
Z_ARRVAL_P(stack)->nTableSize = new_hash.nTableSize;
Z_ARRVAL_P(stack)->nTableMask = new_hash.nTableMask;
Z_ARRVAL_P(stack)->nNumUsed = new_hash.nNumUsed;
Z_ARRVAL_P(stack)->nNumOfElements = new_hash.nNumOfElements;
Z_ARRVAL_P(stack)->nNextFreeElement = new_hash.nNextFreeElement;
Z_ARRVAL_P(stack)->arData = new_hash.arData;
Z_ARRVAL_P(stack)->pDestructor = new_hash.pDestructor;
zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));
/* Clean up and return the number of elements in the stack */
RETVAL_LONG(zend_hash_num_elements(Z_ARRVAL_P(stack)));
}
/* }}} */
/* {{{ proto array array_splice(array input, int offset [, int length [, array replacement]])
Removes the elements designated by offset and length and replace them with supplied array */
PHP_FUNCTION(array_splice)
{
zval *array, /* Input array */
*repl_array = NULL; /* Replacement array */
HashTable *rem_hash = NULL;
zend_long offset,
length = 0;
int num_in; /* Number of elements in the input array */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/l|lz/", &array, &offset, &length, &repl_array) == FAILURE) {
return;
}
num_in = zend_hash_num_elements(Z_ARRVAL_P(array));
if (ZEND_NUM_ARGS() < 3) {
length = num_in;
}
if (ZEND_NUM_ARGS() == 4) {
/* Make sure the last argument, if passed, is an array */
convert_to_array_ex(repl_array);
}
/* Don't create the array of removed elements if it's not going
* to be used; e.g. only removing and/or replacing elements */
if (USED_RET()) {
zend_long size = length;
/* Clamp the offset.. */
if (offset > num_in) {
offset = num_in;
} else if (offset < 0 && (offset = (num_in + offset)) < 0) {
offset = 0;
}
/* ..and the length */
if (length < 0) {
size = num_in - offset + length;
} else if (((zend_ulong) offset + (zend_ulong) length) > (uint32_t) num_in) {
size = num_in - offset;
}
/* Initialize return value */
array_init_size(return_value, size > 0 ? (uint32_t)size : 0);
rem_hash = Z_ARRVAL_P(return_value);
}
/* Perform splice */
php_splice(Z_ARRVAL_P(array), offset, length, repl_array ? Z_ARRVAL_P(repl_array) : NULL, rem_hash);
}
/* }}} */
/* {{{ proto array array_slice(array input, int offset [, int length [, bool preserve_keys]])
Returns elements specified by offset and length */
PHP_FUNCTION(array_slice)
{
zval *input, /* Input array */
*z_length = NULL, /* How many elements to get */
*entry; /* An array entry */
zend_long offset, /* Offset to get elements from */
length = 0;
zend_bool preserve_keys = 0; /* Whether to preserve keys while copying to the new array or not */
int num_in, /* Number of elements in the input array */
pos; /* Current position in the array */
zend_string *string_key;
zend_ulong num_key;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "al|zb", &input, &offset, &z_length, &preserve_keys) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_ARRAY(input)
Z_PARAM_LONG(offset)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(z_length)
Z_PARAM_BOOL(preserve_keys)
ZEND_PARSE_PARAMETERS_END();
#endif
/* Get number of entries in the input hash */
num_in = zend_hash_num_elements(Z_ARRVAL_P(input));
/* We want all entries from offset to the end if length is not passed or is null */
if (ZEND_NUM_ARGS() < 3 || Z_TYPE_P(z_length) == IS_NULL) {
length = num_in;
} else {
length = zval_get_long(z_length);
}
/* Clamp the offset.. */
if (offset > num_in) {
array_init(return_value);
return;
} else if (offset < 0 && (offset = (num_in + offset)) < 0) {
offset = 0;
}
/* ..and the length */
if (length < 0) {
length = num_in - offset + length;
} else if (((zend_ulong) offset + (zend_ulong) length) > (unsigned) num_in) {
length = num_in - offset;
}
if (length <= 0) {
array_init(return_value);
return;
}
/* Initialize returned array */
array_init_size(return_value, (uint32_t)length);
/* Start at the beginning and go until we hit offset */
pos = 0;
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) {
pos++;
if (pos <= offset) {
continue;
}
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);
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FILL_END();
} else {
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(input), num_key, string_key, entry) {
pos++;
if (pos <= offset) {
continue;
}
if (pos > offset + length) {
break;
}
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);
} else {
entry = zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), entry);
}
}
zval_add_ref(entry);
} ZEND_HASH_FOREACH_END();
}
}
/* }}} */
PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */
{
zval *src_entry, *dest_entry;
zend_string *string_key;
ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) {
if (string_key) {
if ((dest_entry = zend_hash_find(dest, string_key)) != NULL) {
zval *src_zval = src_entry;
zval *dest_zval = dest_entry;
HashTable *thash;
zval tmp;
int ret;
ZVAL_DEREF(src_zval);
ZVAL_DEREF(dest_zval);
thash = Z_TYPE_P(dest_zval) == IS_ARRAY ? Z_ARRVAL_P(dest_zval) : NULL;
if ((thash && thash->u.v.nApplyCount > 1) || (src_entry == dest_entry && Z_ISREF_P(dest_entry) && (Z_REFCOUNT_P(dest_entry) % 2))) {
php_error_docref(NULL, E_WARNING, "recursion detected");
return 0;
}
if (Z_ISREF_P(dest_entry)) {
if (Z_REFCOUNT_P(dest_entry) == 1) {
ZVAL_UNREF(dest_entry);
} else {
Z_DELREF_P(dest_entry);
ZVAL_DUP(dest_entry, dest_zval);
}
dest_zval = dest_entry;
} else {
SEPARATE_ZVAL(dest_zval);
}
if (Z_TYPE_P(dest_zval) == IS_NULL) {
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 > (zend_long)Z_ARRVAL_P(dest_zval)->nNumUsed)) {
Z_ARRVAL_P(dest_zval)->nNextFreeElement = Z_ARRVAL_P(dest_zval)->nNumUsed;
}
} else {
convert_to_array_ex(dest_zval);
}
ZVAL_UNDEF(&tmp);
if (Z_TYPE_P(src_zval) == IS_OBJECT) {
ZVAL_COPY(&tmp, src_zval);
convert_to_array(&tmp);
src_zval = &tmp;
}
if (Z_TYPE_P(src_zval) == IS_ARRAY) {
if (thash && ZEND_HASH_APPLY_PROTECTION(thash)) {
thash->u.v.nApplyCount++;
}
ret = php_array_merge_recursive(Z_ARRVAL_P(dest_zval), Z_ARRVAL_P(src_zval));
if (thash && ZEND_HASH_APPLY_PROTECTION(thash)) {
thash->u.v.nApplyCount--;
}
if (!ret) {
return 0;
}
} else {
if (Z_REFCOUNTED_P(src_entry)) {
Z_ADDREF_P(src_entry);
}
zend_hash_next_index_insert(Z_ARRVAL_P(dest_zval), src_zval);
}
zval_ptr_dtor(&tmp);
} else {
zval *zv = zend_hash_add_new(dest, string_key, src_entry);
zval_add_ref(zv);
}
} else {
zval *zv = zend_hash_next_index_insert_new(dest, src_entry);
zval_add_ref(zv);
}
} ZEND_HASH_FOREACH_END();
return 1;
}
/* }}} */
PHPAPI int php_array_merge(HashTable *dest, HashTable *src) /* {{{ */
{
zval *src_entry;
zend_string *string_key;
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 {
zend_hash_next_index_insert_new(dest, src_entry);
}
} ZEND_HASH_FOREACH_END();
}
return 1;
}
/* }}} */
PHPAPI int php_array_replace_recursive(HashTable *dest, HashTable *src) /* {{{ */
{
zval *src_entry, *dest_entry, *src_zval, *dest_zval;
zend_string *string_key;
zend_ulong num_key;
int ret;
ZEND_HASH_FOREACH_KEY_VAL(src, num_key, string_key, src_entry) {
src_zval = src_entry;
ZVAL_DEREF(src_zval);
if (string_key) {
if (Z_TYPE_P(src_zval) != IS_ARRAY ||
(dest_entry = zend_hash_find(dest, string_key)) == NULL ||
(Z_TYPE_P(dest_entry) != IS_ARRAY &&
(!Z_ISREF_P(dest_entry) || Z_TYPE_P(Z_REFVAL_P(dest_entry)) != IS_ARRAY))) {
zval *zv = zend_hash_update(dest, string_key, src_entry);
zval_add_ref(zv);
continue;
}
} else {
if (Z_TYPE_P(src_zval) != IS_ARRAY ||
(dest_entry = zend_hash_index_find(dest, num_key)) == NULL ||
(Z_TYPE_P(dest_entry) != IS_ARRAY &&
(!Z_ISREF_P(dest_entry) || Z_TYPE_P(Z_REFVAL_P(dest_entry)) != IS_ARRAY))) {
zval *zv = zend_hash_index_update(dest, num_key, src_entry);
zval_add_ref(zv);
continue;
}
}
dest_zval = dest_entry;
ZVAL_DEREF(dest_zval);
if (Z_ARRVAL_P(dest_zval)->u.v.nApplyCount > 1 ||
Z_ARRVAL_P(src_zval)->u.v.nApplyCount > 1 ||
(Z_ISREF_P(src_entry) && Z_ISREF_P(dest_entry) && Z_REF_P(src_entry) == Z_REF_P(dest_entry) && (Z_REFCOUNT_P(dest_entry) % 2))) {
php_error_docref(NULL, E_WARNING, "recursion detected");
return 0;
}
SEPARATE_ZVAL(dest_zval);
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(dest_zval))) {
Z_ARRVAL_P(dest_zval)->u.v.nApplyCount++;
}
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(src_zval))) {
Z_ARRVAL_P(src_zval)->u.v.nApplyCount++;
}
ret = php_array_replace_recursive(Z_ARRVAL_P(dest_zval), Z_ARRVAL_P(src_zval));
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(dest_zval))) {
Z_ARRVAL_P(dest_zval)->u.v.nApplyCount--;
}
if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(src_zval))) {
Z_ARRVAL_P(src_zval)->u.v.nApplyCount--;
}
if (!ret) {
return 0;
}
} ZEND_HASH_FOREACH_END();
return 1;
}
/* }}} */
static inline void php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAMETERS, int recursive, int replace) /* {{{ */
{
zval *args = NULL;
zval *arg;
int argc, i;
#ifndef FAST_ZPP
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_VARIADIC('+', args, argc)
ZEND_PARSE_PARAMETERS_END();
#endif
for (i = 0; i < argc; i++) {
zval *arg = args + i;
ZVAL_DEREF(arg);
if (Z_TYPE_P(arg) != IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "Argument #%d is not an array", i + 1);
RETURN_NULL();
}
}
if (replace) {
HashTable *dest;
/* copy first array */
arg = args;
ZVAL_DEREF(arg);
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(dest, Z_ARRVAL_P(arg));
}
} else {
for (i = 1; i < argc; i++) {
arg = args + i;
ZVAL_DEREF(arg);
zend_hash_merge(dest, Z_ARRVAL_P(arg), zval_add_ref, 1);
}
}
} else {
zval *src_entry;
HashTable *src, *dest;
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);
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);
}
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_FOREACH_END();
}
if (recursive) {
for (i = 1; i < argc; i++) {
arg = args + i;
ZVAL_DEREF(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(dest, Z_ARRVAL_P(arg));
}
}
}
}
/* }}} */
/* {{{ proto array array_merge(array arr1, array arr2 [, array ...])
Merges elements from passed arrays into one array */
PHP_FUNCTION(array_merge)
{
php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
}
/* }}} */
/* {{{ proto array array_merge_recursive(array arr1, array arr2 [, array ...])
Recursively merges elements from passed arrays into one array */
PHP_FUNCTION(array_merge_recursive)
{
php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0);
}
/* }}} */
/* {{{ proto array array_replace(array arr1, array arr2 [, array ...])
Replaces elements from passed arrays into one array */
PHP_FUNCTION(array_replace)
{
php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1);
}
/* }}} */
/* {{{ proto array array_replace_recursive(array arr1, array arr2 [, array ...])
Recursively replaces elements from passed arrays into one array */
PHP_FUNCTION(array_replace_recursive)
{
php_array_merge_or_replace_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1);
}
/* }}} */
/* {{{ proto array array_keys(array input [, mixed search_value[, bool strict]])
Return just the keys from the input array, optionally only for the specified search_value */
PHP_FUNCTION(array_keys)
{
zval *input, /* Input array */
*search_value = NULL, /* Value to search for */
*entry, /* An entry in the input array */