[php-src] master: Merge branch 'PHP-8.6'
| From: | Weilin Du | Date: | Thu, 08 Oct 2026 14:54:39 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142085@lists.php.net to get a copy of this message | ||
Author: Weilin Du (LamentXU123)
Date: 2026-10-08T22:46:15+08:00
Commit: https://github.com/php/php-src/commit/222c13f3e0b660de0873bdefaee7ca644f92531e
Raw diff: https://github.com/php/php-src/commit/222c13f3e0b660de0873bdefaee7ca644f92531e.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
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 Zend/zend_exceptions.c
Diff:
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 0af55b7fbe9b..34dec6b8558b 100644
--- a/Zend/zend_exceptions.c
+++ b/Zend/zend_exceptions.c
@@ -140,6 +140,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);
}
/* }}} */