summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikic@php.net>2015-06-20 18:35:27 +0200
committerNikita Popov <nikic@php.net>2015-06-20 18:37:28 +0200
commitddf41d3aeb6461983572966421917cebc7bf01d2 (patch)
tree2fd8a2141e6fd8cc722193f9f43aa3cd9849c15d
parent7adc0ae631213c7abc0e4102531b273866322ecf (diff)
downloadphp-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.c150
-rw-r--r--Zend/zend_execute.h1
-rw-r--r--Zend/zend_generators.c44
-rw-r--r--Zend/zend_vm_def.h141
-rw-r--r--Zend/zend_vm_execute.h141
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);