Skip to content

Commit d721dcc

Browse files
arnaud-lbbwoebi
andcommitted
Fix colletion of unfinished function call in fibers
Fixes GH-10496. Co-authored-by: Bob Weinand <[email protected]>
1 parent 85d9278 commit d721dcc

File tree

6 files changed

+66
-6
lines changed

6 files changed

+66
-6
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ PHP NEWS
2424

2525
- Fiber:
2626
. Fixed assembly on alpine x86. (nielsdos)
27+
. Fixed bug GH-10496 (segfault when garbage collector is invoked inside of
28+
fiber). (Bob, Arnaud)
2729

2830
- FPM:
2931
. Fixed bug GH-10315 (FPM unknown child alert not valid). (Jakub Zelenka)

Zend/tests/fibers/gh10496.phpt

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Bug GH-10496 (Segfault when garbage collector is invoked inside of fiber)
3+
--FILE--
4+
<?php
5+
6+
function x(&$ref) {
7+
$ref = new class() {
8+
function __destruct() {
9+
print "Dtor x()\n";
10+
}
11+
};
12+
}
13+
function suspend($x) {
14+
Fiber::suspend();
15+
}
16+
$f = new Fiber(function() use (&$f) {
17+
try {
18+
x($var);
19+
\ord(suspend(1));
20+
} finally {
21+
print "Cleaned\n";
22+
}
23+
});
24+
$f->start();
25+
unset($f);
26+
gc_collect_cycles();
27+
print "Collected\n";
28+
29+
?>
30+
--EXPECT--
31+
Cleaned
32+
Dtor x()
33+
Collected

Zend/zend_execute.c

+27-3
Original file line numberDiff line numberDiff line change
@@ -4459,7 +4459,24 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data,
44594459
cleanup_live_vars(execute_data, op_num, catch_op_num);
44604460
}
44614461

4462-
ZEND_API HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer)
4462+
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)
4463+
{
4464+
bool suspended_by_yield = false;
4465+
4466+
if (Z_TYPE_INFO(EX(This)) & ZEND_CALL_GENERATOR) {
4467+
ZEND_ASSERT(EX(return_value));
4468+
4469+
/* The generator object is stored in EX(return_value) */
4470+
zend_generator *generator = (zend_generator*) EX(return_value);
4471+
ZEND_ASSERT(execute_data == generator->execute_data);
4472+
4473+
suspended_by_yield = !(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING);
4474+
}
4475+
4476+
return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, suspended_by_yield);
4477+
}
4478+
4479+
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)
44634480
{
44644481
if (!EX(func) || !ZEND_USER_CODE(EX(func)->common.type)) {
44654482
return NULL;
@@ -4495,8 +4512,15 @@ ZEND_API HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data
44954512
}
44964513

44974514
if (call) {
4498-
/* -1 required because we want the last run opcode, not the next to-be-run one. */
4499-
uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
4515+
uint32_t op_num = execute_data->opline - op_array->opcodes;
4516+
if (suspended_by_yield) {
4517+
/* When the execution was suspended by yield, EX(opline) points to
4518+
* next opline to execute. Otherwise, it points to the opline that
4519+
* suspended execution. */
4520+
op_num--;
4521+
ZEND_ASSERT(EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD
4522+
|| EX(func)->op_array.opcodes[op_num].opcode == ZEND_YIELD_FROM);
4523+
}
45004524
zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer);
45014525
}
45024526

Zend/zend_execute.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,8 @@ ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table);
410410
ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *execute_data);
411411
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);
412412
ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num);
413-
ZEND_API HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer);
413+
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);
414+
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);
414415

415416
zval * ZEND_FASTCALL zend_handle_named_arg(
416417
zend_execute_data **call_ptr, zend_string *arg_name,

Zend/zend_fibers.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *n
652652
HashTable *lastSymTable = NULL;
653653
zend_execute_data *ex = fiber->execute_data;
654654
for (; ex; ex = ex->prev_execute_data) {
655-
HashTable *symTable = zend_unfinished_execution_gc(ex, ex->call, buf);
655+
HashTable *symTable = zend_unfinished_execution_gc_ex(ex, ex->call, buf, false);
656656
if (symTable) {
657657
if (lastSymTable) {
658658
zval *val;

Zend/zend_generators.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
372372
call = zend_generator_revert_call_stack(generator->frozen_call_stack);
373373
}
374374

375-
zend_unfinished_execution_gc(execute_data, call, gc_buffer);
375+
zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, true);
376376

377377
if (UNEXPECTED(generator->frozen_call_stack)) {
378378
zend_generator_revert_call_stack(call);

0 commit comments

Comments
 (0)