Skip to content

Commit ba9f65b

Browse files
committed
Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2: [ci skip] NEWS for GH-15275 Fix crash during GC of suspended generator delegate (#15275)
2 parents 04adeea + 39bacaf commit ba9f65b

8 files changed

+313
-6
lines changed

Zend/tests/gh15275-001.phpt

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
GH-15275 001: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
var_dump(yield from new It());
18+
}
19+
20+
$iterable = f();
21+
22+
$fiber = new Fiber(function () use ($iterable) {
23+
var_dump($iterable->current());
24+
$iterable->next();
25+
var_dump("not executed");
26+
});
27+
28+
$ref = $fiber;
29+
30+
$fiber->start();
31+
32+
gc_collect_cycles();
33+
34+
?>
35+
==DONE==
36+
--EXPECT--
37+
string(3) "foo"
38+
==DONE==

Zend/tests/gh15275-002.phpt

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
GH-15275 002: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
try {
12+
Fiber::suspend();
13+
} finally {
14+
var_dump(__METHOD__);
15+
}
16+
var_dump("not executed");
17+
}
18+
}
19+
20+
function f() {
21+
try {
22+
var_dump(new stdClass, yield from new It());
23+
} finally {
24+
var_dump(__FUNCTION__);
25+
}
26+
}
27+
28+
function g() {
29+
try {
30+
var_dump(new stdClass, yield from f());
31+
} finally {
32+
var_dump(__FUNCTION__);
33+
}
34+
}
35+
36+
$gen = g();
37+
38+
$fiber = new Fiber(function () use ($gen) {
39+
var_dump($gen->current());
40+
$gen->next();
41+
var_dump("not executed");
42+
});
43+
44+
$ref = $fiber;
45+
46+
$fiber->start();
47+
48+
gc_collect_cycles();
49+
50+
?>
51+
==DONE==
52+
--EXPECT--
53+
string(3) "foo"
54+
==DONE==
55+
string(15) "It::getIterator"
56+
string(1) "f"
57+
string(1) "g"

Zend/tests/gh15275-003.phpt

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
GH-15275 003: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
throw new \Exception();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
try {
18+
var_dump(new stdClass, yield from new It());
19+
} finally {
20+
var_dump(__FUNCTION__);
21+
}
22+
}
23+
24+
function g() {
25+
try {
26+
var_dump(new stdClass, yield from f());
27+
} finally {
28+
var_dump(__FUNCTION__);
29+
}
30+
}
31+
32+
$gen = g();
33+
34+
var_dump($gen->current());
35+
$gen->next();
36+
37+
?>
38+
==DONE==
39+
--EXPECTF--
40+
string(3) "foo"
41+
string(1) "f"
42+
string(1) "g"
43+
44+
Fatal error: Uncaught Exception in %s:8
45+
Stack trace:
46+
#0 %s(15): It->getIterator()
47+
#1 %s(23): f()
48+
#2 [internal function]: g()
49+
#3 %s(32): Generator->next()
50+
#4 {main}
51+
thrown in %s on line 8

Zend/tests/gh15275-004.phpt

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
GH-15275 004: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
echo "baz\n";
12+
throw new \Exception();
13+
}
14+
15+
public function __destruct()
16+
{
17+
gc_collect_cycles();
18+
}
19+
}
20+
21+
function f() {
22+
var_dump(new stdClass, yield from new It());
23+
}
24+
25+
$gen = f();
26+
27+
var_dump($gen->current());
28+
$gen->next();
29+
30+
gc_collect_cycles();
31+
32+
?>
33+
==DONE==
34+
--EXPECTF--
35+
string(3) "foo"
36+
baz
37+
38+
Fatal error: Uncaught Exception in %s:9
39+
Stack trace:
40+
#0 %s(19): It->getIterator()
41+
#1 [internal function]: f()
42+
#2 %s(25): Generator->next()
43+
#3 {main}
44+
thrown in %s on line 9

Zend/tests/gh15275-005.phpt

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
GH-15275 005: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
throw new \Exception();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
try {
18+
var_dump(new stdClass, yield from new It());
19+
} finally {
20+
var_dump(__FUNCTION__);
21+
}
22+
}
23+
24+
function g() {
25+
try {
26+
var_dump(new stdClass, yield from f());
27+
} finally {
28+
var_dump(__FUNCTION__);
29+
}
30+
}
31+
32+
$gen = g();
33+
34+
var_dump($gen->current());
35+
$gen->next();
36+
37+
?>
38+
==DONE==
39+
--EXPECTF--
40+
string(3) "foo"
41+
string(1) "f"
42+
string(1) "g"
43+
44+
Fatal error: Uncaught Exception in %s:8
45+
Stack trace:
46+
#0 %s(15): It->getIterator()
47+
#1 %s(23): f()
48+
#2 [internal function]: g()
49+
#3 %s(32): Generator->next()
50+
#4 {main}
51+
thrown in %s on line 8

