mirror of
https://github.com/php/php-src.git
synced 2024-09-21 18:07:23 +00:00
Merge branch 'PHP-8.0' into PHP-8.1
* PHP-8.0: [ci skip] NEWS Fix generator memory leaks when interrupted during argument evaluation (#9756)
This commit is contained in:
commit
4011657719
33
Zend/tests/generators/gh9750-001.phpt
Normal file
33
Zend/tests/generators/gh9750-001.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 001 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f($gen, yield);
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-002.phpt
Normal file
33
Zend/tests/generators/gh9750-002.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 002 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f($gen, f(yield));
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-003.phpt
Normal file
33
Zend/tests/generators/gh9750-003.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 003 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f(f($gen, yield));
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-004.phpt
Normal file
33
Zend/tests/generators/gh9750-004.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 004 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f(new stdClass, $gen, yield);
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
34
Zend/tests/generators/gh9750-005.phpt
Normal file
34
Zend/tests/generators/gh9750-005.phpt
Normal file
@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
Bug GH-9750 002 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f(...$x) {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f(b: $gen, c: yield);
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
$c = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-006.phpt
Normal file
33
Zend/tests/generators/gh9750-006.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 006 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f(...$x) {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f(a: $gen, b: f(c: yield));
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-007.phpt
Normal file
33
Zend/tests/generators/gh9750-007.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 007 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f(...$x) {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
f(f(a: $gen, b: yield));
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
40
Zend/tests/generators/gh9750-008.phpt
Normal file
40
Zend/tests/generators/gh9750-008.phpt
Normal file
@ -0,0 +1,40 @@
|
||||
--TEST--
|
||||
Bug GH-9750 008 (Generator memory leak when interrupted during argument evaluation)
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (version_compare(PHP_VERSION, '8.1', '<')) { die('skip Broken on PHP < 8.1'); }
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class D {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
new D($gen, yield);
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
37
Zend/tests/generators/gh9750-009.phpt
Normal file
37
Zend/tests/generators/gh9750-009.phpt
Normal file
@ -0,0 +1,37 @@
|
||||
--TEST--
|
||||
Bug GH-9750 009 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
public function __construct(public mixed $x) {
|
||||
}
|
||||
public function __invoke() {
|
||||
}
|
||||
public function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function () use (&$gen) {
|
||||
(new C($gen))(yield);
|
||||
};
|
||||
|
||||
$gen = $gen();
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
33
Zend/tests/generators/gh9750-010.phpt
Normal file
33
Zend/tests/generators/gh9750-010.phpt
Normal file
@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
Bug GH-9750 010 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
$gen->valid(yield);
|
||||
};
|
||||
|
||||
$gen = $gen(new C());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
38
Zend/tests/generators/gh9750-011.phpt
Normal file
38
Zend/tests/generators/gh9750-011.phpt
Normal file
@ -0,0 +1,38 @@
|
||||
--TEST--
|
||||
Bug GH-9750 011 (Generator memory leak when interrupted during argument evaluation)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function f() {
|
||||
}
|
||||
|
||||
class C {
|
||||
function getClosure() {
|
||||
return function () {
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
function __destruct() {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$gen = function ($c) use (&$gen) {
|
||||
$c($gen, yield);
|
||||
};
|
||||
|
||||
$gen = $gen((new C())->getClosure());
|
||||
|
||||
foreach ($gen as $value) {
|
||||
break;
|
||||
}
|
||||
|
||||
$gen = null;
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
C::__destruct
|
||||
==DONE==
|
@ -4113,6 +4113,136 @@ static zend_always_inline zend_generator *zend_get_running_generator(EXECUTE_DAT
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_execute_data *call, uint32_t op_num, zend_get_gc_buffer *buf) /* {{{ */
|
||||
{
|
||||
zend_op *opline = EX(func)->op_array.opcodes + op_num;
|
||||
int level;
|
||||
int do_exit;
|
||||
uint32_t num_args;
|
||||
|
||||
if (UNEXPECTED(opline->opcode == ZEND_INIT_FCALL ||
|
||||
opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
|
||||
opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
|
||||
opline->opcode == ZEND_INIT_DYNAMIC_CALL ||
|
||||
opline->opcode == ZEND_INIT_USER_CALL ||
|
||||
opline->opcode == ZEND_INIT_METHOD_CALL ||
|
||||
opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
|
||||
opline->opcode == ZEND_NEW)) {
|
||||
ZEND_ASSERT(op_num);
|
||||
opline--;
|
||||
}
|
||||
|
||||
do {
|
||||
/* find the number of actually passed arguments */
|
||||
level = 0;
|
||||
do_exit = 0;
|
||||
num_args = ZEND_CALL_NUM_ARGS(call);
|
||||
do {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_DO_FCALL:
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
level++;
|
||||
break;
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_INIT_FCALL_BY_NAME:
|
||||
case ZEND_INIT_NS_FCALL_BY_NAME:
|
||||
case ZEND_INIT_DYNAMIC_CALL:
|
||||
case ZEND_INIT_USER_CALL:
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_NEW:
|
||||
if (level == 0) {
|
||||
num_args = 0;
|
||||
do_exit = 1;
|
||||
}
|
||||
level--;
|
||||
break;
|
||||
case ZEND_SEND_VAL:
|
||||
case ZEND_SEND_VAL_EX:
|
||||
case ZEND_SEND_VAR:
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_SEND_FUNC_ARG:
|
||||
case ZEND_SEND_REF:
|
||||
case ZEND_SEND_VAR_NO_REF:
|
||||
case ZEND_SEND_VAR_NO_REF_EX:
|
||||
case ZEND_SEND_USER:
|
||||
if (level == 0) {
|
||||
/* For named args, the number of arguments is up to date. */
|
||||
if (opline->op2_type != IS_CONST) {
|
||||
num_args = opline->op2.num;
|
||||
}
|
||||
do_exit = 1;
|
||||
}
|
||||
break;
|
||||
case ZEND_SEND_ARRAY:
|
||||
case ZEND_SEND_UNPACK:
|
||||
case ZEND_CHECK_UNDEF_ARGS:
|
||||
if (level == 0) {
|
||||
do_exit = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!do_exit) {
|
||||
opline--;
|
||||
}
|
||||
} while (!do_exit);
|
||||
if (call->prev_execute_data) {
|
||||
/* skip current call region */
|
||||
level = 0;
|
||||
do_exit = 0;
|
||||
do {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_DO_FCALL:
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
level++;
|
||||
break;
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_INIT_FCALL_BY_NAME:
|
||||
case ZEND_INIT_NS_FCALL_BY_NAME:
|
||||
case ZEND_INIT_DYNAMIC_CALL:
|
||||
case ZEND_INIT_USER_CALL:
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_NEW:
|
||||
if (level == 0) {
|
||||
do_exit = 1;
|
||||
}
|
||||
level--;
|
||||
break;
|
||||
}
|
||||
opline--;
|
||||
} while (!do_exit);
|
||||
}
|
||||
|
||||
if (EXPECTED(num_args > 0)) {
|
||||
zval *p = ZEND_CALL_ARG(call, 1);
|
||||
do {
|
||||
zend_get_gc_buffer_add_zval(buf, p);
|
||||
p++;
|
||||
} while (--num_args);
|
||||
}
|
||||
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
|
||||
zend_get_gc_buffer_add_obj(buf, Z_OBJ(call->This));
|
||||
}
|
||||
if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
|
||||
zval *val;
|
||||
ZEND_HASH_FOREACH_VAL(call->extra_named_params, val) {
|
||||
zend_get_gc_buffer_add_zval(buf, val);
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
if (call->func->common.fn_flags & ZEND_ACC_CLOSURE) {
|
||||
zend_get_gc_buffer_add_obj(buf, ZEND_CLOSURE_OBJECT(call->func));
|
||||
}
|
||||
|
||||
call = call->prev_execute_data;
|
||||
} while (call);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t op_num) /* {{{ */
|
||||
{
|
||||
if (UNEXPECTED(EX(call))) {
|
||||
|
@ -376,6 +376,7 @@ ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode
|
||||
|
||||
ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table);
|
||||
ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *execute_data);
|
||||
ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_execute_data *call, uint32_t op_num, zend_get_gc_buffer *buf);
|
||||
ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num);
|
||||
|
||||
zval * ZEND_FASTCALL zend_handle_named_arg(
|
||||
|
@ -95,6 +95,20 @@ ZEND_API zend_execute_data* zend_generator_freeze_call_stack(zend_execute_data *
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static zend_execute_data* zend_generator_revert_call_stack(zend_execute_data *call)
|
||||
{
|
||||
zend_execute_data *prev = NULL;
|
||||
|
||||
do {
|
||||
zend_execute_data *next = call->prev_execute_data;
|
||||
call->prev_execute_data = prev;
|
||||
prev = call;
|
||||
call = next;
|
||||
} while (call);
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
static void zend_generator_cleanup_unfinished_execution(
|
||||
zend_generator *generator, zend_execute_data *execute_data, uint32_t catch_op_num) /* {{{ */
|
||||
{
|
||||
@ -373,6 +387,15 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
|
||||
zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params);
|
||||
}
|
||||
|
||||
if (UNEXPECTED(generator->frozen_call_stack)) {
|
||||
/* The frozen stack is linked in reverse order */
|
||||
zend_execute_data *call = zend_generator_revert_call_stack(generator->frozen_call_stack);
|
||||
/* -1 required because we want the last run opcode, not the next to-be-run one. */
|
||||
uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
|
||||
zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer);
|
||||
zend_generator_revert_call_stack(call);
|
||||
}
|
||||
|
||||
if (execute_data->opline != op_array->opcodes) {
|
||||
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;
|
||||
for (i = 0; i < op_array->last_live_range; i++) {
|
||||
@ -607,7 +630,7 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
|
||||
static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
|
||||
{
|
||||
--generator->execute_data->opline;
|
||||
|
||||
|
||||
zval *value;
|
||||
if (Z_TYPE(generator->values) == IS_ARRAY) {
|
||||
HashTable *ht = Z_ARR(generator->values);
|
||||
@ -670,7 +693,7 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener
|
||||
ZVAL_LONG(&generator->key, iter->index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
++generator->execute_data->opline;
|
||||
return SUCCESS;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user