Skip to content

Commit ddf41d3

Browse files
committed
Fix generator memory leak
Make sure HANDLE_EXCEPTION and generator unwinds stay in sync in the future by extracting a common function.
1 parent 7adc0ae commit ddf41d3

File tree

5 files changed

+156
-321
lines changed

5 files changed

+156
-321
lines changed

Zend/zend_execute.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,156 @@ static zend_always_inline zend_generator *zend_get_running_generator(zend_execut
23782378
}
23792379
/* }}} */
23802380

2381+
static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) /* {{{ */
2382+
{
2383+
int i;
2384+
if (UNEXPECTED(EX(call))) {
2385+
zend_execute_data *call = EX(call);
2386+
zend_op *opline = EX(func)->op_array.opcodes + op_num;
2387+
int level;
2388+
int do_exit;
2389+
2390+
do {
2391+
/* If the exception was thrown during a function call there might be
2392+
* arguments pushed to the stack that have to be dtor'ed. */
2393+
2394+
/* find the number of actually passed arguments */
2395+
level = 0;
2396+
do_exit = 0;
2397+
do {
2398+
switch (opline->opcode) {
2399+
case ZEND_DO_FCALL:
2400+
case ZEND_DO_ICALL:
2401+
case ZEND_DO_UCALL:
2402+
case ZEND_DO_FCALL_BY_NAME:
2403+
level++;
2404+
break;
2405+
case ZEND_INIT_FCALL:
2406+
case ZEND_INIT_FCALL_BY_NAME:
2407+
case ZEND_INIT_NS_FCALL_BY_NAME:
2408+
case ZEND_INIT_DYNAMIC_CALL:
2409+
case ZEND_INIT_USER_CALL:
2410+
case ZEND_INIT_METHOD_CALL:
2411+
case ZEND_INIT_STATIC_METHOD_CALL:
2412+
case ZEND_NEW:
2413+
if (level == 0) {
2414+
ZEND_CALL_NUM_ARGS(call) = 0;
2415+
do_exit = 1;
2416+
}
2417+
level--;
2418+
break;
2419+
case ZEND_SEND_VAL:
2420+
case ZEND_SEND_VAL_EX:
2421+
case ZEND_SEND_VAR:
2422+
case ZEND_SEND_VAR_EX:
2423+
case ZEND_SEND_REF:
2424+
case ZEND_SEND_VAR_NO_REF:
2425+
case ZEND_SEND_USER:
2426+
if (level == 0) {
2427+
ZEND_CALL_NUM_ARGS(call) = opline->op2.num;
2428+
do_exit = 1;
2429+
}
2430+
break;
2431+
case ZEND_SEND_ARRAY:
2432+
case ZEND_SEND_UNPACK:
2433+
if (level == 0) {
2434+
do_exit = 1;
2435+
}
2436+
break;
2437+
}
2438+
if (!do_exit) {
2439+
opline--;
2440+
}
2441+
} while (!do_exit);
2442+
if (call->prev_execute_data) {
2443+
/* skip current call region */
2444+
level = 0;
2445+
do_exit = 0;
2446+
do {
2447+
switch (opline->opcode) {
2448+
case ZEND_DO_FCALL:
2449+
case ZEND_DO_ICALL:
2450+
case ZEND_DO_UCALL:
2451+
case ZEND_DO_FCALL_BY_NAME:
2452+
level++;
2453+
break;
2454+
case ZEND_INIT_FCALL:
2455+
case ZEND_INIT_FCALL_BY_NAME:
2456+
case ZEND_INIT_NS_FCALL_BY_NAME:
2457+
case ZEND_INIT_DYNAMIC_CALL:
2458+
case ZEND_INIT_USER_CALL:
2459+
case ZEND_INIT_METHOD_CALL:
2460+
case ZEND_INIT_STATIC_METHOD_CALL:
2461+
case ZEND_NEW:
2462+
if (level == 0) {
2463+
do_exit = 1;
2464+
}
2465+
level--;
2466+
break;
2467+
}
2468+
opline--;
2469+
} while (!do_exit);
2470+
}
2471+
2472+
zend_vm_stack_free_args(EX(call));
2473+
2474+
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
2475+
if (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR) {
2476+
if (!(ZEND_CALL_INFO(call) & ZEND_CALL_CTOR_RESULT_UNUSED)) {
2477+
GC_REFCOUNT(Z_OBJ(call->This))--;
2478+
}
2479+
if (GC_REFCOUNT(Z_OBJ(call->This)) == 1) {
2480+
zend_object_store_ctor_failed(Z_OBJ(call->This));
2481+
}
2482+
}
2483+
OBJ_RELEASE(Z_OBJ(call->This));
2484+
}
2485+
if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
2486+
zend_string_release(call->func->common.function_name);
2487+
zend_free_trampoline(call->func);
2488+
}
2489+
2490+
EX(call) = call->prev_execute_data;
2491+
zend_vm_stack_free_call_frame(call);
2492+
call = EX(call);
2493+
} while (call);
2494+
}
2495+
2496+
for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) {
2497+
const zend_brk_cont_element *brk_cont = &EX(func)->op_array.brk_cont_array[i];
2498+
if (brk_cont->start < 0) {
2499+
continue;
2500+
} else if (brk_cont->start > op_num) {
2501+
/* further blocks will not be relevant... */
2502+
break;
2503+
} else if (op_num < brk_cont->brk) {
2504+
if (!catch_op_num || catch_op_num >= brk_cont->brk) {
2505+
zend_op *brk_opline = &EX(func)->op_array.opcodes[brk_cont->brk];
2506+
2507+
if (brk_opline->opcode == ZEND_FREE) {
2508+
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
2509+
} else if (brk_opline->opcode == ZEND_FE_FREE) {
2510+
zval *var = EX_VAR(brk_opline->op1.var);
2511+
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
2512+
zend_hash_iterator_del(Z_FE_ITER_P(var));
2513+
}
2514+
zval_ptr_dtor_nogc(var);
2515+
} else if (brk_opline->opcode == ZEND_END_SILENCE) {
2516+
/* restore previous error_reporting value */
2517+
if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) {
2518+
EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var));
2519+
}
2520+
}
2521+
}
2522+
}
2523+
}
2524+
}
2525+
/* }}} */
2526+
2527+
void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) {
2528+
i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
2529+
}
2530+
23812531
#ifdef HAVE_GCC_GLOBAL_REGS
23822532
# if defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(i386)
23832533
# define ZEND_VM_FP_GLOBAL_REG "%esi"