Zend/tests/gh15275-006.phpt

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
GH-15275 006: Crash during GC of suspended generator delegate
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
echo "baz\n";
12+
throw new \Exception();
13+
}
14+
15+
public function __destruct()
16+
{
17+
throw new \Exception();
18+
}
19+
}
20+
21+
function f() {
22+
var_dump(new stdClass, yield from new It());
23+
}
24+
25+
$gen = f();
26+
27+
var_dump($gen->current());
28+
$gen->next();
29+
30+
gc_collect_cycles();
31+
32+
?>
33+
==DONE==
34+
--EXPECTF--
35+
string(3) "foo"
36+
baz
37+
38+
Fatal error: Uncaught Exception in %s:9
39+
Stack trace:
40+
#0 %s(19): It->getIterator()
41+
#1 [internal function]: f()
42+
#2 %s(25): Generator->next()
43+
#3 {main}
44+
45+
Next Exception in %s:14
46+
Stack trace:
47+
#0 %s(19): It->__destruct()
48+
#1 [internal function]: f()
49+
#2 %s(25): Generator->next()
50+
#3 {main}
51+
thrown in %s on line 14

Zend/zend_execute.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -4598,7 +4598,13 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d
45984598
}
45994599

46004600
if (call) {
4601-
uint32_t op_num = execute_data->opline - op_array->opcodes;
4601+
uint32_t op_num;
4602+
if (UNEXPECTED(execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION)) {
4603+
op_num = EG(opline_before_exception) - op_array->opcodes;
4604+
} else {
4605+
op_num = execute_data->opline - op_array->opcodes;
4606+
}
4607+
ZEND_ASSERT(op_num < op_array->last);
46024608
if (suspended_by_yield) {
46034609
/* When the execution was suspended by yield, EX(opline) points to
46044610
* next opline to execute. Otherwise, it points to the opline that

Zend/zend_generators.c

+14-5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "zend_closures.h"
2727
#include "zend_generators_arginfo.h"
2828
#include "zend_observer.h"
29+
#include "zend_vm_opcodes.h"
2930

3031
ZEND_API zend_class_entry *zend_ce_generator;
3132
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
@@ -508,6 +509,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
508509
* to pretend the exception happened during the YIELD opcode. */
509510
EG(current_execute_data) = generator->execute_data;
510511
generator->execute_data->opline--;
512+
ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_YIELD
513+
|| generator->execute_data->opline->opcode == ZEND_YIELD_FROM);
511514
generator->execute_data->prev_execute_data = original_execute_data;
512515

513516
if (exception) {
@@ -516,13 +519,14 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
516519
zend_rethrow_exception(EG(current_execute_data));
517520
}
518521

522+
generator->execute_data->opline++;
523+
519524
/* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */
520525
if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
521526
zval_ptr_dtor(&generator->values);
522527
ZVAL_UNDEF(&generator->values);
523528
}
524529

525-
generator->execute_data->opline++;
526530
EG(current_execute_data) = original_execute_data;
527531
}
528532

@@ -652,8 +656,6 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
652656

653657
static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
654658
{
655-
--generator->execute_data->opline;
656-
657659
zval *value;
658660
if (Z_TYPE(generator->values) == IS_ARRAY) {
659661
HashTable *ht = Z_ARR(generator->values);
@@ -735,14 +737,12 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener
735737
}
736738
}
737739

738-
++generator->execute_data->opline;
739740
return SUCCESS;
740741

741742
failure:
742743
zval_ptr_dtor(&generator->values);
743744
ZVAL_UNDEF(&generator->values);
744745

745-
++generator->execute_data->opline;
746746
return FAILURE;
747747
}
748748
/* }}} */
@@ -807,6 +807,15 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
807807
generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
808808
return;
809809
}
810+
if (UNEXPECTED(EG(exception))) {
811+
/* Decrementing opline_before_exception to pretend the exception
812+
* happened during the YIELD_FROM opcode. */
813+
if (generator->execute_data) {
814+
ZEND_ASSERT(generator->execute_data->opline == EG(exception_op));
815+
ZEND_ASSERT((EG(opline_before_exception)-1)->opcode == ZEND_YIELD_FROM);
816+
EG(opline_before_exception)--;
817+
}
818+
}
810819
/* If there are no more delegated values, resume the generator
811820
* after the "yield from" expression. */
812821
}

0 commit comments

Comments
 (0)