diff --git a/ext/standard/array.c b/ext/standard/array.c index 6252b61d2042a..726fd6706f2a1 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -294,6 +294,56 @@ static zend_always_inline int php_array_data_compare_unstable_i(Bucket *f, Bucke } /* }}} */ +static int php_zval_compare_strict_function(zval *z1, zval *z2) +{ + ZVAL_DEREF(z1); + ZVAL_DEREF(z2); + + zend_uchar t1 = Z_TYPE_P(z1); + zend_uchar t2 = Z_TYPE_P(z2); + if (t1 != t2) { + return (t1 < t2) ? -1 : 1; + } + + switch (t1) { + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + return 0; + case IS_LONG: + return ZEND_THREEWAY_COMPARE(Z_LVAL_P(z1), Z_LVAL_P(z2)); + case IS_DOUBLE: { + double d1 = Z_DVAL_P(z1); + double d2 = Z_DVAL_P(z2); + if (isnan(d1)) { + return -1; + } else if (isnan(d2)) { + return 1; + } else { + return ZEND_THREEWAY_COMPARE(d1, d2); + } + } + case IS_STRING: + return zend_binary_zval_strcmp(z1, z2); + case IS_ARRAY: + return zend_hash_compare( + Z_ARRVAL_P(z1), Z_ARRVAL_P(z2), + (compare_func_t) php_zval_compare_strict_function, + /* ordered */ true + ); + case IS_OBJECT: + return ZEND_THREEWAY_COMPARE(Z_OBJ_P(z1)->handle, Z_OBJ_P(z2)->handle); + case IS_RESOURCE: + return ZEND_THREEWAY_COMPARE(Z_RES_HANDLE_P(z1), Z_RES_HANDLE_P(z2)); + EMPTY_SWITCH_DEFAULT_CASE() + } +} + +static zend_always_inline int php_array_data_compare_strict_unstable_i(Bucket *f, Bucket *s) +{ + return php_zval_compare_strict_function(&f->val, &s->val); +} + static zend_always_inline int php_array_data_compare_numeric_unstable_i(Bucket *f, Bucket *s) /* {{{ */ { return numeric_compare_function(&f->val, &s->val); @@ -357,6 +407,11 @@ DEFINE_SORT_VARIANTS(data_compare_string_locale); DEFINE_SORT_VARIANTS(natural_compare); DEFINE_SORT_VARIANTS(natural_case_compare); +// Declare without macro since we don't need the other variants +static zend_never_inline int php_array_data_compare_strict_unstable(Bucket *a, Bucket *b) { + return php_array_data_compare_strict_unstable_i(a, b); +} + static bucket_compare_func_t php_get_key_compare_func(zend_long sort_type, int reverse) /* {{{ */ { switch (sort_type & ~PHP_SORT_FLAG_CASE) { @@ -4559,7 +4614,9 @@ PHP_FUNCTION(array_unique) return; } - cmp = php_get_data_compare_func_unstable(sort_type, 0); + cmp = sort_type == PHP_ARRAY_UNIQUE_IDENTICAL + ? php_array_data_compare_strict_unstable + : php_get_data_compare_func_unstable(sort_type, 0); RETVAL_ARR(zend_array_dup(Z_ARRVAL_P(array))); diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index ab56a8c0e8fbb..692b0983ca753 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -86,6 +86,11 @@ * @cvalue PHP_SORT_FLAG_CASE */ const SORT_FLAG_CASE = UNKNOWN; +/** + * @var int + * @cvalue PHP_ARRAY_UNIQUE_IDENTICAL + */ +const ARRAY_UNIQUE_IDENTICAL = UNKNOWN; /** * @var int diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 077f9876df7c2..525ebf91ac790 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 39d455982dfdea9d0b9b646bc207b05f7108d1b2 */ + * Stub hash: 3cf9560cd16524bd2fef772effbb27b66eeea7fc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -3499,6 +3499,7 @@ static void register_basic_functions_symbols(int module_number) REGISTER_LONG_CONSTANT("SORT_LOCALE_STRING", PHP_SORT_LOCALE_STRING, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SORT_NATURAL", PHP_SORT_NATURAL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SORT_FLAG_CASE", PHP_SORT_FLAG_CASE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("ARRAY_UNIQUE_IDENTICAL", PHP_ARRAY_UNIQUE_IDENTICAL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CASE_LOWER", PHP_CASE_LOWER, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CASE_UPPER", PHP_CASE_UPPER, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("COUNT_NORMAL", PHP_COUNT_NORMAL, CONST_PERSISTENT); diff --git a/ext/standard/php_array.h b/ext/standard/php_array.h index 5d42a22f42040..f389e300f876b 100644 --- a/ext/standard/php_array.h +++ b/ext/standard/php_array.h @@ -54,8 +54,12 @@ PHPAPI bool php_array_pick_keys(const php_random_algo *algo, php_random_status * #define PHP_SORT_ASC 4 #define PHP_SORT_LOCALE_STRING 5 #define PHP_SORT_NATURAL 6 +//#define PHP_DONT_USE 7 #define PHP_SORT_FLAG_CASE 8 +// Must not clash with the PHP_SORT_ flags +#define PHP_ARRAY_UNIQUE_IDENTICAL 7 + #define PHP_COUNT_NORMAL 0 #define PHP_COUNT_RECURSIVE 1 diff --git a/ext/standard/tests/array/array_unique_identical.phpt b/ext/standard/tests/array/array_unique_identical.phpt new file mode 100644 index 0000000000000..e4c53f2cd74cb --- /dev/null +++ b/ext/standard/tests/array/array_unique_identical.phpt @@ -0,0 +1,75 @@ +--TEST-- +Test array_unique() with ARRAY_UNIQUE_IDENTICAL +--FILE-- + $a]; +$b3 = (object)['foo' => $b]; +var_dump(array_unique([$a, $b, $a2, $b2, $a3, $b3], ARRAY_UNIQUE_IDENTICAL)); + +?> +--EXPECT-- +array(1) { + [0]=> + string(4) "1234" +} +array(2) { + [0]=> + string(4) "1234" + [1]=> + int(1234) +} +array(6) { + [0]=> + int(0) + [1]=> + string(1) "0" + [2]=> + float(0) + [3]=> + string(3) "0.0" + [4]=> + string(0) "" + [5]=> + NULL +} +array(6) { + [0]=> + object(stdClass)#1 (0) { + } + [1]=> + object(stdClass)#2 (0) { + } + [2]=> + array(1) { + [0]=> + object(stdClass)#1 (0) { + } + } + [3]=> + array(1) { + [0]=> + object(stdClass)#2 (0) { + } + } + [4]=> + object(stdClass)#3 (1) { + ["foo"]=> + object(stdClass)#1 (0) { + } + } + [5]=> + object(stdClass)#4 (1) { + ["foo"]=> + object(stdClass)#2 (0) { + } + } +} diff --git a/ext/standard/tests/array/array_unique_identical_enums.phpt b/ext/standard/tests/array/array_unique_identical_enums.phpt new file mode 100644 index 0000000000000..ab535b644394f --- /dev/null +++ b/ext/standard/tests/array/array_unique_identical_enums.phpt @@ -0,0 +1,83 @@ +--TEST-- +array_unique() with ARRAY_UNIQUE_IDENTICAL and enums +--FILE-- + Foo::Bar, + 'b' => Foo::Baz, + 'c' => Foo::Bar, +], ARRAY_UNIQUE_IDENTICAL)); + +var_dump(array_unique([ + &$bar, + Foo::Bar, + &$bar2, + Foo::Baz, +], ARRAY_UNIQUE_IDENTICAL)); + +$value2 = "hello"; +$value3 = 0; +$value4 = &$value2; +var_dump(array_unique([ + 0, + &$value4, + &$value2, + "hello", + &$value3, + $value4 +], ARRAY_UNIQUE_IDENTICAL)); + +?> +--EXPECT-- +array(2) { + [0]=> + enum(Foo::Bar) + [1]=> + enum(Foo::Baz) +} +array(2) { + [0]=> + enum(Foo::Bar) + [2]=> + enum(Foo::Baz) +} +array(2) { + ["a"]=> + enum(Foo::Bar) + ["b"]=> + enum(Foo::Baz) +} +array(2) { + [0]=> + &enum(Foo::Bar) + [3]=> + enum(Foo::Baz) +} +array(2) { + [0]=> + int(0) + [1]=> + &string(5) "hello" +} diff --git a/ext/standard/tests/array/array_unique_identical_nan.phpt b/ext/standard/tests/array/array_unique_identical_nan.phpt new file mode 100644 index 0000000000000..a97bb919856c5 --- /dev/null +++ b/ext/standard/tests/array/array_unique_identical_nan.phpt @@ -0,0 +1,77 @@ +--TEST-- +array_unique() with ARRAY_UNIQUE_IDENTICAL preserves NAN +--FILE-- + NAN, + 'bar' => NAN, +], ARRAY_UNIQUE_IDENTICAL)); + +var_dump(array_unique([ + [NAN], + 1.0, + [NAN], +], ARRAY_UNIQUE_IDENTICAL)); + +var_dump(array_unique([ + 1.0, + NAN, + 1.0, + NAN, + 1.0, +], ARRAY_UNIQUE_IDENTICAL)); + +var_dump(array_unique([ + 1.0, + NAN, + 1.0, +], ARRAY_UNIQUE_IDENTICAL)); + +?> +--EXPECT-- +array(2) { + [0]=> + float(NAN) + [1]=> + float(NAN) +} +array(2) { + ["foo"]=> + float(NAN) + ["bar"]=> + float(NAN) +} +array(3) { + [0]=> + array(1) { + [0]=> + float(NAN) + } + [1]=> + float(1) + [2]=> + array(1) { + [0]=> + float(NAN) + } +} +array(3) { + [0]=> + float(1) + [1]=> + float(NAN) + [3]=> + float(NAN) +} +array(2) { + [0]=> + float(1) + [1]=> + float(NAN) +}