From 40e322e201f4afa96ba56a818b771ea4ae902e39 Mon Sep 17 00:00:00 2001 From: mehmetcansahin Date: Mon, 20 Jul 2026 17:06:38 +0300 Subject: [PATCH] Optimize array intersection functions for empty operands --- ext/standard/array.c | 12 +++ .../array_intersect_assoc_empty_operands.phpt | 73 +++++++++++++++++++ .../array_intersect_key_empty_operands.phpt | 71 ++++++++++++++++++ ...array_uintersect_assoc_empty_operands.phpt | 65 +++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 ext/standard/tests/array/array_intersect_assoc_empty_operands.phpt create mode 100644 ext/standard/tests/array/array_intersect_key_empty_operands.phpt create mode 100644 ext/standard/tests/array/array_uintersect_assoc_empty_operands.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index 2d1dd584906a..2160bd7a8cbb 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -5048,6 +5048,18 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa } } + /* Later operands may only be checked when no data comparison is performed. */ + if (data_compare_type == INTERSECT_COMP_DATA_NONE) { + for (i = 0; i < argc; i++) { + if (zend_hash_num_elements(Z_ARRVAL(args[i])) == 0) { + RETURN_EMPTY_ARRAY(); + } + } + } else if (zend_hash_num_elements(Z_ARRVAL(args[0])) == 0 || + (argc > 1 && zend_hash_num_elements(Z_ARRVAL(args[1])) == 0)) { + RETURN_EMPTY_ARRAY(); + } + array_init(return_value); /* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */ diff --git a/ext/standard/tests/array/array_intersect_assoc_empty_operands.phpt b/ext/standard/tests/array/array_intersect_assoc_empty_operands.phpt new file mode 100644 index 000000000000..c097c66f8d00 --- /dev/null +++ b/ext/standard/tests/array/array_intersect_assoc_empty_operands.phpt @@ -0,0 +1,73 @@ +--TEST-- +array_intersect_assoc() with empty operands +--FILE-- + []], ['k' => []])); +var_dump(array_intersect_assoc(['k' => []], [], ['k' => []])); + +echo "Single operand:\n"; +var_dump(array_intersect_assoc(['k' => 1])); +var_dump(array_intersect_assoc([])); + +echo "Warnings before third empty operand:\n"; +$warnings = []; +set_error_handler(static function (int $severity, string $message) use (&$warnings): bool { + if ($severity === E_WARNING) { + $warnings[] = $message; + return true; + } + return false; +}); +$result = array_intersect_assoc(['k' => []], ['k' => []], []); +restore_error_handler(); +var_dump($result); +var_dump($warnings); + +echo "Warning converted to exception:\n"; +set_error_handler(static function (int $severity, string $message): never { + throw new ErrorException($message, 0, $severity); +}); +try { + array_intersect_assoc(['k' => []], ['k' => []], []); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} finally { + restore_error_handler(); +} + +echo "Invalid argument after empty operand:\n"; +try { + array_intersect_assoc(['k' => 1], [], 42); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Safe empty operands: +array(0) { +} +array(0) { +} +Single operand: +array(1) { + ["k"]=> + int(1) +} +array(0) { +} +Warnings before third empty operand: +array(0) { +} +array(2) { + [0]=> + string(26) "Array to string conversion" + [1]=> + string(26) "Array to string conversion" +} +Warning converted to exception: +ErrorException: Array to string conversion +Invalid argument after empty operand: +array_intersect_assoc(): Argument #3 must be of type array, int given diff --git a/ext/standard/tests/array/array_intersect_key_empty_operands.phpt b/ext/standard/tests/array/array_intersect_key_empty_operands.phpt new file mode 100644 index 000000000000..84a313f3f2a5 --- /dev/null +++ b/ext/standard/tests/array/array_intersect_key_empty_operands.phpt @@ -0,0 +1,71 @@ +--TEST-- +array_intersect_key() with empty operands +--FILE-- + 1], ['a' => 1])); +var_dump(array_intersect_key(['a' => 1], [], ['a' => 1])); +var_dump(array_intersect_key(['a' => 1], ['a' => 1], [])); + +echo "Invalid argument after empty operand:\n"; +try { + array_intersect_key(['a' => 1], [], 42); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +echo "Single argument:\n"; +$single = [3 => 'three', 'a' => 'A', 1 => 'one']; +var_dump(array_intersect_key($single) === $single); + +echo "Result order:\n"; +$result = array_intersect_key($single, [1 => null, 3 => null, 'a' => null]); +var_dump(array_keys($result)); + +echo "Append to empty result:\n"; +$result = array_intersect_key([9 => 'nine'], []); +$result[] = 'appended'; +var_dump(array_keys($result)); + +echo "Single argument with stale next free index:\n"; +$single = [2 => 'two', 100 => 'removed']; +unset($single[100]); +$result = array_intersect_key($single); +$result[] = 'appended'; +var_dump(array_keys($result)); + +?> +--EXPECT-- +Empty operands: +array(0) { +} +array(0) { +} +array(0) { +} +Invalid argument after empty operand: +array_intersect_key(): Argument #3 must be of type array, int given +Single argument: +bool(true) +Result order: +array(3) { + [0]=> + int(3) + [1]=> + string(1) "a" + [2]=> + int(1) +} +Append to empty result: +array(1) { + [0]=> + int(0) +} +Single argument with stale next free index: +array(2) { + [0]=> + int(2) + [1]=> + int(3) +} diff --git a/ext/standard/tests/array/array_uintersect_assoc_empty_operands.phpt b/ext/standard/tests/array/array_uintersect_assoc_empty_operands.phpt new file mode 100644 index 000000000000..cfe255a2a33b --- /dev/null +++ b/ext/standard/tests/array/array_uintersect_assoc_empty_operands.phpt @@ -0,0 +1,65 @@ +--TEST-- +array_uintersect_assoc() with empty operands +--FILE-- + $right; +}; + +echo "Single operand:\n"; +var_dump(array_uintersect_assoc(['k' => 1], $compare)); +var_dump($callbackCount); +var_dump(array_uintersect_assoc([], $compare)); +var_dump($callbackCount); + +echo "Second empty operand:\n"; +$result = array_uintersect_assoc(['k' => 1], [], ['k' => 1], $compare); +var_dump($result); +var_dump($callbackCount); + +echo "Third empty operand:\n"; +$callbackCount = 0; +$result = array_uintersect_assoc(['k' => 1], ['k' => 1], [], $compare); +var_dump($result); +var_dump($callbackCount); + +echo "Invalid callback with empty operands:\n"; +try { + array_uintersect_assoc([], [], '__missing_array_intersect_callback__'); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +echo "Invalid argument after empty operand:\n"; +try { + array_uintersect_assoc(['k' => 1], [], 42, $compare); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Single operand: +array(1) { + ["k"]=> + int(1) +} +int(0) +array(0) { +} +int(0) +Second empty operand: +array(0) { +} +int(0) +Third empty operand: +array(0) { +} +int(1) +Invalid callback with empty operands: +array_uintersect_assoc(): Argument #3 must be a valid callback, function "__missing_array_intersect_callback__" not found or invalid function name +Invalid argument after empty operand: +array_uintersect_assoc(): Argument #3 must be of type array, int given