[php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6
| From: | Weilin Du | Date: | Thu, 08 Oct 2026 14:54:39 +0000 |
| Subject: | [php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6 | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142084@lists.php.net to get a copy of this message | ||
Author: Weilin Du (LamentXU123)
Date: 2026-10-08T22:45:50+08:00
Commit: https://github.com/php/php-src/commit/fd19f41128d1137931eed50060613ab24d52292b
Raw diff: https://github.com/php/php-src/commit/fd19f41128d1137931eed50060613ab24d52292b.diff
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5:
Fix leak when the added previous exception is already in the chain (#24177)
Changed paths:
A Zend/tests/exception_previous_already_in_chain_destructor.phpt
A Zend/tests/fibers/gc-destructor-throw-previous-already-in-chain.phpt
A Zend/tests/fibers/unfinished-fiber-with-throw-previous-already-in-chain.phpt
A Zend/tests/generators/finally/yield_throw_previous_already_in_chain.phpt
A Zend/tests/try/try_finally_previous_already_in_chain.phpt
M NEWS
M Zend/zend_exceptions.c
Diff:
diff --git a/NEWS b/NEWS
index 2f82b0f2b5ba..f58b84c88fda 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.6.0RC4
+- Core:
+ . Fixed memory leak when an exception already contains the pending exception
+ in its previous chain. (Edmond)
+
- Opcache:
. Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a
root trace at the opcache.jit_max_root_traces limit, causing spurious
diff --git a/Zend/tests/exception_previous_already_in_chain_destructor.phpt
b/Zend/tests/exception_previous_already_in_chain_destructor.phpt
new file mode 100644
index 000000000000..d302609aa76c
--- /dev/null
+++ b/Zend/tests/exception_previous_already_in_chain_destructor.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Exception thrown in a destructor during unwinding that already has the pending exception in its
previous chain
+--FILE--
+<?php
+class ThrowsInDestructor {
+ public function __construct(private Throwable $pending) {}
+
+ public function __destruct() {
+ throw new LogicException("thrown", 0, $this->pending);
+ }
+}
+
+function f() {
+ $pending = new RuntimeException("pending");
+ $object = new ThrowsInDestructor($pending);
+ throw $pending;
+}
+
+try {
+ f();
+} catch (Throwable $t) {
+ for (; $t !== null; $t = $t->getPrevious()) {
+ echo $t->getMessage(), "\n";
+ }
+}
+?>
+--EXPECT--
+thrown
+pending
diff --git a/Zend/tests/fibers/gc-destructor-throw-previous-already-in-chain.phpt
b/Zend/tests/fibers/gc-destructor-throw-previous-already-in-chain.phpt
new file mode 100644
index 000000000000..0e50ab9fb29a
--- /dev/null
+++ b/Zend/tests/fibers/gc-destructor-throw-previous-already-in-chain.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Exception thrown by a GC destructor that already has the exception of a previous, suspended
destructor in its previous chain
+--FILE--
+<?php
+class Cycle {
+ public static ?Throwable $pending = null;
+ public $self;
+
+ public function __construct() {
+ $this->self = $this;
+ }
+
+ public function __destruct() {
+ if (self::$pending === null) {
+ try {
+ Fiber::suspend();
+ } finally {
+ self::$pending = new RuntimeException("pending");
+ throw self::$pending;
+ }
+ }
+ throw new LogicException("thrown", 0, self::$pending);
+ }
+}
+
+$fiber = new Fiber(function () {
+ new Cycle;
+ new Cycle;
+ try {
+ gc_collect_cycles();
+ } catch (Throwable $t) {
+ for (; $t !== null; $t = $t->getPrevious()) {
+ echo $t->getMessage(), "\n";
+ }
+ }
+});
+$fiber->start();
+?>
+--EXPECT--
+thrown
+pending
diff --git a/Zend/tests/fibers/unfinished-fiber-with-throw-previous-already-in-chain.phpt
b/Zend/tests/fibers/unfinished-fiber-with-throw-previous-already-in-chain.phpt
new file mode 100644
index 000000000000..cf27c8eeff02
--- /dev/null
+++ b/Zend/tests/fibers/unfinished-fiber-with-throw-previous-already-in-chain.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Exception thrown in finally of a fiber destroyed during unwinding that already has the pending
exception in its previous chain
+--FILE--
+<?php
+function f() {
+ $pending = new RuntimeException("pending");
+ $fiber = new Fiber(function () use ($pending) {
+ try {
+ Fiber::suspend();
+ } finally {
+ throw new LogicException("thrown", 0, $pending);
+ }
+ });
+ $fiber->start();
+ throw $pending;
+}
+
+try {
+ f();
+} catch (Throwable $t) {
+ for (; $t !== null; $t = $t->getPrevious()) {
+ echo $t->getMessage(), "\n";
+ }
+}
+?>
+--EXPECT--
+thrown
+pending
diff --git a/Zend/tests/generators/finally/yield_throw_previous_already_in_chain.phpt
b/Zend/tests/generators/finally/yield_throw_previous_already_in_chain.phpt
new file mode 100644
index 000000000000..9205eedbc5ea
--- /dev/null
+++ b/Zend/tests/generators/finally/yield_throw_previous_already_in_chain.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Exception thrown in finally of a generator destroyed during unwinding that already has the pending
exception in its previous chain
+--FILE--
+<?php
+function gen(Throwable $pending) {
+ try {
+ yield;
+ } finally {
+ throw new LogicException("thrown", 0, $pending);
+ }
+}
+
+function f() {
+ $pending = new RuntimeException("pending");
+ $generator = gen($pending);
+ $generator->current();
+ throw $pending;
+}
+
+try {
+ f();
+} catch (Throwable $t) {
+ for (; $t !== null; $t = $t->getPrevious()) {
+ echo $t->getMessage(), "\n";
+ }
+}
+?>
+--EXPECT--
+thrown
+pending
diff --git a/Zend/tests/try/try_finally_previous_already_in_chain.phpt
b/Zend/tests/try/try_finally_previous_already_in_chain.phpt
new file mode 100644
index 000000000000..fd0bdc20cb79
--- /dev/null
+++ b/Zend/tests/try/try_finally_previous_already_in_chain.phpt
@@ -0,0 +1,38 @@
+--TEST--
+Exception thrown in finally that already has the pending exception in its previous chain
+--FILE--
+<?php
+function direct() {
+ $e = new RuntimeException("pending");
+ try {
+ throw $e;
+ } finally {
+ throw new LogicException("thrown", 0, $e);
+ }
+}
+
+function nested() {
+ $e = new RuntimeException("pending");
+ try {
+ throw $e;
+ } finally {
+ throw new LogicException("thrown", 0, new Exception("middle", 0, $e));
+ }
+}
+
+foreach (['direct', 'nested'] as $function) {
+ try {
+ $function();
+ } catch (Throwable $t) {
+ for (; $t !== null; $t = $t->getPrevious()) {
+ echo $t->getMessage(), "\n";
+ }
+ }
+}
+?>
+--EXPECT--
+thrown
+pending
+thrown
+middle
+pending
diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c
index 9702a335d461..3b645b8fbbf9 100644
--- a/Zend/zend_exceptions.c
+++ b/Zend/zend_exceptions.c
@@ -141,6 +141,8 @@ void zend_exception_set_previous(zend_object *exception, zend_object *add_previo
}
ex = previous;
} while (Z_OBJ_P(ex) != add_previous);
+
+ OBJ_RELEASE(add_previous);
}
/* }}} */