Skip to content

Commit 6fa8a25

Browse files
committed
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
1 parent 0406a55 commit 6fa8a25

File tree

3 files changed

+50
-2
lines changed

3 files changed

+50
-2
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ PHP NEWS
1515
causes assertion failure). (nielsdos)
1616
. Fixed bug GH-19306 (Generator can be resumed while fetching next value from
1717
delegated Generator). (Arnaud)
18+
. Fixed bug GH-19326 (Calling Generator::throw() on a running generator with
19+
a non-Generator delegate crashes). (Arnaud)
1820

1921
- FTP:
2022
. Fix theoretical issues with hrtime() not being available. (nielsdos)

Zend/tests/gh19326.phpt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-19326: Calling Generator::throw() on a running generator with a non-Generator delegate crashes
3+
--FILE--
4+
<?php
5+
6+
class It implements IteratorAggregate {
7+
public function getIterator(): Generator {
8+
yield "";
9+
Fiber::suspend();
10+
}
11+
}
12+
13+
function g() {
14+
yield from new It();
15+
}
16+
17+
$b = g();
18+
$b->rewind();
19+
20+
$fiber = new Fiber(function () use ($b) {
21+
$b->next();
22+
});
23+
24+
$fiber->start();
25+
26+
try {
27+
$b->throw(new Exception('test'));
28+
} catch (Error $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
32+
$fiber->resume();
33+
34+
?>
35+
--EXPECT--
36+
Cannot resume an already running generator

Zend/zend_generators.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,14 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_
492492
return ptr;
493493
}
494494

495-
static void zend_generator_throw_exception(zend_generator *generator, zval *exception)
495+
static zend_result zend_generator_throw_exception(zend_generator *generator, zval *exception)
496496
{
497+
if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
498+
zval_ptr_dtor(exception);
499+
zend_throw_error(NULL, "Cannot resume an already running generator");
500+
return FAILURE;
501+
}
502+
497503
zend_execute_data *original_execute_data = EG(current_execute_data);
498504

499505
/* 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
519525
}
520526

521527
EG(current_execute_data) = original_execute_data;
528+
529+
return SUCCESS;
522530
}
523531

524532
static void zend_generator_add_child(zend_generator *generator, zend_generator *child)
@@ -1026,7 +1034,9 @@ ZEND_METHOD(Generator, throw)
10261034
if (generator->execute_data) {
10271035
zend_generator *root = zend_generator_get_current(generator);
10281036

1029-
zend_generator_throw_exception(root, exception);
1037+
if (zend_generator_throw_exception(root, exception) == FAILURE) {
1038+
return;
1039+
}
10301040

10311041
zend_generator_resume(generator);
10321042

0 commit comments

Comments
 (0)