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:
Arnaud Le Blanc 2022-11-04 15:59:14 +01:00
commit 4011657719
14 changed files with 536 additions and 2 deletions

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View 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==

View File

@ -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))) {

View File

@ -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(

View File

@ -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;