mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Merge branch 'PHP-8.3'
* PHP-8.3: [ci skip] NEWS for GH-15330 [ci skip] NEWS for GH-15330 Do not scan generator frames more than once (#15330)
This commit is contained in:
commit
bf96980104
10 changed files with 357 additions and 35 deletions
38
Zend/tests/gh15330-001.phpt
Normal file
38
Zend/tests/gh15330-001.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 001: Do not scan generator frames more than once
|
||||||
|
--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==
|
33
Zend/tests/gh15330-002.phpt
Normal file
33
Zend/tests/gh15330-002.phpt
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 002: Do not scan generator frames more than once
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function g() {
|
||||||
|
yield 'foo';
|
||||||
|
Fiber::suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
var_dump(yield from g());
|
||||||
|
}
|
||||||
|
|
||||||
|
$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/gh15330-003.phpt
Normal file
57
Zend/tests/gh15330-003.phpt
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 003: Do not scan generator frames more than once
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class It implements \IteratorAggregate
|
||||||
|
{
|
||||||
|
public function getIterator(): \Generator
|
||||||
|
{
|
||||||
|
yield 'foo';
|
||||||
|
Fiber::suspend();
|
||||||
|
var_dump("not executed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Canary {
|
||||||
|
public function __construct(public mixed $value) {}
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function f($canary) {
|
||||||
|
var_dump(yield from new It());
|
||||||
|
}
|
||||||
|
|
||||||
|
$canary = new Canary(null);
|
||||||
|
|
||||||
|
$iterable = f($canary);
|
||||||
|
|
||||||
|
$fiber = new Fiber(function () use ($iterable, $canary) {
|
||||||
|
var_dump($canary, $iterable->current());
|
||||||
|
$iterable->next();
|
||||||
|
var_dump("not executed");
|
||||||
|
});
|
||||||
|
|
||||||
|
$canary->value = $fiber;
|
||||||
|
|
||||||
|
$fiber->start();
|
||||||
|
|
||||||
|
$iterable->current();
|
||||||
|
|
||||||
|
$fiber = $iterable = $canary = null;
|
||||||
|
|
||||||
|
gc_collect_cycles();
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECTF--
|
||||||
|
object(Canary)#%d (1) {
|
||||||
|
["value"]=>
|
||||||
|
object(Fiber)#%d (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string(3) "foo"
|
||||||
|
string(18) "Canary::__destruct"
|
||||||
|
==DONE==
|
52
Zend/tests/gh15330-004.phpt
Normal file
52
Zend/tests/gh15330-004.phpt
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 004: Do not scan generator frames more than once
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Canary {
|
||||||
|
public function __construct(public mixed $value) {}
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() {
|
||||||
|
yield 'foo';
|
||||||
|
Fiber::suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
function f($canary) {
|
||||||
|
var_dump(yield from g());
|
||||||
|
}
|
||||||
|
|
||||||
|
$canary = new Canary(null);
|
||||||
|
|
||||||
|
$iterable = f($canary);
|
||||||
|
|
||||||
|
$fiber = new Fiber(function () use ($iterable, $canary) {
|
||||||
|
var_dump($canary, $iterable->current());
|
||||||
|
$iterable->next();
|
||||||
|
var_dump("not executed");
|
||||||
|
});
|
||||||
|
|
||||||
|
$canary->value = $fiber;
|
||||||
|
|
||||||
|
$fiber->start();
|
||||||
|
|
||||||
|
$iterable->current();
|
||||||
|
|
||||||
|
$fiber = $iterable = $canary = null;
|
||||||
|
|
||||||
|
gc_collect_cycles();
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECTF--
|
||||||
|
object(Canary)#%d (1) {
|
||||||
|
["value"]=>
|
||||||
|
object(Fiber)#%d (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string(3) "foo"
|
||||||
|
string(18) "Canary::__destruct"
|
||||||
|
==DONE==
|
53
Zend/tests/gh15330-005.phpt
Normal file
53
Zend/tests/gh15330-005.phpt
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 005: Do not scan generator frames more than once
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Canary {
|
||||||
|
public function __construct(public mixed $value) {}
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() {
|
||||||
|
yield 'foo';
|
||||||
|
Fiber::suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
function f($canary) {
|
||||||
|
var_dump(yield from g());
|
||||||
|
}
|
||||||
|
|
||||||
|
$canary = new Canary(null);
|
||||||
|
|
||||||
|
$iterable = f($canary);
|
||||||
|
|
||||||
|
$fiber = new Fiber(function () use ($iterable, $canary) {
|
||||||
|
var_dump($canary, $iterable->current());
|
||||||
|
$f = $iterable->next(...);
|
||||||
|
$f();
|
||||||
|
var_dump("not executed");
|
||||||
|
});
|
||||||
|
|
||||||
|
$canary->value = $fiber;
|
||||||
|
|
||||||
|
$fiber->start();
|
||||||
|
|
||||||
|
$iterable->current();
|
||||||
|
|
||||||
|
$fiber = $iterable = $canary = null;
|
||||||
|
|
||||||
|
gc_collect_cycles();
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECTF--
|
||||||
|
object(Canary)#%d (1) {
|
||||||
|
["value"]=>
|
||||||
|
object(Fiber)#%d (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string(3) "foo"
|
||||||
|
string(18) "Canary::__destruct"
|
||||||
|
==DONE==
|
56
Zend/tests/gh15330-006.phpt
Normal file
56
Zend/tests/gh15330-006.phpt
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
--TEST--
|
||||||
|
GH-15330 006: Do not scan generator frames more than once
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Canary {
|
||||||
|
public function __construct(public mixed $value) {}
|
||||||
|
public function __destruct() {
|
||||||
|
var_dump(__METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function h() {
|
||||||
|
yield 'foo';
|
||||||
|
Fiber::suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() {
|
||||||
|
yield from h();
|
||||||
|
}
|
||||||
|
|
||||||
|
function f($canary) {
|
||||||
|
var_dump(yield from g());
|
||||||
|
}
|
||||||
|
|
||||||
|
$canary = new Canary(null);
|
||||||
|
|
||||||
|
$iterable = f($canary);
|
||||||
|
|
||||||
|
$fiber = new Fiber(function () use ($iterable, $canary) {
|
||||||
|
var_dump($canary, $iterable->current());
|
||||||
|
$iterable->next();
|
||||||
|
var_dump("not executed");
|
||||||
|
});
|
||||||
|
|
||||||
|
$canary->value = $fiber;
|
||||||
|
|
||||||
|
$fiber->start();
|
||||||
|
|
||||||
|
$iterable->current();
|
||||||
|
|
||||||
|
$fiber = $iterable = $canary = null;
|
||||||
|
|
||||||
|
gc_collect_cycles();
|
||||||
|
|
||||||
|
?>
|
||||||
|
==DONE==
|
||||||
|
--EXPECTF--
|
||||||
|
object(Canary)#%d (1) {
|
||||||
|
["value"]=>
|
||||||
|
object(Fiber)#%d (0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string(3) "foo"
|
||||||
|
string(18) "Canary::__destruct"
|
||||||
|
==DONE==
|
|
@ -4753,7 +4753,20 @@ ZEND_API ZEND_ATTRIBUTE_DEPRECATED HashTable *zend_unfinished_execution_gc(zend_
|
||||||
|
|
||||||
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)
|
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)
|
||||||
{
|
{
|
||||||
if (!EX(func) || !ZEND_USER_CODE(EX(func)->common.type)) {
|
if (!EX(func)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) {
|
||||||
|
zend_get_gc_buffer_add_obj(gc_buffer, Z_OBJ(execute_data->This));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
|
||||||
|
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ZEND_USER_CODE(EX(func)->common.type)) {
|
||||||
|
ZEND_ASSERT(!(EX_CALL_INFO() & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4774,12 +4787,6 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) {
|
|
||||||
zend_get_gc_buffer_add_obj(gc_buffer, Z_OBJ(execute_data->This));
|
|
||||||
}
|
|
||||||
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
|
|
||||||
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func)));
|
|
||||||
}
|
|
||||||
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
|
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
|
||||||
zval extra_named_params;
|
zval extra_named_params;
|
||||||
ZVAL_ARR(&extra_named_params, EX(extra_named_params));
|
ZVAL_ARR(&extra_named_params, EX(extra_named_params));
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "zend.h"
|
#include "zend.h"
|
||||||
#include "zend_API.h"
|
#include "zend_API.h"
|
||||||
|
#include "zend_gc.h"
|
||||||
#include "zend_ini.h"
|
#include "zend_ini.h"
|
||||||
#include "zend_variables.h"
|
#include "zend_variables.h"
|
||||||
#include "zend_vm.h"
|
#include "zend_vm.h"
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
#include "zend_mmap.h"
|
#include "zend_mmap.h"
|
||||||
#include "zend_compile.h"
|
#include "zend_compile.h"
|
||||||
#include "zend_closures.h"
|
#include "zend_closures.h"
|
||||||
|
#include "zend_generators.h"
|
||||||
|
|
||||||
#include "zend_fibers.h"
|
#include "zend_fibers.h"
|
||||||
#include "zend_fibers_arginfo.h"
|
#include "zend_fibers_arginfo.h"
|
||||||
|
@ -824,7 +826,25 @@ static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *n
|
||||||
HashTable *lastSymTable = NULL;
|
HashTable *lastSymTable = NULL;
|
||||||
zend_execute_data *ex = fiber->execute_data;
|
zend_execute_data *ex = fiber->execute_data;
|
||||||
for (; ex; ex = ex->prev_execute_data) {
|
for (; ex; ex = ex->prev_execute_data) {
|
||||||
HashTable *symTable = zend_unfinished_execution_gc_ex(ex, ex->func && ZEND_USER_CODE(ex->func->type) ? ex->call : NULL, buf, false);
|
HashTable *symTable;
|
||||||
|
if (ZEND_CALL_INFO(ex) & ZEND_CALL_GENERATOR) {
|
||||||
|
/* The generator object is stored in ex->return_value */
|
||||||
|
zend_generator *generator = (zend_generator*)ex->return_value;
|
||||||
|
/* There are two cases to consider:
|
||||||
|
* - If the generator is currently running, the Generator's GC
|
||||||
|
* handler will ignore it because it is not collectable. However,
|
||||||
|
* in this context the generator is suspended in Fiber::suspend()
|
||||||
|
* and may be collectable, so we can inspect it.
|
||||||
|
* - If the generator is not running, the Generator's GC handler
|
||||||
|
* will inspect it. In this case we have to skip the frame.
|
||||||
|
*/
|
||||||
|
if (!(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
symTable = zend_generator_frame_gc(buf, generator);
|
||||||
|
} else {
|
||||||
|
symTable = zend_unfinished_execution_gc_ex(ex, ex->func && ZEND_USER_CODE(ex->func->type) ? ex->call : NULL, buf, false);
|
||||||
|
}
|
||||||
if (symTable) {
|
if (symTable) {
|
||||||
if (lastSymTable) {
|
if (lastSymTable) {
|
||||||
zval *val;
|
zval *val;
|
||||||
|
|
|
@ -395,11 +395,38 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
HashTable *zend_generator_frame_gc(zend_get_gc_buffer *gc_buffer, zend_generator *generator)
|
||||||
|
{
|
||||||
|
zend_execute_data *execute_data = generator->execute_data;
|
||||||
|
zend_execute_data *call = NULL;
|
||||||
|
|
||||||
|
zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
|
||||||
|
zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
|
||||||
|
zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
|
||||||
|
zend_get_gc_buffer_add_zval(gc_buffer, &generator->values);
|
||||||
|
|
||||||
|
if (UNEXPECTED(generator->frozen_call_stack)) {
|
||||||
|
/* The frozen stack is linked in reverse order */
|
||||||
|
call = zend_generator_revert_call_stack(generator->frozen_call_stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashTable *ht = zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, true);
|
||||||
|
|
||||||
|
if (UNEXPECTED(generator->frozen_call_stack)) {
|
||||||
|
zend_generator_revert_call_stack(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generator->node.parent) {
|
||||||
|
zend_get_gc_buffer_add_obj(gc_buffer, &generator->node.parent->std);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ht;
|
||||||
|
}
|
||||||
|
|
||||||
static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *n) /* {{{ */
|
static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *n) /* {{{ */
|
||||||
{
|
{
|
||||||
zend_generator *generator = (zend_generator*)object;
|
zend_generator *generator = (zend_generator*)object;
|
||||||
zend_execute_data *execute_data = generator->execute_data;
|
zend_execute_data *execute_data = generator->execute_data;
|
||||||
zend_execute_data *call = NULL;
|
|
||||||
|
|
||||||
if (!execute_data) {
|
if (!execute_data) {
|
||||||
if (UNEXPECTED(generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
|
if (UNEXPECTED(generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
|
||||||
|
@ -428,34 +455,11 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
|
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
|
||||||
zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
|
HashTable *ht = zend_generator_frame_gc(gc_buffer, generator);
|
||||||
zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
|
|
||||||
zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
|
|
||||||
zend_get_gc_buffer_add_zval(gc_buffer, &generator->values);
|
|
||||||
|
|
||||||
if (UNEXPECTED(generator->frozen_call_stack)) {
|
|
||||||
/* The frozen stack is linked in reverse order */
|
|
||||||
call = zend_generator_revert_call_stack(generator->frozen_call_stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, true);
|
|
||||||
|
|
||||||
if (UNEXPECTED(generator->frozen_call_stack)) {
|
|
||||||
zend_generator_revert_call_stack(call);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (generator->node.parent) {
|
|
||||||
zend_get_gc_buffer_add_obj(gc_buffer, &generator->node.parent->std);
|
|
||||||
}
|
|
||||||
|
|
||||||
zend_get_gc_buffer_use(gc_buffer, table, n);
|
zend_get_gc_buffer_use(gc_buffer, table, n);
|
||||||
if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) {
|
|
||||||
return execute_data->symbol_table;
|
return ht;
|
||||||
} else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,8 @@ static zend_always_inline zend_generator *zend_generator_get_current(zend_genera
|
||||||
return zend_generator_update_current(generator);
|
return zend_generator_update_current(generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HashTable *zend_generator_frame_gc(zend_get_gc_buffer *gc_buffer, zend_generator *generator);
|
||||||
|
|
||||||
END_EXTERN_C()
|
END_EXTERN_C()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue