From e8b8341d3d5ffe10360746c343d403c1a77b38f1 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 4 Apr 2023 18:19:19 +0200 Subject: [PATCH] Support enums in array_unique Fixes GH-9775 Closes GH-11015 --- NEWS | 2 ++ Zend/tests/gh9775_1.phpt | 56 ++++++++++++++++++++++++++++++++++++++++ Zend/tests/gh9775_2.phpt | 56 ++++++++++++++++++++++++++++++++++++++++ ext/standard/array.c | 22 +++++++++++++++- 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh9775_1.phpt create mode 100644 Zend/tests/gh9775_2.phpt diff --git a/NEWS b/NEWS index 746746e801f..941f6108807 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,8 @@ PHP NEWS - Standard: . Fixed bug GH-10990 (mail() throws TypeError after iterating over $additional_headers array by reference). (nielsdos) + . Fixed bug GH-9775 (Duplicates returned by array_unique when using enums). + (ilutov) 13 Apr 2023, PHP 8.1.18 diff --git a/Zend/tests/gh9775_1.phpt b/Zend/tests/gh9775_1.phpt new file mode 100644 index 00000000000..e2ea5287f5d --- /dev/null +++ b/Zend/tests/gh9775_1.phpt @@ -0,0 +1,56 @@ +--TEST-- +GH-9775: Backed enum in array_unique() +--FILE-- + +--EXPECT-- +array(8) { + [0]=> + enum(Test::COURSES_ADMIN) + [1]=> + enum(Test::COURSES_REPORTING_ACCESS) + [2]=> + enum(Test::BUNDLES_ADMIN) + [3]=> + enum(Test::USERS_ADMIN) + [4]=> + enum(Test::B2B_DASHBOARD_ACCESS) + [6]=> + enum(Test::INSTRUCTORS_ADMIN) + [8]=> + enum(Test::COUPONS_ADMIN) + [9]=> + enum(Test::AUTHENTICATED) +} diff --git a/Zend/tests/gh9775_2.phpt b/Zend/tests/gh9775_2.phpt new file mode 100644 index 00000000000..94ef0029fa9 --- /dev/null +++ b/Zend/tests/gh9775_2.phpt @@ -0,0 +1,56 @@ +--TEST-- +GH-9775: Pure enum in array_unique() +--FILE-- + +--EXPECT-- +array(8) { + [0]=> + enum(Test::COURSES_ADMIN) + [1]=> + enum(Test::COURSES_REPORTING_ACCESS) + [2]=> + enum(Test::BUNDLES_ADMIN) + [3]=> + enum(Test::USERS_ADMIN) + [4]=> + enum(Test::B2B_DASHBOARD_ACCESS) + [6]=> + enum(Test::INSTRUCTORS_ADMIN) + [8]=> + enum(Test::COUPONS_ADMIN) + [9]=> + enum(Test::AUTHENTICATED) +} diff --git a/ext/standard/array.c b/ext/standard/array.c index 9a814ea07da..fb705cd34c4 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -345,7 +345,27 @@ static zend_always_inline int php_array_key_compare_string_locale_unstable_i(Buc static zend_always_inline int php_array_data_compare_unstable_i(Bucket *f, Bucket *s) /* {{{ */ { - return zend_compare(&f->val, &s->val); + int result = zend_compare(&f->val, &s->val); + /* Special enums handling for array_unique. We don't want to add this logic to zend_compare as + * that would be observable via comparison operators. */ + zval *rhs = &s->val; + ZVAL_DEREF(rhs); + if (UNEXPECTED(Z_TYPE_P(rhs) == IS_OBJECT) + && result == ZEND_UNCOMPARABLE + && (Z_OBJCE_P(rhs)->ce_flags & ZEND_ACC_ENUM)) { + zval *lhs = &f->val; + ZVAL_DEREF(lhs); + if (Z_TYPE_P(lhs) == IS_OBJECT && (Z_OBJCE_P(lhs)->ce_flags & ZEND_ACC_ENUM)) { + // Order doesn't matter, we just need to group the same enum values + uintptr_t lhs_uintptr = (uintptr_t)Z_OBJ_P(lhs); + uintptr_t rhs_uintptr = (uintptr_t)Z_OBJ_P(rhs); + return lhs_uintptr == rhs_uintptr ? 0 : (lhs_uintptr < rhs_uintptr ? -1 : 1); + } else { + // Shift enums to the end of the array + return -1; + } + } + return result; } /* }}} */