[php-src] PHP-8.6: Merge branch 'PHP-8.5' into PHP-8.6

From: 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); } /* }}} */

« previous php.cvs (#142084) next »