Zend/zend_execute.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ ZEND_API zval *zend_get_zval_ptr(int op_type, const znode_op *node, const zend_e
305305

306306
ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table);
307307
void zend_free_compiled_variables(zend_execute_data *execute_data);
308+
void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num);
308309

309310
#define CACHE_ADDR(num) \
310311
((void**)((char*)EX_RUN_TIME_CACHE() + (num)))

Zend/zend_generators.c

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -34,53 +34,15 @@ static zend_object *zend_generator_create(zend_class_entry *class_type);
3434
static void zend_generator_cleanup_unfinished_execution(zend_generator *generator) /* {{{ */
3535
{
3636
zend_execute_data *execute_data = generator->execute_data;
37-
zend_op_array *op_array = &execute_data->func->op_array;
37+
/* -1 required because we want the last run opcode, not the next to-be-run one. */
38+
uint32_t op_num = execute_data->opline - execute_data->func->op_array.opcodes - 1;
3839

3940
if (generator->send_target) {
4041
if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target);
4142
generator->send_target = NULL;
4243
}
4344

44-
/* Manually free loop variables, as execution couldn't reach their
45-
* SWITCH_FREE / FREE opcodes. */
46-
{
47-
/* -1 required because we want the last run opcode, not the
48-
* next to-be-run one. */
49-
uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
50-
51-
int i;
52-
for (i = 0; i < op_array->last_brk_cont; ++i) {
53-
zend_brk_cont_element *brk_cont = op_array->brk_cont_array + i;
54-
55-
if (brk_cont->start < 0) {
56-
continue;
57-
} else if ((uint32_t)brk_cont->start > op_num) {
58-
break;
59-
} else if (brk_cont->brk >= 0 && (uint32_t)brk_cont->brk > op_num) {
60-
zend_op *brk_opline = op_array->opcodes + brk_cont->brk;
61-
62-
if (brk_opline->opcode == ZEND_FREE) {
63-
zval *var = EX_VAR(brk_opline->op1.var);
64-
zval_ptr_dtor_nogc(var);
65-
} else if (brk_opline->opcode == ZEND_FE_FREE) {
66-
zval *var = EX_VAR(brk_opline->op1.var);
67-
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
68-
zend_hash_iterator_del(Z_FE_ITER_P(var));
69-
}
70-
zval_ptr_dtor_nogc(var);
71-
}
72-
}
73-
}
74-
}
75-
76-
/* If yield was used as a function argument there may be active
77-
* method calls those objects need to be freed */
78-
while (execute_data->call) {
79-
if (ZEND_CALL_INFO(execute_data->call) & ZEND_CALL_RELEASE_THIS) {
80-
OBJ_RELEASE(Z_OBJ(execute_data->call->This));
81-
}
82-
execute_data->call = execute_data->call->prev_execute_data;
83-
}
45+
zend_cleanup_unfinished_execution(execute_data, op_num, 0);
8446
}
8547
/* }}} */
8648

