[php-src] PHP-8.5: Merge branch 'PHP-8.4' into 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:30+01:00

Commit: https://github.com/php/php-src/commit/067408fd2edcc505f3c9750ffcc834dae6c73b50
Raw diff: https://github.com/php/php-src/commit/067408fd2edcc505f3c9750ffcc834dae6c73b50.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  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 fcc6b3879666..2bdb51d30598 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -4124,12 +4124,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"
+  }
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.