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

Prevent throwing in running generator

Generator::throw() on a running generator is not allowed. It throws "Cannot
resume an already running generator" when trying to resume the generator to
handle the provided exception.

However, when calling Generator::throw() on a generator with a non-Generator
delegate, we release the delegate regardless. If a Fiber was suspended in
the delegate, this causes use after frees when the Fiber is resumed.

Fix this by throwing "Cannot resume an already running generator" earlier.

Fixes GH-19326
Closes GH-19327
This commit is contained in:
Arnaud Le Blanc
2025-07-31 09:18:23 +02:00
parent 0406a55c92
commit 6fa8a25a40
3 changed files with 50 additions and 2 deletions

2
NEWS
View File

@@ -15,6 +15,8 @@ PHP NEWS
causes assertion failure). (nielsdos) causes assertion failure). (nielsdos)
. Fixed bug GH-19306 (Generator can be resumed while fetching next value from . Fixed bug GH-19306 (Generator can be resumed while fetching next value from
delegated Generator). (Arnaud) delegated Generator). (Arnaud)
. Fixed bug GH-19326 (Calling Generator::throw() on a running generator with
a non-Generator delegate crashes). (Arnaud)
- FTP: - FTP:
. Fix theoretical issues with hrtime() not being available. (nielsdos) . Fix theoretical issues with hrtime() not being available. (nielsdos)

36
Zend/tests/gh19326.phpt Normal file
View File

@@ -0,0 +1,36 @@
--TEST--
GH-19326: Calling Generator::throw() on a running generator with a non-Generator delegate crashes
--FILE--
<?php
class It implements IteratorAggregate {
public function getIterator(): Generator {
yield "";
Fiber::suspend();
}
}
function g() {
yield from new It();
}
$b = g();
$b->rewind();
$fiber = new Fiber(function () use ($b) {
$b->next();
});
$fiber->start();
try {
$b->throw(new Exception('test'));
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$fiber->resume();
?>
--EXPECT--
Cannot resume an already running generator

View File

@@ -492,8 +492,14 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_
return ptr; return ptr;
} }
static void zend_generator_throw_exception(zend_generator *generator, zval *exception) static zend_result zend_generator_throw_exception(zend_generator *generator, zval *exception)
{ {
if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
zval_ptr_dtor(exception);
zend_throw_error(NULL, "Cannot resume an already running generator");
return FAILURE;
}
zend_execute_data *original_execute_data = EG(current_execute_data); zend_execute_data *original_execute_data = EG(current_execute_data);
/* Throw the exception in the context of the generator. Decrementing the opline /* Throw the exception in the context of the generator. Decrementing the opline
@@ -519,6 +525,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
} }
EG(current_execute_data) = original_execute_data; EG(current_execute_data) = original_execute_data;
return SUCCESS;
} }
static void zend_generator_add_child(zend_generator *generator, zend_generator *child) static void zend_generator_add_child(zend_generator *generator, zend_generator *child)
@@ -1026,7 +1034,9 @@ ZEND_METHOD(Generator, throw)
if (generator->execute_data) { if (generator->execute_data) {
zend_generator *root = zend_generator_get_current(generator); zend_generator *root = zend_generator_get_current(generator);
zend_generator_throw_exception(root, exception); if (zend_generator_throw_exception(root, exception) == FAILURE) {
return;
}
zend_generator_resume(generator); zend_generator_resume(generator);