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

From: Date: Mon, 05 Oct 2026 16:52:21 +0000
Subject: [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-141968@lists.php.net to get a copy of this message
Author: Arnaud Le Blanc (arnaud-lb) Date: 2026-10-05T18:49:59+02:00 Commit: https://github.com/php/php-src/commit/f2a89256996908816955c573968715000c3426af Raw diff: https://github.com/php/php-src/commit/f2a89256996908816955c573968715000c3426af.diff Merge branch 'PHP-8.4' into PHP-8.5 * PHP-8.4: [ci skip] NEWS Rethrow exceptions from destructors called by the GC in a fiber (#24118) Changed paths: A Zend/tests/fibers/destructors_013.phpt A Zend/tests/fibers/destructors_014.phpt A Zend/tests/fibers/destructors_015.phpt M NEWS M Zend/zend_gc.c Diff: diff --git a/NEWS b/NEWS index 4d949ee74209..94cad245e0f1 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,8 @@ PHP NEWS function and a later request calls it. (Ilia Alshanetsky) . Fixed System V shared memory emulation for Windows attaching segments past the end of their mapping. (Ilia Alshanetsky) + . Fixed exception thrown by destructor during GC in a Fiber not being rethrown + into the frame that triggered the GC. (Nicolas Grekas) - CLI . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during diff --git a/Zend/tests/fibers/destructors_013.phpt b/Zend/tests/fibers/destructors_013.phpt new file mode 100644 index 000000000000..0e555b370569 --- /dev/null +++ b/Zend/tests/fibers/destructors_013.phpt @@ -0,0 +1,49 @@ +--TEST-- +Fibers in destructors 013: Exception thrown by a destructor called by the GC is thrown by the statement that triggered the GC +--INI-- +zend.enable_gc=1 +--FILE-- +<?php + +class Cycle { + public $self; + public function __construct() { + $this->self = $this; + } + public function __destruct() { + echo "Cycle::__destruct\n"; + throw new Exception('Cycle::__destruct'); + } +} + +$objects = []; +for ($i = 0; $i < 20000; $i++) { + $objects[] = new stdClass(); +} + +function f() { + global $objects; + try { + $copies = [...$objects]; + new Cycle(); + // Releasing the copies fills the GC root buffer + $copies = null; + echo "Not reached\n"; + } catch (Exception $e) { + echo 'Caught: ', $e->getMessage(), "\n"; + } +} + +$fiber = new Fiber(function () { + try { + f(); + } catch (Exception $e) { + echo 'Escaped: ', $e->getMessage(), "\n"; + } +}); +$fiber->start(); + +?> +--EXPECT-- +Cycle::__destruct +Caught: Cycle::__destruct diff --git a/Zend/tests/fibers/destructors_014.phpt b/Zend/tests/fibers/destructors_014.phpt new file mode 100644 index 000000000000..be8c335e6781 --- /dev/null +++ b/Zend/tests/fibers/destructors_014.phpt @@ -0,0 +1,55 @@ +--TEST-- +Fibers in destructors 014: Exception thrown by a destructor called by the GC during exception unwinding is chained +--INI-- +zend.enable_gc=1 +--FILE-- +<?php + +class Cycle { + public $self; + public function __construct() { + $this->self = $this; + } + public function __destruct() { + echo "Cycle::__destruct\n"; + throw new Exception('Cycle::__destruct'); + } +} + +$objects = []; +for ($i = 0; $i < 20000; $i++) { + $objects[] = new stdClass(); +} + +function f() { + global $objects; + // Releasing these copies during unwinding fills the GC root buffer + $copies = [...$objects]; + new Cycle(); + throw new Exception('f'); +} + +function g() { + try { + f(); + echo "Not reached\n"; + } catch (Exception $e) { + echo 'Caught: ', $e->getMessage(), "\n"; + echo 'Previous: ', $e->getPrevious()->getMessage(), "\n"; + } +} + +$fiber = new Fiber(function () { + try { + g(); + } catch (Exception $e) { + echo 'Escaped: ', $e->getMessage(), "\n"; + } +}); +$fiber->start(); + +?> +--EXPECT-- +Cycle::__destruct +Caught: Cycle::__destruct +Previous: f diff --git a/Zend/tests/fibers/destructors_015.phpt b/Zend/tests/fibers/destructors_015.phpt new file mode 100644 index 000000000000..481c69fc795a --- /dev/null +++ b/Zend/tests/fibers/destructors_015.phpt @@ -0,0 +1,60 @@ +--TEST-- +Fibers in destructors 015: Exception thrown by a destructor called by the GC from internal code +--INI-- +zend.enable_gc=1 +--FILE-- +<?php + +class Cycle { + public $self; + public function __construct() { + $this->self = $this; + } + public function __destruct() { + echo "Cycle::__destruct\n"; + throw new Exception('Cycle::__destruct'); + } +} + +$fiber = new Fiber(function () { + try { + new Cycle(); + gc_collect_cycles(); + echo "Not reached\n"; + } catch (Exception $e) { + echo 'Caught: ', $e->getMessage(), "\n"; + } +}); +$fiber->start(); + +$objects = []; +for ($i = 0; $i < 20000; $i++) { + $objects[] = new stdClass(); +} + +function create_fiber() { + global $objects; + $copies = [...$objects]; + + // The fiber releases the copies after its function returned, which fills the GC root buffer + return new Fiber(function () use ($copies) { + new Cycle(); + echo "Return\n"; + }); +} + +$fiber = create_fiber(); +try { + $fiber->start(); + echo "Not reached\n"; +} catch (Exception $e) { + echo 'Caught: ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cycle::__destruct +Caught: Cycle::__destruct +Return +Cycle::__destruct +Caught: Cycle::__destruct diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index 066453552ba8..281994dbfd7f 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -1958,6 +1958,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end) zend_object *exception = NULL; remember_prev_exception(&exception); + zend_object *old_exception = exception; if (UNEXPECTED(!fiber)) { fiber = gc_create_destructor_fiber(); @@ -1992,6 +1993,12 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end) } EG(exception) = exception; + + /* Destructors ran in another fiber, out of reach of zend_call_function()'s rethrow */ + if (exception && !old_exception && EG(current_execute_data) && EG(current_execute_data)->func + && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { + zend_rethrow_exception(EG(current_execute_data)); + } } /* Perform a garbage collection run. The default implementation of gc_collect_cycles. */

« previous php.cvs (#141968) next »