]> granicus.if.org Git - php/commitdiff
Fix generator memory leak
authorNikita Popov <nikic@php.net>
Sat, 20 Jun 2015 16:35:27 +0000 (18:35 +0200)
committerNikita Popov <nikic@php.net>
Sat, 20 Jun 2015 16:37:28 +0000 (18:37 +0200)
Make sure HANDLE_EXCEPTION and generator unwinds stay in sync in
the future by extracting a common function.

Zend/zend_execute.c
Zend/zend_execute.h
Zend/zend_generators.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

index 39a8fb242dd0b99a4cfb4db1542bf235ac953439..491dbb8e68f1880dc01ed8dab4d6dd873b55b0fa 100644 (file)
@@ -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"
index 3d470a9e810157698d738994085061911b5dab51..91100a9de506c412b67d96a180d613ee982837ca 100644 (file)
@@ -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)))
index 9687a556afed19a89af9e9174b75293289920427..fea3a2f50ed01069abd97169132c0365fe4db104 100644 (file)
@@ -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);
 }
 /* }}} */
 
index 7aea3daaf64e7195b0cb3e9d3ba9c6cf4b9af6b5..8462d950cb1669bcfab1701e087ce4e07f815a41 100644 (file)
@@ -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);
index 8010458b54b54fc2175019944a081d837fd9d095..df9b1ed58c347bb8a4da24266659280e9188b25f 100644 (file)
@@ -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);