Skip to content

Commit 40e322e

Browse files
Optimize array intersection functions for empty operands
1 parent f745a2f commit 40e322e

4 files changed

Lines changed: 221 additions & 0 deletions

File tree

ext/standard/array.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5048,6 +5048,18 @@ static void php_array_intersect_key(INTERNAL_FUNCTION_PARAMETERS, int data_compa
50485048
}
50495049
}
50505050

5051+
/* Later operands may only be checked when no data comparison is performed. */
5052+
if (data_compare_type == INTERSECT_COMP_DATA_NONE) {
5053+
for (i = 0; i < argc; i++) {
5054+
if (zend_hash_num_elements(Z_ARRVAL(args[i])) == 0) {
5055+
RETURN_EMPTY_ARRAY();
5056+
}
5057+
}
5058+
} else if (zend_hash_num_elements(Z_ARRVAL(args[0])) == 0 ||
5059+
(argc > 1 && zend_hash_num_elements(Z_ARRVAL(args[1])) == 0)) {
5060+
RETURN_EMPTY_ARRAY();
5061+
}
5062+
50515063
array_init(return_value);
50525064

50535065
/* Iterate over keys of the first array, to compute keys that are in all of the other arrays. */
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
--TEST--
2+
array_intersect_assoc() with empty operands
3+
--FILE--
4+
<?php
5+
6+
echo "Safe empty operands:\n";
7+
var_dump(array_intersect_assoc([], ['k' => []], ['k' => []]));
8+
var_dump(array_intersect_assoc(['k' => []], [], ['k' => []]));
9+
10+
echo "Single operand:\n";
11+
var_dump(array_intersect_assoc(['k' => 1]));
12+
var_dump(array_intersect_assoc([]));
13+
14+
echo "Warnings before third empty operand:\n";
15+
$warnings = [];
16+
set_error_handler(static function (int $severity, string $message) use (&$warnings): bool {
17+
if ($severity === E_WARNING) {
18+
$warnings[] = $message;
19+
return true;
20+
}
21+
return false;
22+
});
23+
$result = array_intersect_assoc(['k' => []], ['k' => []], []);
24+
restore_error_handler();
25+
var_dump($result);
26+
var_dump($warnings);
27+
28+
echo "Warning converted to exception:\n";
29+
set_error_handler(static function (int $severity, string $message): never {
30+
throw new ErrorException($message, 0, $severity);
31+
});
32+
try {
33+
array_intersect_assoc(['k' => []], ['k' => []], []);
34+
} catch (Throwable $e) {
35+
echo $e::class, ': ', $e->getMessage(), "\n";
36+
} finally {
37+
restore_error_handler();
38+
}
39+
40+
echo "Invalid argument after empty operand:\n";
41+
try {
42+
array_intersect_assoc(['k' => 1], [], 42);
43+
} catch (TypeError $e) {
44+
echo $e->getMessage(), "\n";
45+
}
46+
47+
?>
48+
--EXPECT--
49+
Safe empty operands:
50+
array(0) {
51+
}
52+
array(0) {
53+
}
54+
Single operand:
55+
array(1) {
56+
["k"]=>
57+
int(1)
58+
}
59+
array(0) {
60+
}
61+
Warnings before third empty operand:
62+
array(0) {
63+
}
64+
array(2) {
65+
[0]=>
66+
string(26) "Array to string conversion"
67+
[1]=>
68+
string(26) "Array to string conversion"
69+
}
70+
Warning converted to exception:
71+
ErrorException: Array to string conversion
72+
Invalid argument after empty operand:
73+
array_intersect_assoc(): Argument #3 must be of type array, int given
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
--TEST--
2+
array_intersect_key() with empty operands
3+
--FILE--
4+
<?php
5+
6+
echo "Empty operands:\n";
7+
var_dump(array_intersect_key([], ['a' => 1], ['a' => 1]));
8+
var_dump(array_intersect_key(['a' => 1], [], ['a' => 1]));
9+
var_dump(array_intersect_key(['a' => 1], ['a' => 1], []));
10+
11+
echo "Invalid argument after empty operand:\n";
12+
try {
13+
array_intersect_key(['a' => 1], [], 42);
14+
} catch (TypeError $e) {
15+
echo $e->getMessage(), "\n";
16+
}
17+
18+
echo "Single argument:\n";
19+
$single = [3 => 'three', 'a' => 'A', 1 => 'one'];
20+
var_dump(array_intersect_key($single) === $single);
21+
22+
echo "Result order:\n";
23+
$result = array_intersect_key($single, [1 => null, 3 => null, 'a' => null]);
24+
var_dump(array_keys($result));
25+
26+
echo "Append to empty result:\n";
27+
$result = array_intersect_key([9 => 'nine'], []);
28+
$result[] = 'appended';
29+
var_dump(array_keys($result));
30+
31+
echo "Single argument with stale next free index:\n";
32+
$single = [2 => 'two', 100 => 'removed'];
33+
unset($single[100]);
34+
$result = array_intersect_key($single);
35+
$result[] = 'appended';
36+
var_dump(array_keys($result));
37+
38+
?>
39+
--EXPECT--
40+
Empty operands:
41+
array(0) {
42+
}
43+
array(0) {
44+
}
45+
array(0) {
46+
}
47+
Invalid argument after empty operand:
48+
array_intersect_key(): Argument #3 must be of type array, int given
49+
Single argument:
50+
bool(true)
51+
Result order:
52+
array(3) {
53+
[0]=>
54+
int(3)
55+
[1]=>
56+
string(1) "a"
57+
[2]=>
58+
int(1)
59+
}
60+
Append to empty result:
61+
array(1) {
62+
[0]=>
63+
int(0)
64+
}
65+
Single argument with stale next free index:
66+
array(2) {
67+
[0]=>
68+
int(2)
69+
[1]=>
70+
int(3)
71+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
array_uintersect_assoc() with empty operands
3+
--FILE--
4+
<?php
5+
6+
$callbackCount = 0;
7+
$compare = static function (mixed $left, mixed $right) use (&$callbackCount): int {
8+
$callbackCount++;
9+
return $left <=> $right;
10+
};
11+
12+
echo "Single operand:\n";
13+
var_dump(array_uintersect_assoc(['k' => 1], $compare));
14+
var_dump($callbackCount);
15+
var_dump(array_uintersect_assoc([], $compare));
16+
var_dump($callbackCount);
17+
18+
echo "Second empty operand:\n";
19+
$result = array_uintersect_assoc(['k' => 1], [], ['k' => 1], $compare);
20+
var_dump($result);
21+
var_dump($callbackCount);
22+
23+
echo "Third empty operand:\n";
24+
$callbackCount = 0;
25+
$result = array_uintersect_assoc(['k' => 1], ['k' => 1], [], $compare);
26+
var_dump($result);
27+
var_dump($callbackCount);
28+
29+
echo "Invalid callback with empty operands:\n";
30+
try {
31+
array_uintersect_assoc([], [], '__missing_array_intersect_callback__');
32+
} catch (TypeError $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
36+
echo "Invalid argument after empty operand:\n";
37+
try {
38+
array_uintersect_assoc(['k' => 1], [], 42, $compare);
39+
} catch (TypeError $e) {
40+
echo $e->getMessage(), "\n";
41+
}
42+
43+
?>
44+
--EXPECT--
45+
Single operand:
46+
array(1) {
47+
["k"]=>
48+
int(1)
49+
}
50+
int(0)
51+
array(0) {
52+
}
53+
int(0)
54+
Second empty operand:
55+
array(0) {
56+
}
57+
int(0)
58+
Third empty operand:
59+
array(0) {
60+
}
61+
int(1)
62+
Invalid callback with empty operands:
63+
array_uintersect_assoc(): Argument #3 must be a valid callback, function "__missing_array_intersect_callback__" not found or invalid function name
64+
Invalid argument after empty operand:
65+
array_uintersect_assoc(): Argument #3 must be of type array, int given

0 commit comments

Comments
 (0)