diff --git a/Zend/tests/gh9775_1.phpt b/Zend/tests/gh9775_1.phpt new file mode 100644 index 0000000000000..e2ea5287f5df9 --- /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 0000000000000..94ef0029fa93d --- /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 9a814ea07da47..fb705cd34c4e8 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; } /* }}} */