From f260636d9d225334208b23889882720d8c594a12 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 4 Apr 2023 18:19:19 +0200 Subject: [PATCH 1/2] Support enums in array_unique Fixes GH-9775 --- Zend/tests/gh9775.phpt | 54 ++++++++++++++++++++++++++++++++++++++++++ ext/standard/array.c | 19 ++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh9775.phpt diff --git a/Zend/tests/gh9775.phpt b/Zend/tests/gh9775.phpt new file mode 100644 index 000000000000..ce192ca41600 --- /dev/null +++ b/Zend/tests/gh9775.phpt @@ -0,0 +1,54 @@ +--TEST-- +GH-9775: 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 9a814ea07da4..ec4c093ec0f0 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -345,7 +345,24 @@ 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 handling for enums + zval *rhs = &s->val; + if (UNEXPECTED(Z_TYPE_P(rhs) == IS_OBJECT) + && result == ZEND_UNCOMPARABLE + && (Z_OBJCE_P(rhs)->ce_flags & ZEND_ACC_ENUM)) { + zval *lhs = &f->val; + 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; } /* }}} */ From a98e61ee2ee3e18163c5670ae25de73e69da0ff4 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 5 Apr 2023 11:52:24 +0200 Subject: [PATCH 2/2] Handle refs, add pure enum test --- Zend/tests/{gh9775.phpt => gh9775_1.phpt} | 6 ++- Zend/tests/gh9775_2.phpt | 56 +++++++++++++++++++++++ ext/standard/array.c | 5 +- 3 files changed, 64 insertions(+), 3 deletions(-) rename Zend/tests/{gh9775.phpt => gh9775_1.phpt} (90%) create mode 100644 Zend/tests/gh9775_2.phpt diff --git a/Zend/tests/gh9775.phpt b/Zend/tests/gh9775_1.phpt similarity index 90% rename from Zend/tests/gh9775.phpt rename to Zend/tests/gh9775_1.phpt index ce192ca41600..e2ea5287f5df 100644 --- a/Zend/tests/gh9775.phpt +++ b/Zend/tests/gh9775_1.phpt @@ -1,5 +1,5 @@ --TEST-- -GH-9775: Enum in array_unique() +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/ext/standard/array.c b/ext/standard/array.c index ec4c093ec0f0..fb705cd34c4e 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -346,12 +346,15 @@ 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) /* {{{ */ { int result = zend_compare(&f->val, &s->val); - // Special handling for enums + /* 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);