From a206e01befe668f000bcaf9d1ba305538d52b092 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 11:52:23 +0100 Subject: [PATCH 1/7] std: refactor intersect/diff compare functions into equality functions As we don't care about relative ordering. --- ext/standard/array.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 0b184efc3998..8d6f2bcfad7e 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5082,13 +5082,13 @@ PHP_FUNCTION(array_unique) } /* }}} */ -static int zval_compare(zval *first, zval *second) /* {{{ */ +static bool zval_is_equal(zval *first, zval *second) { - return string_compare_function(first, second); + return string_compare_function(first, second) == 0; } -/* }}} */ -static int zval_user_compare(zval *a, zval *b) /* {{{ */ +/* Note this assumes the user callback follows the same semantics of other compare functions so 0 means equal */ +static bool zval_is_user_equal(zval *a, zval *b) { zval args[2]; zval retval; @@ -5102,15 +5102,14 @@ static int zval_user_compare(zval *a, zval *b) /* {{{ */ zend_call_function(&BG(user_compare_fci), &BG(user_compare_fci_cache)); zend_long ret = php_get_long(&retval); - return ZEND_NORMALIZE_BOOL(ret); + return ZEND_NORMALIZE_BOOL(ret) == 0; } -/* }}} */ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ { uint32_t argc, i; zval *args; - int (*intersect_data_compare_func)(zval *, zval *) = NULL; + bool (*intersect_data_compare_func)(zval *, zval *) = NULL; bool ok; zval *val, *data; char *param_spec; @@ -5122,14 +5121,14 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa if (data_compare_type == INTERSECT_COMP_DATA_USER) { /* INTERSECT_COMP_DATA_USER - array_uintersect_assoc() */ param_spec = "+f"; - intersect_data_compare_func = zval_user_compare; + intersect_data_compare_func = zval_is_user_equal; } else { /* INTERSECT_COMP_DATA_NONE - array_intersect_key() INTERSECT_COMP_DATA_INTERNAL - array_intersect_assoc() */ param_spec = "+"; if (data_compare_type == INTERSECT_COMP_DATA_INTERNAL) { - intersect_data_compare_func = zval_compare; + intersect_data_compare_func = zval_is_equal; } } @@ -5156,7 +5155,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa for (i = 1; i < argc; i++) { if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) == NULL || (intersect_data_compare_func && - intersect_data_compare_func(val, data) != 0) + intersect_data_compare_func(val, data) == false) ) { ok = false; break; @@ -5171,7 +5170,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa for (i = 1; i < argc; i++) { if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) == NULL || (intersect_data_compare_func && - intersect_data_compare_func(val, data) != 0) + intersect_data_compare_func(val, data) == false) ) { ok = false; break; @@ -5720,7 +5719,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty { uint32_t argc, i; zval *args; - int (*diff_data_compare_func)(zval *, zval *) = NULL; + bool (*diff_data_compare_func)(zval *, zval *) = NULL; bool ok; zval *val, *data; zend_string *key; @@ -5732,13 +5731,13 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty if (zend_parse_parameters(ZEND_NUM_ARGS(), "+f", &args, &argc, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) { RETURN_THROWS(); } - diff_data_compare_func = zval_user_compare; + diff_data_compare_func = zval_is_user_equal; } else { if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { RETURN_THROWS(); } if (data_compare_type == DIFF_COMP_DATA_INTERNAL) { - diff_data_compare_func = zval_compare; + diff_data_compare_func = zval_is_equal; } } @@ -5761,7 +5760,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty for (i = 1; i < argc; i++) { if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) != NULL && (!diff_data_compare_func || - diff_data_compare_func(val, data) == 0) + diff_data_compare_func(val, data) == true) ) { ok = false; break; @@ -5776,7 +5775,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty for (i = 1; i < argc; i++) { if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) != NULL && (!diff_data_compare_func || - diff_data_compare_func(val, data) == 0) + diff_data_compare_func(val, data) == true) ) { ok = false; break; From 587cb58a2dc8f261fb9295db9d82bea6b7a46d1d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 12:26:08 +0100 Subject: [PATCH 2/7] std: always call a equality callback for array_{diff|intersect}_key() Simplifying some logic so that it can be merged in the future --- ext/standard/array.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 8d6f2bcfad7e..ca5f2db4c8b2 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5105,11 +5105,17 @@ static bool zval_is_user_equal(zval *a, zval *b) return ZEND_NORMALIZE_BOOL(ret) == 0; } +static bool zval_always_true(zval *val, zval *data) { + return true; +} + +typedef bool (*php_diff_intersect_equality_fn)(zval *, zval *); + static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ { uint32_t argc, i; zval *args; - bool (*intersect_data_compare_func)(zval *, zval *) = NULL; + php_diff_intersect_equality_fn callback = zval_always_true; bool ok; zval *val, *data; char *param_spec; @@ -5121,14 +5127,14 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa if (data_compare_type == INTERSECT_COMP_DATA_USER) { /* INTERSECT_COMP_DATA_USER - array_uintersect_assoc() */ param_spec = "+f"; - intersect_data_compare_func = zval_is_user_equal; + callback = zval_is_user_equal; } else { /* INTERSECT_COMP_DATA_NONE - array_intersect_key() INTERSECT_COMP_DATA_INTERNAL - array_intersect_assoc() */ param_spec = "+"; if (data_compare_type == INTERSECT_COMP_DATA_INTERNAL) { - intersect_data_compare_func = zval_is_equal; + callback = zval_is_equal; } } @@ -5154,8 +5160,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa ok = true; for (i = 1; i < argc; i++) { if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) == NULL || - (intersect_data_compare_func && - intersect_data_compare_func(val, data) == false) + callback(val, data) == false ) { ok = false; break; @@ -5169,8 +5174,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa ok = true; for (i = 1; i < argc; i++) { if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) == NULL || - (intersect_data_compare_func && - intersect_data_compare_func(val, data) == false) + callback(val, data) == false ) { ok = false; break; @@ -5719,7 +5723,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty { uint32_t argc, i; zval *args; - bool (*diff_data_compare_func)(zval *, zval *) = NULL; + php_diff_intersect_equality_fn callback = zval_always_true; bool ok; zval *val, *data; zend_string *key; @@ -5731,13 +5735,13 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty if (zend_parse_parameters(ZEND_NUM_ARGS(), "+f", &args, &argc, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) { RETURN_THROWS(); } - diff_data_compare_func = zval_is_user_equal; + callback = zval_is_user_equal; } else { if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { RETURN_THROWS(); } if (data_compare_type == DIFF_COMP_DATA_INTERNAL) { - diff_data_compare_func = zval_is_equal; + callback = zval_is_equal; } } @@ -5759,8 +5763,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty ok = true; for (i = 1; i < argc; i++) { if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) != NULL && - (!diff_data_compare_func || - diff_data_compare_func(val, data) == true) + callback(val, data) == true ) { ok = false; break; @@ -5774,8 +5777,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty ok = true; for (i = 1; i < argc; i++) { if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) != NULL && - (!diff_data_compare_func || - diff_data_compare_func(val, data) == true) + callback(val, data) == true ) { ok = false; break; From aa6a48810147e5f7aa6594a9d41dea2df2aa63bc Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 12:42:09 +0100 Subject: [PATCH 3/7] std: handle NULL data zval in callback fns for array_{diff|intersect}_key() Simplifying some logic so that it can be merged in the future --- ext/standard/array.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index ca5f2db4c8b2..e8481346141b 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5082,19 +5082,25 @@ PHP_FUNCTION(array_unique) } /* }}} */ -static bool zval_is_equal(zval *first, zval *second) +static bool zval_is_equal(zval *val, zval *data) { - return string_compare_function(first, second) == 0; + if (!data) { + return false; + } + return string_compare_function(val, data) == 0; } /* Note this assumes the user callback follows the same semantics of other compare functions so 0 means equal */ -static bool zval_is_user_equal(zval *a, zval *b) +static bool zval_is_user_equal(zval *val, zval *data) { + if (!data) { + return false; + } zval args[2]; zval retval; - ZVAL_COPY_VALUE(&args[0], a); - ZVAL_COPY_VALUE(&args[1], b); + ZVAL_COPY_VALUE(&args[0], val); + ZVAL_COPY_VALUE(&args[1], data); BG(user_compare_fci).param_count = 2; BG(user_compare_fci).params = args; @@ -5106,7 +5112,7 @@ static bool zval_is_user_equal(zval *a, zval *b) } static bool zval_always_true(zval *val, zval *data) { - return true; + return data; } typedef bool (*php_diff_intersect_equality_fn)(zval *, zval *); @@ -5159,9 +5165,8 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa if (key == NULL) { ok = true; for (i = 1; i < argc; i++) { - if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) == NULL || - callback(val, data) == false - ) { + data = zend_hash_index_find(Z_ARRVAL(args[i]), h); + if (callback(val, data) == false) { ok = false; break; } @@ -5173,9 +5178,8 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa } else { ok = true; for (i = 1; i < argc; i++) { - if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) == NULL || - callback(val, data) == false - ) { + data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); + if (callback(val, data) == false) { ok = false; break; } @@ -5762,9 +5766,8 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty if (key == NULL) { ok = true; for (i = 1; i < argc; i++) { - if ((data = zend_hash_index_find(Z_ARRVAL(args[i]), h)) != NULL && - callback(val, data) == true - ) { + data = zend_hash_index_find(Z_ARRVAL(args[i]), h); + if (callback(val, data) == true) { ok = false; break; } @@ -5776,9 +5779,8 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty } else { ok = true; for (i = 1; i < argc; i++) { - if ((data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key)) != NULL && - callback(val, data) == true - ) { + data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); + if (callback(val, data) == true) { ok = false; break; } From 9f6c0e956ffc5f6921099c99dd875f0b81156309 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 12:43:42 +0100 Subject: [PATCH 4/7] std: merge array population logic for array_{diff|intersect}_key() --- ext/standard/array.c | 129 ++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 82 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index e8481346141b..154bc459801a 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5117,19 +5117,54 @@ static bool zval_always_true(zval *val, zval *data) { typedef bool (*php_diff_intersect_equality_fn)(zval *, zval *); +ZEND_ATTRIBUTE_NONNULL static void php_check_and_populate_array_matching_condition( + HashTable *ht, + uint32_t argc, zval *args, + php_diff_intersect_equality_fn callback, bool callback_result +) { + /* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */ + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), zend_ulong h, zend_string *key, zval *val) { + bool ok = true; + zval *data; + + if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) { + val = Z_REFVAL_P(val); + } + if (key == NULL) { + for (uint32_t i = 1; i < argc; i++) { + data = zend_hash_index_find(Z_ARRVAL(args[i]), h); + if (callback(val, data) == callback_result) { + ok = false; + break; + } + } + if (ok) { + Z_TRY_ADDREF_P(val); + zend_hash_index_add_new(ht, h, val); + } + } else { + for (uint32_t i = 1; i < argc; i++) { + data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); + if (callback(val, data) == callback_result) { + ok = false; + break; + } + } + if (ok) { + Z_TRY_ADDREF_P(val); + zend_hash_add_new(ht, key, val); + } + } + } ZEND_HASH_FOREACH_END(); +} + static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ { - uint32_t argc, i; + uint32_t argc; zval *args; php_diff_intersect_equality_fn callback = zval_always_true; - bool ok; - zval *val, *data; char *param_spec; - zend_string *key; - zend_ulong h; - /* Get the argument count */ - argc = ZEND_NUM_ARGS(); if (data_compare_type == INTERSECT_COMP_DATA_USER) { /* INTERSECT_COMP_DATA_USER - array_uintersect_assoc() */ param_spec = "+f"; @@ -5148,7 +5183,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa RETURN_THROWS(); } - for (i = 0; i < argc; i++) { + for (uint32_t i = 0; i < argc; i++) { if (Z_TYPE(args[i]) != IS_ARRAY) { zend_argument_type_error(i + 1, "must be of type array, %s given", zend_zval_value_name(&args[i])); RETURN_THROWS(); @@ -5156,40 +5191,7 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa } array_init(return_value); - - /* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */ - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), h, key, val) { - if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) { - val = Z_REFVAL_P(val); - } - if (key == NULL) { - ok = true; - for (i = 1; i < argc; i++) { - data = zend_hash_index_find(Z_ARRVAL(args[i]), h); - if (callback(val, data) == false) { - ok = false; - break; - } - } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_index_add_new(Z_ARRVAL_P(return_value), h, val); - } - } else { - ok = true; - for (i = 1; i < argc; i++) { - data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); - if (callback(val, data) == false) { - ok = false; - break; - } - } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_add_new(Z_ARRVAL_P(return_value), key, val); - } - } - } ZEND_HASH_FOREACH_END(); + php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, false); } /* }}} */ @@ -5725,13 +5727,9 @@ PHP_FUNCTION(array_uintersect_uassoc) static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ { - uint32_t argc, i; + uint32_t argc; zval *args; php_diff_intersect_equality_fn callback = zval_always_true; - bool ok; - zval *val, *data; - zend_string *key; - zend_ulong h; /* Get the argument count */ argc = ZEND_NUM_ARGS(); @@ -5749,7 +5747,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty } } - for (i = 0; i < argc; i++) { + for (uint32_t i = 0; i < argc; i++) { if (Z_TYPE(args[i]) != IS_ARRAY) { zend_argument_type_error(i + 1, "must be of type array, %s given", zend_zval_value_name(&args[i])); RETURN_THROWS(); @@ -5757,40 +5755,7 @@ static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_ty } array_init(return_value); - - /* Iterate over keys of the first array, to compute keys that aren't in the other arrays. */ - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), h, key, val) { - if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) { - val = Z_REFVAL_P(val); - } - if (key == NULL) { - ok = true; - for (i = 1; i < argc; i++) { - data = zend_hash_index_find(Z_ARRVAL(args[i]), h); - if (callback(val, data) == true) { - ok = false; - break; - } - } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_index_add_new(Z_ARRVAL_P(return_value), h, val); - } - } else { - ok = true; - for (i = 1; i < argc; i++) { - data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); - if (callback(val, data) == true) { - ok = false; - break; - } - } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_add_new(Z_ARRVAL_P(return_value), key, val); - } - } - } ZEND_HASH_FOREACH_END(); + php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, true); } /* }}} */ From 9ae5a96eadfd932db2c229bdd8e76e578201ad36 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 12:55:20 +0100 Subject: [PATCH 5/7] std: merge array key diff and intersect logic --- ext/standard/array.c | 137 +++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 89 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 154bc459801a..a0a6306290ef 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5158,25 +5158,17 @@ ZEND_ATTRIBUTE_NONNULL static void php_check_and_populate_array_matching_conditi } ZEND_HASH_FOREACH_END(); } -static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ +static void php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAMETERS, php_diff_intersect_equality_fn callback, bool callback_result) { uint32_t argc; zval *args; - php_diff_intersect_equality_fn callback = zval_always_true; char *param_spec; - if (data_compare_type == INTERSECT_COMP_DATA_USER) { - /* INTERSECT_COMP_DATA_USER - array_uintersect_assoc() */ + if (callback == zval_is_user_equal) { + /* array_uintersect_assoc() or array_udiff_assoc() */ param_spec = "+f"; - callback = zval_is_user_equal; } else { - /* INTERSECT_COMP_DATA_NONE - array_intersect_key() - INTERSECT_COMP_DATA_INTERNAL - array_intersect_assoc() */ param_spec = "+"; - - if (data_compare_type == INTERSECT_COMP_DATA_INTERNAL) { - callback = zval_is_equal; - } } if (zend_parse_parameters(ZEND_NUM_ARGS(), param_spec, &args, &argc, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) { @@ -5191,9 +5183,52 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa } array_init(return_value); - php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, false); + php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, callback_result); +} + +/* Returns the entries of arr1 that have keys which are present in all the other arguments. + * Kind of equivalent to array_diff(array_keys($arr1), array_keys($arr2)[,array_keys(...)]). + * Equivalent of array_intersect_assoc() but does not do compare of the data. */ +PHP_FUNCTION(array_intersect_key) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_always_true, false); +} + +/* Returns the entries of arr1 that have values which are present in all the other arguments. + * Keys are used to do more restrictive check */ +PHP_FUNCTION(array_intersect_assoc) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_equal, false); +} + +/* Returns the entries of arr1 that have values which are present in all the other arguments. + * Keys are used to do more restrictive check. + * Data is compared by using a user-supplied callback. */ +PHP_FUNCTION(array_uintersect_assoc) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_user_equal, false); +} + +/* Returns the entries of arr1 that have keys which are not present in any of the others arguments. + * This function is like array_diff() but works on the keys instead of the values. + * The associativity is preserved. */ +PHP_FUNCTION(array_diff_key) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_always_true, true); +} + +/* Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal */ +PHP_FUNCTION(array_diff_assoc) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_equal, true); +} + +/* Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal. + * Keys are compared by user supplied function. */ +PHP_FUNCTION(array_udiff_assoc) +{ + php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_user_equal, true); } -/* }}} */ static void php_array_intersect(INTERNAL_FUNCTION_PARAMETERS, int behavior, int data_compare_type, int key_compare_type) /* {{{ */ { @@ -5460,13 +5495,6 @@ static void php_array_intersect(INTERNAL_FUNCTION_PARAMETERS, int behavior, int } /* }}} */ -/* {{{ Returns the entries of arr1 that have keys which are present in all the other arguments. Kind of equivalent to array_diff(array_keys($arr1), array_keys($arr2)[,array_keys(...)]). Equivalent of array_intersect_assoc() but does not do compare of the data. */ -PHP_FUNCTION(array_intersect_key) -{ - php_array_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTERSECT_COMP_DATA_NONE); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have keys which are present in all the other arguments. Kind of equivalent to array_diff(array_keys($arr1), array_keys($arr2)[,array_keys(...)]). The comparison of the keys is performed by a user supplied function. Equivalent of array_intersect_uassoc() but does not do compare of the data. */ PHP_FUNCTION(array_intersect_ukey) { @@ -5697,13 +5725,6 @@ PHP_FUNCTION(array_uintersect) } /* }}} */ -/* {{{ Returns the entries of arr1 that have values which are present in all the other arguments. Keys are used to do more restrictive check */ -PHP_FUNCTION(array_intersect_assoc) -{ - php_array_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTERSECT_COMP_DATA_INTERNAL); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have values which are present in all the other arguments. Keys are used to do more restrictive check and they are compared by using a user-supplied callback. */ PHP_FUNCTION(array_intersect_uassoc) { @@ -5711,13 +5732,6 @@ PHP_FUNCTION(array_intersect_uassoc) } /* }}} */ -/* {{{ Returns the entries of arr1 that have values which are present in all the other arguments. Keys are used to do more restrictive check. Data is compared by using a user-supplied callback. */ -PHP_FUNCTION(array_uintersect_assoc) -{ - php_array_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTERSECT_COMP_DATA_USER); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have values which are present in all the other arguments. Keys are used to do more restrictive check. Both data and keys are compared by using user-supplied callbacks. */ PHP_FUNCTION(array_uintersect_uassoc) { @@ -5725,40 +5739,6 @@ PHP_FUNCTION(array_uintersect_uassoc) } /* }}} */ -static void php_array_diff_key(INTERNAL_FUNCTION_PARAMETERS, int data_compare_type) /* {{{ */ -{ - uint32_t argc; - zval *args; - php_diff_intersect_equality_fn callback = zval_always_true; - - /* Get the argument count */ - argc = ZEND_NUM_ARGS(); - if (data_compare_type == DIFF_COMP_DATA_USER) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "+f", &args, &argc, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) { - RETURN_THROWS(); - } - callback = zval_is_user_equal; - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { - RETURN_THROWS(); - } - if (data_compare_type == DIFF_COMP_DATA_INTERNAL) { - callback = zval_is_equal; - } - } - - for (uint32_t i = 0; i < argc; i++) { - if (Z_TYPE(args[i]) != IS_ARRAY) { - zend_argument_type_error(i + 1, "must be of type array, %s given", zend_zval_value_name(&args[i])); - RETURN_THROWS(); - } - } - - array_init(return_value); - php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, true); -} -/* }}} */ - static void php_array_diff(INTERNAL_FUNCTION_PARAMETERS, int behavior, int data_compare_type, int key_compare_type) /* {{{ */ { zval *args = NULL; @@ -6012,13 +5992,6 @@ static void php_array_diff(INTERNAL_FUNCTION_PARAMETERS, int behavior, int data_ } /* }}} */ -/* {{{ Returns the entries of arr1 that have keys which are not present in any of the others arguments. This function is like array_diff() but works on the keys instead of the values. The associativity is preserved. */ -PHP_FUNCTION(array_diff_key) -{ - php_array_diff_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIFF_COMP_DATA_NONE); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have keys which are not present in any of the others arguments. User supplied function is used for comparing the keys. This function is like array_udiff() but works on the keys instead of the values. The associativity is preserved. */ PHP_FUNCTION(array_diff_ukey) { @@ -6163,13 +6136,6 @@ PHP_FUNCTION(array_udiff) } /* }}} */ -/* {{{ Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal */ -PHP_FUNCTION(array_diff_assoc) -{ - php_array_diff_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIFF_COMP_DATA_INTERNAL); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal. Elements are compared by user supplied function. */ PHP_FUNCTION(array_diff_uassoc) { @@ -6177,13 +6143,6 @@ PHP_FUNCTION(array_diff_uassoc) } /* }}} */ -/* {{{ Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal. Keys are compared by user supplied function. */ -PHP_FUNCTION(array_udiff_assoc) -{ - php_array_diff_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIFF_COMP_DATA_USER); -} -/* }}} */ - /* {{{ Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal. Keys and elements are compared by user supplied functions. */ PHP_FUNCTION(array_udiff_uassoc) { From b79fd79b48df5c09ae105b14b95cc61540e3c1e1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 17:52:40 +0100 Subject: [PATCH 6/7] std: rework loop inside of php_check_and_populate_array_matching_condition Use goto to mimic a continue statement to the outerloop --- ext/standard/array.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index a0a6306290ef..2a6fd70f07c4 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5124,7 +5124,6 @@ ZEND_ATTRIBUTE_NONNULL static void php_check_and_populate_array_matching_conditi ) { /* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */ ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), zend_ulong h, zend_string *key, zval *val) { - bool ok = true; zval *data; if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) { @@ -5134,27 +5133,22 @@ ZEND_ATTRIBUTE_NONNULL static void php_check_and_populate_array_matching_conditi for (uint32_t i = 1; i < argc; i++) { data = zend_hash_index_find(Z_ARRVAL(args[i]), h); if (callback(val, data) == callback_result) { - ok = false; - break; + goto next; } } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_index_add_new(ht, h, val); - } + Z_TRY_ADDREF_P(val); + zend_hash_index_add_new(ht, h, val); } else { for (uint32_t i = 1; i < argc; i++) { data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); if (callback(val, data) == callback_result) { - ok = false; - break; + goto next; } } - if (ok) { - Z_TRY_ADDREF_P(val); - zend_hash_add_new(ht, key, val); - } + Z_TRY_ADDREF_P(val); + zend_hash_add_new(ht, key, val); } + next: } ZEND_HASH_FOREACH_END(); } From e237ca60c0ab87fe0c8b6aeb085314ee3bcc713f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 20 Oct 2026 17:50:11 +0100 Subject: [PATCH 7/7] std: extract array_u{diff|intersect}_assoc implementation And refactor it so that it no longer relies on BG(user_compare_fci) --- ext/standard/array.c | 98 +++++++++++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 2a6fd70f07c4..19d498f3d739 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5091,22 +5091,16 @@ static bool zval_is_equal(zval *val, zval *data) } /* Note this assumes the user callback follows the same semantics of other compare functions so 0 means equal */ -static bool zval_is_user_equal(zval *val, zval *data) +static bool zval_is_user_equal(zval *data, zend_fcall_info_cache *fcc, zval *args) { if (!data) { return false; } - zval args[2]; zval retval; - ZVAL_COPY_VALUE(&args[0], val); ZVAL_COPY_VALUE(&args[1], data); + zend_call_known_fcc(fcc, &retval, 2, args, NULL); - BG(user_compare_fci).param_count = 2; - BG(user_compare_fci).params = args; - BG(user_compare_fci).retval = &retval; - - zend_call_function(&BG(user_compare_fci), &BG(user_compare_fci_cache)); zend_long ret = php_get_long(&retval); return ZEND_NORMALIZE_BOOL(ret) == 0; } @@ -5156,28 +5150,82 @@ static void php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAMETERS, php_diff_ { uint32_t argc; zval *args; - char *param_spec; - if (callback == zval_is_user_equal) { - /* array_uintersect_assoc() or array_udiff_assoc() */ - param_spec = "+f"; - } else { - param_spec = "+"; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) { + RETURN_THROWS(); } - if (zend_parse_parameters(ZEND_NUM_ARGS(), param_spec, &args, &argc, &BG(user_compare_fci), &BG(user_compare_fci_cache)) == FAILURE) { + for (uint32_t i = 0; i < argc; i++) { + if (Z_TYPE(args[i]) != IS_ARRAY) { + zend_argument_type_error(i + 1, "must be of type array, %s given", zend_zval_value_name(&args[i])); + RETURN_THROWS(); + } + } + + array_init(return_value); + php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, callback_result); +} + +ZEND_ATTRIBUTE_NONNULL static void php_check_and_populate_array_matching_condition_user_fn( + HashTable *ht, + uint32_t argc, zval *args, + zend_fcall_info_cache *fcc, bool callback_result +) { + /* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */ + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(args[0]), zend_ulong h, zend_string *key, zval *val) { + zval params[2]; + ZVAL_COPY_VALUE(¶ms[0], val); + zval *data; + + if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) { + val = Z_REFVAL_P(val); + } + if (key == NULL) { + for (uint32_t i = 1; i < argc; i++) { + data = zend_hash_index_find(Z_ARRVAL(args[i]), h); + if (zval_is_user_equal(data, fcc, params) == callback_result) { + goto next; + } + } + Z_TRY_ADDREF_P(val); + zend_hash_index_add_new(ht, h, val); + } else { + for (uint32_t i = 1; i < argc; i++) { + data = zend_hash_find_known_hash(Z_ARRVAL(args[i]), key); + if (zval_is_user_equal(data, fcc, params) == callback_result) { + goto next; + } + } + Z_TRY_ADDREF_P(val); + zend_hash_add_new(ht, key, val); + } + next: + } ZEND_HASH_FOREACH_END(); +} + +static void php_array_diff_intersect_key_user_fn(INTERNAL_FUNCTION_PARAMETERS, bool callback_result) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + uint32_t argc; + zval *args; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "+F", &args, &argc, &fci, &fcc) == FAILURE) { + zend_release_fcall_info_cache(&fcc); RETURN_THROWS(); } for (uint32_t i = 0; i < argc; i++) { if (Z_TYPE(args[i]) != IS_ARRAY) { zend_argument_type_error(i + 1, "must be of type array, %s given", zend_zval_value_name(&args[i])); + zend_release_fcall_info_cache(&fcc); RETURN_THROWS(); } } array_init(return_value); - php_check_and_populate_array_matching_condition(Z_ARR_P(return_value), argc, args, callback, callback_result); + php_check_and_populate_array_matching_condition_user_fn(Z_ARR_P(return_value), argc, args, &fcc, callback_result); + zend_release_fcall_info_cache(&fcc); } /* Returns the entries of arr1 that have keys which are present in all the other arguments. @@ -5195,14 +5243,6 @@ PHP_FUNCTION(array_intersect_assoc) php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_equal, false); } -/* Returns the entries of arr1 that have values which are present in all the other arguments. - * Keys are used to do more restrictive check. - * Data is compared by using a user-supplied callback. */ -PHP_FUNCTION(array_uintersect_assoc) -{ - php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_user_equal, false); -} - /* Returns the entries of arr1 that have keys which are not present in any of the others arguments. * This function is like array_diff() but works on the keys instead of the values. * The associativity is preserved. */ @@ -5217,11 +5257,19 @@ PHP_FUNCTION(array_diff_assoc) php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_equal, true); } +/* Returns the entries of arr1 that have values which are present in all the other arguments. + * Keys are used to do more restrictive check. + * Data is compared by using a user-supplied callback. */ +PHP_FUNCTION(array_uintersect_assoc) +{ + php_array_diff_intersect_key_user_fn(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); +} + /* Returns the entries of arr1 that have values which are not present in any of the others arguments but do additional checks whether the keys are equal. * Keys are compared by user supplied function. */ PHP_FUNCTION(array_udiff_assoc) { - php_array_diff_intersect_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, zval_is_user_equal, true); + php_array_diff_intersect_key_user_fn(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static void php_array_intersect(INTERNAL_FUNCTION_PARAMETERS, int behavior, int data_compare_type, int key_compare_type) /* {{{ */