1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix infinite loop in GC destructor fiber
This commit is contained in:
Ilija Tovilo
2026-01-11 01:21:30 +01:00
3 changed files with 86 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
--TEST--
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
--FILE--
<?php
class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
try {
Fiber::suspend();
} finally {
throw new Exception();
}
}
}
$f = new Fiber(function () {
new Cycle();
gc_collect_cycles();
});
$f->start();
?>
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 [internal function]: Cycle->__destruct()
#1 [internal function]: gc_destructor_fiber()
#2 {main}
thrown in %s on line %d

View File

@@ -0,0 +1,34 @@
--TEST--
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
--FILE--
<?php
class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
try {
Fiber::suspend();
} finally {
Fiber::suspend();
}
}
}
$f = new Fiber(function () {
new Cycle();
gc_collect_cycles();
});
$f->start();
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot suspend in a force-closed fiber in %s:%d
Stack trace:
#0 %s(%d): Fiber::suspend()
#1 [internal function]: Cycle->__destruct()
#2 [internal function]: gc_destructor_fiber()
#3 {main}
thrown in %s on line %d

View File

@@ -76,6 +76,7 @@
#include "zend_types.h"
#include "zend_weakrefs.h"
#include "zend_string.h"
#include "zend_exceptions.h"
#ifndef GC_BENCH
# define GC_BENCH 0
@@ -1932,6 +1933,17 @@ static zend_fiber *gc_create_destructor_fiber(void)
return fiber;
}
static void remember_prev_exception(zend_object **prev_exception)
{
if (EG(exception)) {
if (*prev_exception) {
zend_exception_set_previous(EG(exception), *prev_exception);
}
*prev_exception = EG(exception);
EG(exception) = NULL;
}
}
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
{
ZEND_ASSERT(!GC_G(dtor_fiber_running));
@@ -1947,6 +1959,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
zend_fiber_resume(fiber, NULL, NULL);
}
zend_object *exception = NULL;
remember_prev_exception(&exception);
for (;;) {
/* At this point, fiber has executed until suspension */
GC_TRACE("resumed from destructor fiber");
@@ -1960,7 +1975,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
/* We do not own the fiber anymore. It may be collected if the
* application does not reference it. */
zend_object_release(&fiber->std);
remember_prev_exception(&exception);
fiber = gc_create_destructor_fiber();
remember_prev_exception(&exception);
continue;
} else {
/* Fiber suspended itself after calling all destructors */
@@ -1968,6 +1985,8 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
break;
}
}
EG(exception) = exception;
}
/* Perform a garbage collection run. The default implementation of gc_collect_cycles. */