mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Fix crash during GC of suspended generator delegate (#15275)
This commit is contained in:
parent
4d71580e00
commit
c767fec2d0
8 changed files with 313 additions and 6 deletions
38
Zend/tests/gh15275-001.phpt
Normal file
38
Zend/tests/gh15275-001.phpt
Normal file
|
@ -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
Zend/tests/gh15275-002.phpt
Normal file
57
Zend/tests/gh15275-002.phpt
Normal file
|
@ -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
Zend/tests/gh15275-003.phpt
Normal file
51
Zend/tests/gh15275-003.phpt
Normal file
|
@ -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
Zend/tests/gh15275-004.phpt
Normal file
44
Zend/tests/gh15275-004.phpt
Normal file
|
@ -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
Zend/tests/gh15275-005.phpt
Normal file
51
Zend/tests/gh15275-005.phpt
Normal file
|
@ -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
Zend/tests/gh15275-006.phpt
Normal file
51
Zend/tests/gh15275-006.phpt
Normal file
|
@ -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
|
|
@ -4471,7 +4471,13 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d
|
||||||
}
|
}
|
||||||
|
|
||||||
if (call) {
|
if (call) {
|
||||||
uint32_t op_num = execute_data->opline - op_array->opcodes;
|
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 (suspended_by_yield) {
|
if (suspended_by_yield) {
|
||||||
/* When the execution was suspended by yield, EX(opline) points to
|
/* When the execution was suspended by yield, EX(opline) points to
|
||||||
* next opline to execute. Otherwise, it points to the opline that
|
* next opline to execute. Otherwise, it points to the opline that
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "zend_closures.h"
|
#include "zend_closures.h"
|
||||||
#include "zend_generators_arginfo.h"
|
#include "zend_generators_arginfo.h"
|
||||||
#include "zend_observer.h"
|
#include "zend_observer.h"
|
||||||
|
#include "zend_vm_opcodes.h"
|
||||||
|
|
||||||
ZEND_API zend_class_entry *zend_ce_generator;
|
ZEND_API zend_class_entry *zend_ce_generator;
|
||||||
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
|
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
|
||||||
|
@ -512,6 +513,8 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
|
||||||
* to pretend the exception happened during the YIELD opcode. */
|
* to pretend the exception happened during the YIELD opcode. */
|
||||||
EG(current_execute_data) = generator->execute_data;
|
EG(current_execute_data) = generator->execute_data;
|
||||||
generator->execute_data->opline--;
|
generator->execute_data->opline--;
|
||||||
|
ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_YIELD
|
||||||
|
|| generator->execute_data->opline->opcode == ZEND_YIELD_FROM);
|
||||||
generator->execute_data->prev_execute_data = original_execute_data;
|
generator->execute_data->prev_execute_data = original_execute_data;
|
||||||
|
|
||||||
if (exception) {
|
if (exception) {
|
||||||
|
@ -520,13 +523,14 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce
|
||||||
zend_rethrow_exception(EG(current_execute_data));
|
zend_rethrow_exception(EG(current_execute_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generator->execute_data->opline++;
|
||||||
|
|
||||||
/* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */
|
/* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */
|
||||||
if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
|
if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
|
||||||
zval_ptr_dtor(&generator->values);
|
zval_ptr_dtor(&generator->values);
|
||||||
ZVAL_UNDEF(&generator->values);
|
ZVAL_UNDEF(&generator->values);
|
||||||
}
|
}
|
||||||
|
|
||||||
generator->execute_data->opline++;
|
|
||||||
EG(current_execute_data) = original_execute_data;
|
EG(current_execute_data) = original_execute_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,8 +660,6 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
|
||||||
|
|
||||||
static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
|
static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
|
||||||
{
|
{
|
||||||
--generator->execute_data->opline;
|
|
||||||
|
|
||||||
zval *value;
|
zval *value;
|
||||||
if (Z_TYPE(generator->values) == IS_ARRAY) {
|
if (Z_TYPE(generator->values) == IS_ARRAY) {
|
||||||
HashTable *ht = Z_ARR(generator->values);
|
HashTable *ht = Z_ARR(generator->values);
|
||||||
|
@ -739,14 +741,12 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++generator->execute_data->opline;
|
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
|
|
||||||
failure:
|
failure:
|
||||||
zval_ptr_dtor(&generator->values);
|
zval_ptr_dtor(&generator->values);
|
||||||
ZVAL_UNDEF(&generator->values);
|
ZVAL_UNDEF(&generator->values);
|
||||||
|
|
||||||
++generator->execute_data->opline;
|
|
||||||
return FAILURE;
|
return FAILURE;
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
@ -811,6 +811,15 @@ try_again:
|
||||||
generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
|
generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (UNEXPECTED(EG(exception))) {
|
||||||
|
/* Decrementing opline_before_exception to pretend the exception
|
||||||
|
* happened during the YIELD_FROM opcode. */
|
||||||
|
if (generator->execute_data) {
|
||||||
|
ZEND_ASSERT(generator->execute_data->opline == EG(exception_op));
|
||||||
|
ZEND_ASSERT((EG(opline_before_exception)-1)->opcode == ZEND_YIELD_FROM);
|
||||||
|
EG(opline_before_exception)--;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* If there are no more delegated values, resume the generator
|
/* If there are no more delegated values, resume the generator
|
||||||
* after the "yield from" expression. */
|
* after the "yield from" expression. */
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue