diff options
author | Nikita Popov <nikic@php.net> | 2015-06-20 18:35:27 +0200 |
---|---|---|
committer | Nikita Popov <nikic@php.net> | 2015-06-20 18:37:28 +0200 |
commit | ddf41d3aeb6461983572966421917cebc7bf01d2 (patch) | |
tree | 2fd8a2141e6fd8cc722193f9f43aa3cd9849c15d | |
parent | 7adc0ae631213c7abc0e4102531b273866322ecf (diff) | |
download | php-git-ddf41d3aeb6461983572966421917cebc7bf01d2.tar.gz |
Fix generator memory leak
Make sure HANDLE_EXCEPTION and generator unwinds stay in sync in
the future by extracting a common function.
-rw-r--r-- | Zend/zend_execute.c | 150 | ||||
-rw-r--r-- | Zend/zend_execute.h | 1 | ||||
-rw-r--r-- | Zend/zend_generators.c | 44 | ||||
-rw-r--r-- | Zend/zend_vm_def.h | 141 | ||||
-rw-r--r-- | Zend/zend_vm_execute.h | 141 |
5 files changed, 156 insertions, 321 deletions
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 39a8fb242d..491dbb8e68 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2378,6 +2378,156 @@ static zend_always_inline zend_generator *zend_get_running_generator(zend_execut } /* }}} */ +static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) /* {{{ */ +{ + int i; + if (UNEXPECTED(EX(call))) { + zend_execute_data *call = EX(call); + zend_op *opline = EX(func)->op_array.opcodes + op_num; + int level; + int do_exit; + + do { + /* If the exception was thrown during a function call there might be + * arguments pushed to the stack that have to be dtor'ed. */ + + /* find the number of actually passed arguments */ + 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) { + ZEND_CALL_NUM_ARGS(call) = 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_REF: + case ZEND_SEND_VAR_NO_REF: + case ZEND_SEND_USER: + if (level == 0) { + ZEND_CALL_NUM_ARGS(call) = opline->op2.num; + do_exit = 1; + } + break; + case ZEND_SEND_ARRAY: + case ZEND_SEND_UNPACK: + 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); + } + + zend_vm_stack_free_args(EX(call)); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + if (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR) { + if (!(ZEND_CALL_INFO(call) & ZEND_CALL_CTOR_RESULT_UNUSED)) { + GC_REFCOUNT(Z_OBJ(call->This))--; + } + if (GC_REFCOUNT(Z_OBJ(call->This)) == 1) { + zend_object_store_ctor_failed(Z_OBJ(call->This)); + } + } + OBJ_RELEASE(Z_OBJ(call->This)); + } + if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { + zend_string_release(call->func->common.function_name); + zend_free_trampoline(call->func); + } + + EX(call) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + call = EX(call); + } while (call); + } + + for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) { + const zend_brk_cont_element *brk_cont = &EX(func)->op_array.brk_cont_array[i]; + if (brk_cont->start < 0) { + continue; + } else if (brk_cont->start > op_num) { + /* further blocks will not be relevant... */ + break; + } else if (op_num < brk_cont->brk) { + if (!catch_op_num || catch_op_num >= brk_cont->brk) { + zend_op *brk_opline = &EX(func)->op_array.opcodes[brk_cont->brk]; + + if (brk_opline->opcode == ZEND_FREE) { + zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var)); + } else if (brk_opline->opcode == ZEND_FE_FREE) { + zval *var = EX_VAR(brk_opline->op1.var); + if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) { + zend_hash_iterator_del(Z_FE_ITER_P(var)); + } + zval_ptr_dtor_nogc(var); + } else if (brk_opline->opcode == ZEND_END_SILENCE) { + /* restore previous error_reporting value */ + if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) { + EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var)); + } + } + } + } + } +} +/* }}} */ + +void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) { + i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); +} + #ifdef HAVE_GCC_GLOBAL_REGS # if defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(i386) # define ZEND_VM_FP_GLOBAL_REG "%esi" diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3d470a9e81..91100a9de5 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -305,6 +305,7 @@ ZEND_API zval *zend_get_zval_ptr(int op_type, const znode_op *node, const zend_e ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table); void zend_free_compiled_variables(zend_execute_data *execute_data); +void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num); #define CACHE_ADDR(num) \ ((void**)((char*)EX_RUN_TIME_CACHE() + (num))) diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 9687a556af..fea3a2f50e 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -34,53 +34,15 @@ static zend_object *zend_generator_create(zend_class_entry *class_type); static void zend_generator_cleanup_unfinished_execution(zend_generator *generator) /* {{{ */ { zend_execute_data *execute_data = generator->execute_data; - zend_op_array *op_array = &execute_data->func->op_array; + /* -1 required because we want the last run opcode, not the next to-be-run one. */ + uint32_t op_num = execute_data->opline - execute_data->func->op_array.opcodes - 1; if (generator->send_target) { if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target); generator->send_target = NULL; } - /* Manually free loop variables, as execution couldn't reach their - * SWITCH_FREE / FREE opcodes. */ - { - /* -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; - - int i; - for (i = 0; i < op_array->last_brk_cont; ++i) { - zend_brk_cont_element *brk_cont = op_array->brk_cont_array + i; - - if (brk_cont->start < 0) { - continue; - } else if ((uint32_t)brk_cont->start > op_num) { - break; - } else if (brk_cont->brk >= 0 && (uint32_t)brk_cont->brk > op_num) { - zend_op *brk_opline = op_array->opcodes + brk_cont->brk; - - if (brk_opline->opcode == ZEND_FREE) { - zval *var = EX_VAR(brk_opline->op1.var); - zval_ptr_dtor_nogc(var); - } else if (brk_opline->opcode == ZEND_FE_FREE) { - zval *var = EX_VAR(brk_opline->op1.var); - if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) { - zend_hash_iterator_del(Z_FE_ITER_P(var)); - } - zval_ptr_dtor_nogc(var); - } - } - } - } - - /* If yield was used as a function argument there may be active - * method calls those objects need to be freed */ - while (execute_data->call) { - if (ZEND_CALL_INFO(execute_data->call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(execute_data->call->This)); - } - execute_data->call = execute_data->call->prev_execute_data; - } + zend_cleanup_unfinished_execution(execute_data, op_num, 0); } /* }}} */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7aea3daaf6..8462d950cb 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7208,146 +7208,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) } } - if (UNEXPECTED(EX(call))) { - zend_execute_data *call = EX(call); - zend_op *opline = EX(func)->op_array.opcodes + op_num; - int level; - int do_exit; - - do { - /* If the exception was thrown during a function call there might be - * arguments pushed to the stack that have to be dtor'ed. */ - - /* find the number of actually passed arguments */ - 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) { - ZEND_CALL_NUM_ARGS(call) = 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_REF: - case ZEND_SEND_VAR_NO_REF: - case ZEND_SEND_USER: - if (level == 0) { - ZEND_CALL_NUM_ARGS(call) = opline->op2.num; - do_exit = 1; - } - break; - case ZEND_SEND_ARRAY: - case ZEND_SEND_UNPACK: - 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); - } - - zend_vm_stack_free_args(EX(call)); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - if (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR) { - if (!(ZEND_CALL_INFO(call) & ZEND_CALL_CTOR_RESULT_UNUSED)) { - GC_REFCOUNT(Z_OBJ(call->This))--; - } - if (GC_REFCOUNT(Z_OBJ(call->This)) == 1) { - zend_object_store_ctor_failed(Z_OBJ(call->This)); - } - } - OBJ_RELEASE(Z_OBJ(call->This)); - } - if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { - zend_string_release(call->func->common.function_name); - zend_free_trampoline(call->func); - } - - EX(call) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - call = EX(call); - } while (call); - } - - for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) { - if (EX(func)->op_array.brk_cont_array[i].start < 0) { - continue; - } else if (EX(func)->op_array.brk_cont_array[i].start > op_num) { - /* further blocks will not be relevant... */ - break; - } else if (op_num < EX(func)->op_array.brk_cont_array[i].brk) { - if (!catch_op_num || - catch_op_num >= EX(func)->op_array.brk_cont_array[i].brk) { - zend_op *brk_opline = &EX(func)->op_array.opcodes[EX(func)->op_array.brk_cont_array[i].brk]; - - if (brk_opline->opcode == ZEND_FREE) { - zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var)); - } else if (brk_opline->opcode == ZEND_FE_FREE) { - zval *var = EX_VAR(brk_opline->op1.var); - if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) { - zend_hash_iterator_del(Z_FE_ITER_P(var)); - } - zval_ptr_dtor_nogc(var); - } else if (brk_opline->opcode == ZEND_END_SILENCE) { - /* restore previous error_reporting value */ - if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) { - EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var)); - } - } - } - } - } + i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8010458b54..df9b1ed58c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1501,146 +1501,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER( } } - if (UNEXPECTED(EX(call))) { - zend_execute_data *call = EX(call); - zend_op *opline = EX(func)->op_array.opcodes + op_num; - int level; - int do_exit; - - do { - /* If the exception was thrown during a function call there might be - * arguments pushed to the stack that have to be dtor'ed. */ - - /* find the number of actually passed arguments */ - 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) { - ZEND_CALL_NUM_ARGS(call) = 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_REF: - case ZEND_SEND_VAR_NO_REF: - case ZEND_SEND_USER: - if (level == 0) { - ZEND_CALL_NUM_ARGS(call) = opline->op2.num; - do_exit = 1; - } - break; - case ZEND_SEND_ARRAY: - case ZEND_SEND_UNPACK: - 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); - } - - zend_vm_stack_free_args(EX(call)); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - if (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR) { - if (!(ZEND_CALL_INFO(call) & ZEND_CALL_CTOR_RESULT_UNUSED)) { - GC_REFCOUNT(Z_OBJ(call->This))--; - } - if (GC_REFCOUNT(Z_OBJ(call->This)) == 1) { - zend_object_store_ctor_failed(Z_OBJ(call->This)); - } - } - OBJ_RELEASE(Z_OBJ(call->This)); - } - if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { - zend_string_release(call->func->common.function_name); - zend_free_trampoline(call->func); - } - - EX(call) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - call = EX(call); - } while (call); - } - - for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) { - if (EX(func)->op_array.brk_cont_array[i].start < 0) { - continue; - } else if (EX(func)->op_array.brk_cont_array[i].start > op_num) { - /* further blocks will not be relevant... */ - break; - } else if (op_num < EX(func)->op_array.brk_cont_array[i].brk) { - if (!catch_op_num || - catch_op_num >= EX(func)->op_array.brk_cont_array[i].brk) { - zend_op *brk_opline = &EX(func)->op_array.opcodes[EX(func)->op_array.brk_cont_array[i].brk]; - - if (brk_opline->opcode == ZEND_FREE) { - zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var)); - } else if (brk_opline->opcode == ZEND_FE_FREE) { - zval *var = EX_VAR(brk_opline->op1.var); - if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) { - zend_hash_iterator_del(Z_FE_ITER_P(var)); - } - zval_ptr_dtor_nogc(var); - } else if (brk_opline->opcode == ZEND_END_SILENCE) { - /* restore previous error_reporting value */ - if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) { - EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var)); - } - } - } - } - } + i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var); |