[php-src] master: Merge branch 'PHP-8.5'
David Carlier <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: David Carlier (devnexen)
Date: 2026-08-18T12:28:33+01:00
Commit: https://github.com/php/php-src/commit/a482d662b11e7e3f5c026ba316caed9c81155118
Raw diff: https://github.com/php/php-src/commit/a482d662b11e7e3f5c026ba316caed9c81155118.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
ext/standard: array_merge_recursive() fix leak.
Changed paths:
A ext/standard/tests/array/array_merge_recursive_object_leak.phpt
M ext/standard/array.c
Diff:
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 648b48479099..acf65c07bd67 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -4037,12 +4037,13 @@ PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */
GC_TRY_UNPROTECT_RECURSION(thash);
}
if (!ret) {
+ zval_ptr_dtor(&tmp);
return 0;
}
} else {
Z_TRY_ADDREF_P(src_zval);
zval *zv = zend_hash_next_index_insert(Z_ARRVAL_P(dest_zval), src_zval);
- if (EXPECTED(!zv)) {
+ if (UNEXPECTED(!zv)) {
Z_TRY_DELREF_P(src_zval);
zend_cannot_add_element();
return 0;
diff --git a/ext/standard/tests/array/array_merge_recursive_object_leak.phpt b/ext/standard/tests/array/array_merge_recursive_object_leak.phpt
new file mode 100644
index 000000000000..f4313057cf81
--- /dev/null
+++ b/ext/standard/tests/array/array_merge_recursive_object_leak.phpt
@@ -0,0 +1,55 @@
+--TEST--
+array_merge_recursive() must not leak the array converted from an object when the merge below it fails
+--FILE--
+<?php
+
+$dest = [];
+$dest['k'] = &$dest;
+try {
+ array_merge_recursive($dest, ['k' => (object) ['k' => 1]]);
+} catch (\Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+/* Control: same failing exit, array source, nothing to release. */
+$control = [];
+$control['k'] = &$control;
+try {
+ array_merge_recursive($control, ['k' => ['k' => 1]]);
+} catch (\Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+/* Several nested levels convert an object before the failure unwinds through them. */
+$ring = [[], [], []];
+for ($i = 0; $i < 3; $i++) {
+ $ring[$i]['k'] = &$ring[($i + 1) % 3];
+}
+$src = (object) ['k' => 1];
+for ($i = 1; $i < 3; $i++) {
+ $src = (object) ['k' => $src];
+}
+try {
+ array_merge_recursive($ring[0], ['k' => $src]);
+} catch (\Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+/* The successful path still releases it exactly once. */
+$ok = ['k' => ['a']];
+var_dump(array_merge_recursive($ok, ['k' => (object) ['b']]));
+
+?>
+--EXPECT--
+Error: Recursion detected
+Error: Recursion detected
+Error: Recursion detected
+array(1) {
+ ["k"]=>
+ array(2) {
+ [0]=>
+ string(1) "a"
+ [1]=>
+ string(1) "b"
+ }
+}