Skip to content

Commit 4c71b4e

Browse files
committed
ext/standard: array_merge_recursive() fix leak.
object to array conversion failure leaked the temporary zval. while at it, fix reverse expectation on a failed neighbour insertion. Close GH-23340
1 parent 19ac30f commit 4c71b4e

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

ext/standard/array.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4133,12 +4133,13 @@ PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */
41334133
GC_TRY_UNPROTECT_RECURSION(thash);
41344134
}
41354135
if (!ret) {
4136+
zval_ptr_dtor(&tmp);
41364137
return 0;
41374138
}
41384139
} else {
41394140
Z_TRY_ADDREF_P(src_zval);
41404141
zval *zv = zend_hash_next_index_insert(Z_ARRVAL_P(dest_zval), src_zval);
4141-
if (EXPECTED(!zv)) {
4142+
if (UNEXPECTED(!zv)) {
41424143
Z_TRY_DELREF_P(src_zval);
41434144
zend_cannot_add_element();
41444145
return 0;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
array_merge_recursive() must not leak the array converted from an object when the merge below it fails
3+
--FILE--
4+
<?php
5+
6+
$dest = [];
7+
$dest['k'] = &$dest;
8+
try {
9+
array_merge_recursive($dest, ['k' => (object) ['k' => 1]]);
10+
} catch (\Throwable $e) {
11+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
12+
}
13+
14+
/* Control: same failing exit, array source, nothing to release. */
15+
$control = [];
16+
$control['k'] = &$control;
17+
try {
18+
array_merge_recursive($control, ['k' => ['k' => 1]]);
19+
} catch (\Throwable $e) {
20+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
21+
}
22+
23+
/* Several nested levels convert an object before the failure unwinds through them. */
24+
$ring = [[], [], []];
25+
for ($i = 0; $i < 3; $i++) {
26+
$ring[$i]['k'] = &$ring[($i + 1) % 3];
27+
}
28+
$src = (object) ['k' => 1];
29+
for ($i = 1; $i < 3; $i++) {
30+
$src = (object) ['k' => $src];
31+
}
32+
try {
33+
array_merge_recursive($ring[0], ['k' => $src]);
34+
} catch (\Throwable $e) {
35+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
36+
}
37+
38+
/* The successful path still releases it exactly once. */
39+
$ok = ['k' => ['a']];
40+
var_dump(array_merge_recursive($ok, ['k' => (object) ['b']]));
41+
42+
?>
43+
--EXPECT--
44+
Error: Recursion detected
45+
Error: Recursion detected
46+
Error: Recursion detected
47+
array(1) {
48+
["k"]=>
49+
array(2) {
50+
[0]=>
51+
string(1) "a"
52+
[1]=>
53+
string(1) "b"
54+
}
55+
}

0 commit comments

Comments
 (0)