Zend/zend_vm_def.h

Lines changed: 1 addition & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -7208,146 +7208,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
72087208
}
72097209
}
72107210

7211-
if (UNEXPECTED(EX(call))) {
7212-
zend_execute_data *call = EX(call);
7213-
zend_op *opline = EX(func)->op_array.opcodes + op_num;
7214-
int level;
7215-
int do_exit;
7216-
7217-
do {
7218-
/* If the exception was thrown during a function call there might be
7219-
* arguments pushed to the stack that have to be dtor'ed. */
7220-
7221-
/* find the number of actually passed arguments */
7222-
level = 0;
7223-
do_exit = 0;
7224-
do {
7225-
switch (opline->opcode) {
7226-
case ZEND_DO_FCALL:
7227-
case ZEND_DO_ICALL:
7228-
case ZEND_DO_UCALL:
7229-
case ZEND_DO_FCALL_BY_NAME:
7230-
level++;
7231-
break;
7232-
case ZEND_INIT_FCALL:
7233-
case ZEND_INIT_FCALL_BY_NAME:
7234-
case ZEND_INIT_NS_FCALL_BY_NAME:
7235-
case ZEND_INIT_DYNAMIC_CALL:
7236-
case ZEND_INIT_USER_CALL:
7237-
case ZEND_INIT_METHOD_CALL:
7238-
case ZEND_INIT_STATIC_METHOD_CALL:
7239-
case ZEND_NEW:
7240-
if (level == 0) {
7241-
ZEND_CALL_NUM_ARGS(call) = 0;
7242-
do_exit = 1;
7243-
}
7244-
level--;
7245-
break;
7246-
case ZEND_SEND_VAL:
7247-
case ZEND_SEND_VAL_EX:
7248-
case ZEND_SEND_VAR:
7249-
case ZEND_SEND_VAR_EX:
7250-
case ZEND_SEND_REF:
7251-
case ZEND_SEND_VAR_NO_REF:
7252-
case ZEND_SEND_USER:
7253-
if (level == 0) {
7254-
ZEND_CALL_NUM_ARGS(call) = opline->op2.num;
7255-
do_exit = 1;
7256-
}
7257-
break;
7258-
case ZEND_SEND_ARRAY:
7259-
case ZEND_SEND_UNPACK:
7260-
if (level == 0) {
7261-
do_exit = 1;
7262-
}
7263-
break;
7264-
}
7265-
if (!do_exit) {
7266-
opline--;
7267-
}
7268-
} while (!do_exit);
7269-
if (call->prev_execute_data) {
7270-
/* skip current call region */
7271-
level = 0;
7272-
do_exit = 0;
7273-
do {
7274-
switch (opline->opcode) {
7275-
case ZEND_DO_FCALL:
7276-
case ZEND_DO_ICALL:
7277-
case ZEND_DO_UCALL:
7278-
case ZEND_DO_FCALL_BY_NAME:
7279-
level++;
7280-
break;
7281-
case ZEND_INIT_FCALL:
7282-
case ZEND_INIT_FCALL_BY_NAME:
7283-
case ZEND_INIT_NS_FCALL_BY_NAME:
7284-
case ZEND_INIT_DYNAMIC_CALL:
7285-
case ZEND_INIT_USER_CALL:
7286-
case ZEND_INIT_METHOD_CALL:
7287-
case ZEND_INIT_STATIC_METHOD_CALL:
7288-
case ZEND_NEW:
7289-
if (level == 0) {
7290-
do_exit = 1;
7291-
}
7292-
level--;
7293-
break;
7294-
}
7295-
opline--;
7296-
} while (!do_exit);
7297-
}
7298-
7299-
zend_vm_stack_free_args(EX(call));
7300-
7301-
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
7302-
if (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR) {
7303-
if (!(ZEND_CALL_INFO(call) & ZEND_CALL_CTOR_RESULT_UNUSED)) {
7304-
GC_REFCOUNT(Z_OBJ(call->This))--;
7305-
}
7306-
if (GC_REFCOUNT(Z_OBJ(call->This)) == 1) {
7307-
zend_object_store_ctor_failed(Z_OBJ(call->This));
7308-
}
7309-
}
7310-
OBJ_RELEASE(Z_OBJ(call->This));
7311-
}
7312-
if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
7313-
zend_string_release(call->func->common.function_name);
7314-
zend_free_trampoline(call->func);
7315-
}
7316-
7317-
EX(call) = call->prev_execute_data;
7318-
zend_vm_stack_free_call_frame(call);
7319-
call = EX(call);
7320-
} while (call);
7321-
}
7322-
7323-
for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) {
7324-
if (EX(func)->op_array.brk_cont_array[i].start < 0) {
7325-
continue;
7326-
} else if (EX(func)->op_array.brk_cont_array[i].start > op_num) {
7327-
/* further blocks will not be relevant... */
7328-
break;
7329-
} else if (op_num < EX(func)->op_array.brk_cont_array[i].brk) {
7330-
if (!catch_op_num ||
7331-
catch_op_num >= EX(func)->op_array.brk_cont_array[i].brk) {
7332-
zend_op *brk_opline = &EX(func)->op_array.opcodes[EX(func)->op_array.brk_cont_array[i].brk];
7333-
7334-
if (brk_opline->opcode == ZEND_FREE) {
7335-
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
7336-
} else if (brk_opline->opcode == ZEND_FE_FREE) {
7337-
zval *var = EX_VAR(brk_opline->op1.var);
7338-
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
7339-
zend_hash_iterator_del(Z_FE_ITER_P(var));
7340-
}
7341-
zval_ptr_dtor_nogc(var);
7342-
} else if (brk_opline->opcode == ZEND_END_SILENCE) {
7343-
/* restore previous error_reporting value */
7344-
if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) {
7345-
EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var));
7346-
}
7347-
}
7348-
}
7349-
}
7350-
}
7211+
i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
73517212

73527213
if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {
73537214
zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var);

0 commit comments

Comments
 (0)