Skip to content

Change YIELD/YIELD_FROM to do not increment opline #15328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ PHP NEWS
. Added missing cstddef include for C++ builds. (cmb)
. Fixed bug GH-15108 (Segfault when destroying generator during shutdown).
(Arnaud)
. Fixed bug GH-15275 (Crash during GC of suspended generator delegate).
(Arnaud)

- BCMath:
. Adjust bcround()'s $mode parameter to only accept the RoundingMode
Expand Down
38 changes: 38 additions & 0 deletions Zend/tests/gh15275-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
GH-15275 001: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
Fiber::suspend();
var_dump("not executed");
}
}

function f() {
var_dump(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();

gc_collect_cycles();

?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==
57 changes: 57 additions & 0 deletions Zend/tests/gh15275-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
GH-15275 002: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
try {
Fiber::suspend();
} finally {
var_dump(__METHOD__);
}
var_dump("not executed");
}
}

function f() {
try {
var_dump(new stdClass, yield from new It());
} finally {
var_dump(__FUNCTION__);
}
}

function g() {
try {
var_dump(new stdClass, yield from f());
} finally {
var_dump(__FUNCTION__);
}
}

$gen = g();

$fiber = new Fiber(function () use ($gen) {
var_dump($gen->current());
$gen->next();
var_dump("not executed");
});

$ref = $fiber;

$fiber->start();

gc_collect_cycles();

?>
==DONE==
--EXPECT--
string(3) "foo"
==DONE==
string(15) "It::getIterator"
string(1) "f"
string(1) "g"
51 changes: 51 additions & 0 deletions Zend/tests/gh15275-003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
GH-15275 003: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
throw new \Exception();
var_dump("not executed");
}
}

function f() {
try {
var_dump(new stdClass, yield from new It());
} finally {
var_dump(__FUNCTION__);
}
}

function g() {
try {
var_dump(new stdClass, yield from f());
} finally {
var_dump(__FUNCTION__);
}
}

$gen = g();

var_dump($gen->current());
$gen->next();

?>
==DONE==
--EXPECTF--
string(3) "foo"
string(1) "f"
string(1) "g"

Fatal error: Uncaught Exception in %s:8
Stack trace:
#0 %s(15): It->getIterator()
#1 %s(23): f()
#2 [internal function]: g()
#3 %s(32): Generator->next()
#4 {main}
thrown in %s on line 8
44 changes: 44 additions & 0 deletions Zend/tests/gh15275-004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
GH-15275 004: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
echo "baz\n";
throw new \Exception();
}

public function __destruct()
{
gc_collect_cycles();
}
}

function f() {
var_dump(new stdClass, yield from new It());
}

$gen = f();

var_dump($gen->current());
$gen->next();

gc_collect_cycles();

?>
==DONE==
--EXPECTF--
string(3) "foo"
baz

Fatal error: Uncaught Exception in %s:9
Stack trace:
#0 %s(19): It->getIterator()
#1 [internal function]: f()
#2 %s(25): Generator->next()
#3 {main}
thrown in %s on line 9
51 changes: 51 additions & 0 deletions Zend/tests/gh15275-005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
GH-15275 005: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
throw new \Exception();
var_dump("not executed");
}
}

function f() {
try {
var_dump(new stdClass, yield from new It());
} finally {
var_dump(__FUNCTION__);
}
}

function g() {
try {
var_dump(new stdClass, yield from f());
} finally {
var_dump(__FUNCTION__);
}
}

$gen = g();

var_dump($gen->current());
$gen->next();

?>
==DONE==
--EXPECTF--
string(3) "foo"
string(1) "f"
string(1) "g"

Fatal error: Uncaught Exception in %s:8
Stack trace:
#0 %s(15): It->getIterator()
#1 %s(23): f()
#2 [internal function]: g()
#3 %s(32): Generator->next()
#4 {main}
thrown in %s on line 8
51 changes: 51 additions & 0 deletions Zend/tests/gh15275-006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
GH-15275 006: Crash during GC of suspended generator delegate
--FILE--
<?php

class It implements \IteratorAggregate
{
public function getIterator(): \Generator
{
yield 'foo';
echo "baz\n";
throw new \Exception();
}

public function __destruct()
{
throw new \Exception();
}
}

function f() {
var_dump(new stdClass, yield from new It());
}

$gen = f();

var_dump($gen->current());
$gen->next();

gc_collect_cycles();

?>
==DONE==
--EXPECTF--
string(3) "foo"
baz

Fatal error: Uncaught Exception in %s:9
Stack trace:
#0 %s(19): It->getIterator()
#1 [internal function]: f()
#2 %s(25): Generator->next()
#3 {main}

Next Exception in %s:14
Stack trace:
#0 %s(19): It->__destruct()
#1 [internal function]: f()
#2 %s(25): Generator->next()
#3 {main}
thrown in %s on line 14
33 changes: 10 additions & 23 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -4711,19 +4711,7 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data,

ZEND_API ZEND_ATTRIBUTE_DEPRECATED HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer)
{
bool suspended_by_yield = false;

if (Z_TYPE_INFO(EX(This)) & ZEND_CALL_GENERATOR) {
ZEND_ASSERT(EX(return_value));

/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator*) EX(return_value);
ZEND_ASSERT(execute_data == generator->execute_data);

suspended_by_yield = !(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING);
}

return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, suspended_by_yield);
return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, false);
}

ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer, bool suspended_by_yield)
Expand Down Expand Up @@ -4761,21 +4749,20 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d
zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params);
}

uint32_t op_num;
if (UNEXPECTED(execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION)) {
op_num = EG(opline_before_exception) - op_array->opcodes;
} else {
op_num = execute_data->opline - op_array->opcodes;
}
ZEND_ASSERT(op_num < op_array->last);

if (call) {
uint32_t op_num = execute_data->opline - op_array->opcodes;
if (suspended_by_yield) {
/* When the execution was suspended by yield, EX(opline) points to
* next opline to execute. Otherwise, it points to the opline that
* suspended execution. */
op_num--;
ZEND_ASSERT(EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD
|| EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD_FROM);
}
zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer);
}

if (execute_data->opline != op_array->opcodes) {
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;
uint32_t i;
for (i = 0; i < op_array->last_live_range; i++) {
const zend_live_range *range = &op_array->live_range[i];
if (range->start > op_num) {
Expand Down
Loading
Loading