Merge branch 'PHP-8.2' into PHP-8.3

* PHP-8.2:
  [ci skip] NEWS
  Fix destruction of generator running in fibers during shutdown (#15158)
This commit is contained in:
Arnaud Le Blanc 2024-07-30 14:56:28 +02:00
commit e24101acb4
No known key found for this signature in database
GPG key ID: 0098C05DD15ABC13
9 changed files with 364 additions and 10 deletions

View file

@ -0,0 +1,36 @@
--TEST--
GH-15108 001: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function f() {
yield from new It();
}
$iterable = f();
$fiber = new Fiber(function () use ($iterable) {
var_dump($iterable->current());
$iterable->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==

View file

@ -0,0 +1,40 @@
--TEST--
GH-15108 002: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function g() {
yield from new It();
}
function f() {
yield from g();
}
$iterable = f();
$fiber = new Fiber(function () use ($iterable) {
var_dump($iterable->current());
$iterable->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==

View file

@ -0,0 +1,38 @@
--TEST--
GH-15108 003: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function f($gen) {
yield from $gen;
}
$a = new It();
$b = f($a);
$c = f($a);
$fiber = new Fiber(function () use ($a, $b, $c) {
var_dump($b->current());
$b->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==

View file

@ -0,0 +1,39 @@
--TEST--
GH-15108 004: Segfault with delegated generator in suspended fiber
--FILE--
<?php
function gen1() {
yield 'foo';
Fiber::suspend();
var_dump("not executed");
};
function gen2($gen) {
yield from $gen;
var_dump("not executed");
}
$a = gen1();
/* Both $b and $c have a root marked with IN_FIBER, but only $b is actually
* running in a fiber (at shutdown) */
$b = gen2($a);
$c = gen2($a);
$fiber = new Fiber(function () use ($a, $b, $c) {
var_dump($b->current());
var_dump($c->current());
$b->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
string(3) "foo"
==DONE==

View file

@ -0,0 +1,45 @@
--TEST--
GH-15108 005: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function f() {
yield from new It();
}
function g() {
yield from f();
}
function h() {
/* g() is an intermediate node and will not be marked with IN_FIBER */
yield from g();
}
$iterable = h();
var_dump($iterable->current());
$fiber = new Fiber(function () use ($iterable) {
$iterable->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==

View file

@ -0,0 +1,49 @@
--TEST--
GH-15108 006: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function f() {
yield from new It();
}
function g() {
yield from f();
}
function gen($gen) {
/* $gen is an intermediate node and will not be marked with IN_FIBER */
yield from $gen;
}
$g = g();
$a = gen($g);
$b = gen($g);
var_dump($a->current());
var_dump($b->current());
$fiber = new Fiber(function () use ($a, $b, $g) {
$a->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
string(3) "foo"
==DONE==

View file

@ -0,0 +1,51 @@
--TEST--
GH-15108 007: Segfault with delegated generator in suspended fiber
--FILE--
<?php
class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}
function f() {
yield from new It();
}
function g() {
yield from f();
}
function gen($gen) {
/* $gen is an intermediate node and will not be marked with IN_FIBER */
yield from $gen;
}
$g = g();
$a = gen($g);
$b = gen($g);
$c = gen($g);
$d = gen($g);
var_dump($a->current());
var_dump($b->current());
$fiber = new Fiber(function () use ($a, $b, $c, $d, $g) {
$b->next();
var_dump("not executed");
});
$ref = $fiber;
$fiber->start();
?>
==DONE==
--EXPECT--
string(3) "foo"
string(3) "foo"
==DONE==

View file

@ -19,6 +19,7 @@
#include "zend.h" #include "zend.h"
#include "zend_API.h" #include "zend_API.h"
#include "zend_hash.h"
#include "zend_interfaces.h" #include "zend_interfaces.h"
#include "zend_exceptions.h" #include "zend_exceptions.h"
#include "zend_generators.h" #include "zend_generators.h"
@ -216,19 +217,68 @@ static zend_always_inline void clear_link_to_root(zend_generator *generator) {
} }
} }
/* In the context of zend_generator_dtor_storage during shutdown, check if
* the intermediate node 'generator' is running in a fiber */
static inline bool check_node_running_in_fiber(zend_generator *generator) {
ZEND_ASSERT(EG(flags) & EG_FLAGS_IN_SHUTDOWN);
ZEND_ASSERT(generator->execute_data);
if (generator->flags & ZEND_GENERATOR_IN_FIBER) {
return true;
}
if (generator->node.children == 0) {
return false;
}
if (generator->flags & ZEND_GENERATOR_DTOR_VISITED) {
return false;
}
generator->flags |= ZEND_GENERATOR_DTOR_VISITED;
if (generator->node.children == 1) {
if (check_node_running_in_fiber(generator->node.child.single)) {
goto in_fiber;
}
return false;
}
zend_generator *child;
ZEND_HASH_FOREACH_PTR(generator->node.child.ht, child) {
if (check_node_running_in_fiber(child)) {
goto in_fiber;
}
} ZEND_HASH_FOREACH_END();
return false;
in_fiber:
generator->flags |= ZEND_GENERATOR_IN_FIBER;
return true;
}
static void zend_generator_dtor_storage(zend_object *object) /* {{{ */ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
{ {
zend_generator *generator = (zend_generator*) object; zend_generator *generator = (zend_generator*) object;
zend_generator *current_generator = zend_generator_get_current(generator);
zend_execute_data *ex = generator->execute_data; zend_execute_data *ex = generator->execute_data;
uint32_t op_num, try_catch_offset; uint32_t op_num, try_catch_offset;
int i; int i;
/* Generator is running in a suspended fiber. /* If current_generator is running in a fiber, there are 2 cases to consider:
* Will be dtor during fiber dtor */ * - If generator is also marked with ZEND_GENERATOR_IN_FIBER, then the
if (zend_generator_get_current(generator)->flags & ZEND_GENERATOR_IN_FIBER) { * entire path from current_generator to generator is executing in a
/* Prevent finally blocks from yielding */ * fiber. Do not dtor now: These will be dtor when terminating the fiber.
generator->flags |= ZEND_GENERATOR_FORCED_CLOSE; * - If generator is not marked with ZEND_GENERATOR_IN_FIBER, and has a
return; * child marked with ZEND_GENERATOR_IN_FIBER, then this an intermediate
* node of case 1. Otherwise generator is not executing in a fiber and we
* can dtor.
*/
if (current_generator->flags & ZEND_GENERATOR_IN_FIBER) {
if (check_node_running_in_fiber(generator)) {
/* Prevent finally blocks from yielding */
generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
return;
}
} }
/* leave yield from mode to properly allow finally execution */ /* leave yield from mode to properly allow finally execution */
@ -718,6 +768,11 @@ try_again:
return; return;
} }
if (EG(active_fiber)) {
orig_generator->flags |= ZEND_GENERATOR_IN_FIBER;
generator->flags |= ZEND_GENERATOR_IN_FIBER;
}
/* Drop the AT_FIRST_YIELD flag */ /* Drop the AT_FIRST_YIELD flag */
orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
@ -748,7 +803,8 @@ try_again:
EG(current_execute_data) = original_execute_data; EG(current_execute_data) = original_execute_data;
EG(jit_trace_num) = original_jit_trace_num; EG(jit_trace_num) = original_jit_trace_num;
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
return; return;
} }
/* If there are no more delegated values, resume the generator /* If there are no more delegated values, resume the generator
@ -761,8 +817,7 @@ try_again:
} }
/* Resume execution */ /* Resume execution */
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
| (EG(active_fiber) ? ZEND_GENERATOR_IN_FIBER : 0);
if (!ZEND_OBSERVER_ENABLED) { if (!ZEND_OBSERVER_ENABLED) {
zend_execute_ex(generator->execute_data); zend_execute_ex(generator->execute_data);
} else { } else {
@ -813,7 +868,7 @@ try_again:
goto try_again; goto try_again;
} }
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
} }
/* }}} */ /* }}} */

View file

@ -95,6 +95,7 @@ static const uint8_t ZEND_GENERATOR_FORCED_CLOSE = 0x2;
static const uint8_t ZEND_GENERATOR_AT_FIRST_YIELD = 0x4; static const uint8_t ZEND_GENERATOR_AT_FIRST_YIELD = 0x4;
static const uint8_t ZEND_GENERATOR_DO_INIT = 0x8; static const uint8_t ZEND_GENERATOR_DO_INIT = 0x8;
static const uint8_t ZEND_GENERATOR_IN_FIBER = 0x10; static const uint8_t ZEND_GENERATOR_IN_FIBER = 0x10;
static const uint8_t ZEND_GENERATOR_DTOR_VISITED = 0x20;
void zend_register_generator_ce(void); void zend_register_generator_ce(void);
ZEND_API void zend_generator_close(zend_generator *generator, bool finished_execution); ZEND_API void zend_generator_close(zend_generator *generator, bool finished_execution);