From 743801054d83285e19f26a1d00dc81e4f7caeefa Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 10 Jul 2015 13:30:25 +0200 Subject: [PATCH] Try to fix finally issue --- Zend/tests/goto_in_foreach.phpt | 16 ++ Zend/tests/try/try_finally_013.phpt | 25 +++ Zend/tests/try/try_finally_014.phpt | 27 +++ Zend/tests/try/try_finally_015.phpt | 29 +++ Zend/tests/try/try_finally_016.phpt | 39 ++++ Zend/tests/try/try_finally_017.phpt | 42 +++++ Zend/tests/try/try_finally_018.phpt | 24 +++ Zend/zend_compile.c | 271 ++++++++++++++-------------- Zend/zend_compile.h | 3 +- Zend/zend_opcode.c | 40 ++-- Zend/zend_vm_def.h | 25 +-- Zend/zend_vm_execute.h | 25 +-- 12 files changed, 377 insertions(+), 189 deletions(-) create mode 100644 Zend/tests/goto_in_foreach.phpt create mode 100644 Zend/tests/try/try_finally_013.phpt create mode 100644 Zend/tests/try/try_finally_014.phpt create mode 100644 Zend/tests/try/try_finally_015.phpt create mode 100644 Zend/tests/try/try_finally_016.phpt create mode 100644 Zend/tests/try/try_finally_017.phpt create mode 100644 Zend/tests/try/try_finally_018.phpt diff --git a/Zend/tests/goto_in_foreach.phpt b/Zend/tests/goto_in_foreach.phpt new file mode 100644 index 00000000000..e618772ba64 --- /dev/null +++ b/Zend/tests/goto_in_foreach.phpt @@ -0,0 +1,16 @@ +--TEST-- +goto inside foreach +--FILE-- + +--EXPECT-- +loop +done diff --git a/Zend/tests/try/try_finally_013.phpt b/Zend/tests/try/try_finally_013.phpt new file mode 100644 index 00000000000..a37df5147d8 --- /dev/null +++ b/Zend/tests/try/try_finally_013.phpt @@ -0,0 +1,25 @@ +--TEST-- +Return in try and finally inside loop +--FILE-- + +===DONE=== +--EXPECT-- +try +finally +===DONE=== diff --git a/Zend/tests/try/try_finally_014.phpt b/Zend/tests/try/try_finally_014.phpt new file mode 100644 index 00000000000..284892a8b5a --- /dev/null +++ b/Zend/tests/try/try_finally_014.phpt @@ -0,0 +1,27 @@ +--TEST-- +Break 2 in try and return in finally inside nested loop +--FILE-- + +===DONE=== +--EXPECT-- +try +finally +===DONE=== diff --git a/Zend/tests/try/try_finally_015.phpt b/Zend/tests/try/try_finally_015.phpt new file mode 100644 index 00000000000..d2580a2e332 --- /dev/null +++ b/Zend/tests/try/try_finally_015.phpt @@ -0,0 +1,29 @@ +--TEST-- +Ignoring return inside loop using finally +--FILE-- + +===DONE=== +--EXPECT-- +int(1) +int(2) +int(3) +===DONE=== diff --git a/Zend/tests/try/try_finally_016.phpt b/Zend/tests/try/try_finally_016.phpt new file mode 100644 index 00000000000..dc91b42b508 --- /dev/null +++ b/Zend/tests/try/try_finally_016.phpt @@ -0,0 +1,39 @@ +--TEST-- +Exception during break 2 +--FILE-- + +===DONE=== +--EXPECT-- +finally +outer catch +===DONE=== diff --git a/Zend/tests/try/try_finally_017.phpt b/Zend/tests/try/try_finally_017.phpt new file mode 100644 index 00000000000..5ba58afdeac --- /dev/null +++ b/Zend/tests/try/try_finally_017.phpt @@ -0,0 +1,42 @@ +--TEST-- +Exception during break 2 with multiple try/catch +--FILE-- + +===DONE=== +--EXPECT-- +finally1 +catch2 +finally2 +===DONE=== diff --git a/Zend/tests/try/try_finally_018.phpt b/Zend/tests/try/try_finally_018.phpt new file mode 100644 index 00000000000..383b44844ef --- /dev/null +++ b/Zend/tests/try/try_finally_018.phpt @@ -0,0 +1,24 @@ +--TEST-- +Combination of foreach, finally and goto +--FILE-- + +--EXPECT-- +finally1 +finally2 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0e9f478f973..63bbee717a5 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -54,6 +54,13 @@ #define FC(member) (CG(file_context).member) +typedef struct _zend_loop_var { + zend_uchar opcode; + uint32_t try_catch_offset; + uint32_t brk_cont_offset; + znode var; +} zend_loop_var; + static inline void zend_alloc_cache_slot(uint32_t literal) { zend_op_array *op_array = CG(active_op_array); Z_CACHE_SLOT(op_array->literals[literal]) = op_array->cache_size; @@ -284,7 +291,7 @@ void zend_file_context_end(zend_file_context *prev_context) /* {{{ */ void zend_init_compiler_data_structures(void) /* {{{ */ { - zend_stack_init(&CG(loop_var_stack), sizeof(znode)); + zend_stack_init(&CG(loop_var_stack), sizeof(zend_loop_var)); zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op)); CG(active_class_entry) = NULL; CG(in_compilation) = 0; @@ -562,19 +569,25 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */ { zend_brk_cont_element *brk_cont_element; int parent = CG(context).current_brk_cont; + zend_loop_var info = {0}; CG(context).current_brk_cont = CG(active_op_array)->last_brk_cont; brk_cont_element = get_next_brk_cont_element(CG(active_op_array)); brk_cont_element->parent = parent; - if (loop_var) { - zend_stack_push(&CG(loop_var_stack), loop_var); + if (loop_var && (loop_var->op_type & (IS_VAR|IS_TMP_VAR))) { + info.opcode = loop_var->flag ? ZEND_FE_FREE : ZEND_FREE; + info.var = *loop_var; + info.brk_cont_offset = CG(context).current_brk_cont; brk_cont_element->start = get_next_op_number(CG(active_op_array)); } else { + info.opcode = ZEND_NOP; /* The start field is used to free temporary variables in case of exceptions. * We won't try to free something of we don't have loop variable. */ brk_cont_element->start = -1; } + + zend_stack_push(&CG(loop_var_stack), &info); } /* }}} */ @@ -586,9 +599,7 @@ static inline void zend_end_loop(int cont_addr) /* {{{ */ brk_cont_element->brk = get_next_op_number(CG(active_op_array)); CG(context).current_brk_cont = brk_cont_element->parent; - if (brk_cont_element->start >= 0) { - zend_stack_del_top(&CG(loop_var_stack)); - } + zend_stack_del_top(&CG(loop_var_stack)); } /* }}} */ @@ -877,24 +888,46 @@ static void str_dtor(zval *zv) /* {{{ */ { static zend_bool zend_is_call(zend_ast *ast); -static int generate_free_loop_var(znode *var) /* {{{ */ +static zend_loop_var *generate_fast_calls(zend_loop_var *var) /* {{{ */ { - switch (var->op_type) { - case IS_UNUSED: - /* Stack separator on function boundary, stop applying */ - return 1; - case IS_VAR: - case IS_TMP_VAR: - { - zend_op *opline = get_next_op(CG(active_op_array)); + zend_loop_var *base = zend_stack_base(&CG(loop_var_stack)); + for (; var >= base && var->opcode == ZEND_FAST_CALL; var--) { + zend_op *opline = get_next_op(CG(active_op_array)); + opline->opcode = ZEND_FAST_CALL; + SET_NODE(opline->result, &var->var); + SET_UNUSED(opline->op1); + SET_UNUSED(opline->op2); + opline->op1.num = var->try_catch_offset; + opline->extended_value = ZEND_FAST_CALL_UNBOUND; + } + return var; +} +/* }}} */ - opline->opcode = var->flag ? ZEND_FE_FREE : ZEND_FREE; - SET_NODE(opline->op1, var); - SET_UNUSED(opline->op2); - } +static zend_loop_var *generate_free_loop_var(zend_loop_var *info) /* {{{ */ +{ + zend_op *opline; + zend_loop_var *base = zend_stack_base(&CG(loop_var_stack)); + ZEND_ASSERT(info->opcode != ZEND_FAST_CALL); + + if (info < base || info->opcode == ZEND_RETURN) { + /* Stack separator */ + return NULL; } - return 0; + if (info->opcode == ZEND_NOP) { + /* Loop doesn't have freeable variable */ + return info - 1; + } + + ZEND_ASSERT(info->var.op_type == IS_VAR || info->var.op_type == IS_TMP_VAR); + opline = get_next_op(CG(active_op_array)); + opline->opcode = info->opcode; + SET_NODE(opline->op1, &info->var); + SET_UNUSED(opline->op2); + opline->op2.num = info->brk_cont_offset; + opline->extended_value = ZEND_FREE_ON_RETURN; + return info - 1; } /* }}} */ @@ -3464,24 +3497,16 @@ void zend_compile_unset(zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_free_foreach_and_switch_variables(uint32_t flags) /* {{{ */ +static void zend_handle_loops_and_finally() /* {{{ */ { - uint32_t start_op_number = get_next_op_number(CG(active_op_array)); - - zend_stack_apply(&CG(loop_var_stack), ZEND_STACK_APPLY_TOPDOWN, (int (*)(void *element)) generate_free_loop_var); - - if (flags) { - uint32_t end_op_number = get_next_op_number(CG(active_op_array)); - - while (start_op_number < end_op_number) { - CG(active_op_array)->opcodes[start_op_number].extended_value |= flags; - start_op_number++; - } + zend_loop_var *loop_var = zend_stack_top(&CG(loop_var_stack)); + while (loop_var) { + loop_var = generate_fast_calls(loop_var); + loop_var = generate_free_loop_var(loop_var); } } /* }}} */ - void zend_compile_return(zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; @@ -3499,7 +3524,7 @@ void zend_compile_return(zend_ast *ast) /* {{{ */ zend_compile_expr(&expr_node, expr_ast); } - zend_free_foreach_and_switch_variables(ZEND_FREE_ON_RETURN); + zend_handle_loops_and_finally(); if (CG(context).in_finally) { opline = zend_emit_op(NULL, ZEND_DISCARD_EXCEPTION, NULL, NULL); @@ -3579,7 +3604,7 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } else { int array_offset = CG(context).current_brk_cont; zend_long nest_level = depth; - znode *loop_var = zend_stack_top(&CG(loop_var_stack)); + zend_loop_var *loop_var = zend_stack_top(&CG(loop_var_stack)); do { if (array_offset == -1) { @@ -3588,9 +3613,9 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ depth, depth == 1 ? "" : "s"); } - if (nest_level > 1 && CG(active_op_array)->brk_cont_array[array_offset].start >= 0) { - generate_free_loop_var(loop_var); - loop_var--; + loop_var = generate_fast_calls(loop_var); + if (nest_level > 1) { + loop_var = generate_free_loop_var(loop_var); } array_offset = CG(active_op_array)->brk_cont_array[array_offset].parent; @@ -3602,115 +3627,65 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } /* }}} */ -void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline) /* {{{ */ +zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline) /* {{{ */ { zend_label *dest; - int current, distance, free_vars; + int current, remove_oplines = opline->op1.num; zval *label; - znode *loop_var = NULL; + uint32_t opnum = opline - op_array->opcodes; - if (pass2_opline) { - label = CT_CONSTANT_EX(op_array, pass2_opline->op2.constant); - } else { - label = &label_node->u.constant; - } + label = CT_CONSTANT_EX(op_array, opline->op2.constant); if (CG(context).labels == NULL || - (dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL) { - - if (pass2_opline) { - CG(in_compilation) = 1; - CG(active_op_array) = op_array; - CG(zend_lineno) = pass2_opline->lineno; - zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label)); - } else { - /* Label is not defined. Delay to pass 2. */ - zend_op *opline; - - current = CG(context).current_brk_cont; - while (current != -1) { - if (op_array->brk_cont_array[current].start >= 0) { - zend_emit_op(NULL, ZEND_NOP, NULL, NULL); - } - current = op_array->brk_cont_array[current].parent; - } - opline = zend_emit_op(NULL, ZEND_GOTO, NULL, label_node); - opline->extended_value = CG(context).current_brk_cont; - return; - } + (dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL + ) { + CG(in_compilation) = 1; + CG(active_op_array) = op_array; + CG(zend_lineno) = opline->lineno; + zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label)); } zval_dtor(label); ZVAL_NULL(label); - /* Check that we are not moving into loop or switch */ - if (pass2_opline) { - current = pass2_opline->extended_value; - } else { - current = CG(context).current_brk_cont; - } - if (!pass2_opline) { - loop_var = zend_stack_top(&CG(loop_var_stack)); - } - for (distance = 0, free_vars = 0; current != dest->brk_cont; distance++) { + current = opline->extended_value; + for (; current != dest->brk_cont; current = op_array->brk_cont_array[current].parent) { if (current == -1) { - if (pass2_opline) { - CG(in_compilation) = 1; - CG(active_op_array) = op_array; - CG(zend_lineno) = pass2_opline->lineno; - } + CG(in_compilation) = 1; + CG(active_op_array) = op_array; + CG(zend_lineno) = opline->lineno; zend_error_noreturn(E_COMPILE_ERROR, "'goto' into loop or switch statement is disallowed"); } if (op_array->brk_cont_array[current].start >= 0) { - if (pass2_opline) { - free_vars++; - } else { - generate_free_loop_var(loop_var); - loop_var--; - } + remove_oplines--; } - current = op_array->brk_cont_array[current].parent; } - if (pass2_opline) { - if (free_vars) { - current = pass2_opline->extended_value; - while (current != dest->brk_cont) { - if (op_array->brk_cont_array[current].start >= 0) { - zend_op *brk_opline = &op_array->opcodes[op_array->brk_cont_array[current].brk]; - - if (brk_opline->opcode == ZEND_FREE) { - (pass2_opline - free_vars)->opcode = ZEND_FREE; - (pass2_opline - free_vars)->op1_type = brk_opline->op1_type; - if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { - (pass2_opline - free_vars)->op1.var = brk_opline->op1.var; - } else { - (pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var); - ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars); - } - free_vars--; - } else if (brk_opline->opcode == ZEND_FE_FREE) { - (pass2_opline - free_vars)->opcode = ZEND_FE_FREE; - (pass2_opline - free_vars)->op1_type = brk_opline->op1_type; - if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) { - (pass2_opline - free_vars)->op1.var = brk_opline->op1.var; - } else { - (pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var); - ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars); - } - free_vars--; - } - } - current = op_array->brk_cont_array[current].parent; - } + for (current = 0; current < op_array->last_try_catch; ++current) { + zend_try_catch_element *elem = &op_array->try_catch_array[current]; + if (elem->try_op > opnum) { + break; + } + if (elem->finally_op && opnum < elem->finally_op - 1 + && (dest->opline_num > elem->finally_end || dest->opline_num < elem->try_op) + ) { + remove_oplines--; } - pass2_opline->opcode = ZEND_JMP; - pass2_opline->op1.opline_num = dest->opline_num; - SET_UNUSED(pass2_opline->op2); - pass2_opline->extended_value = 0; - } else { - zend_op *opline = zend_emit_op(NULL, ZEND_JMP, NULL, NULL); - opline->op1.opline_num = dest->opline_num; } + + ZEND_ASSERT(remove_oplines >= 0); + while (remove_oplines--) { + MAKE_NOP(opline); + opline--; + } + + opline->opcode = ZEND_JMP; + opline->op1.opline_num = dest->opline_num; + opline->extended_value = 0; + SET_UNUSED(opline->op1); + SET_UNUSED(opline->op2); + SET_UNUSED(opline->result); + + return opline; } /* }}} */ @@ -3718,9 +3693,16 @@ void zend_compile_goto(zend_ast *ast) /* {{{ */ { zend_ast *label_ast = ast->child[0]; znode label_node; + zend_op *opline; + uint32_t opnum_start = get_next_op_number(CG(active_op_array)); zend_compile_expr(&label_node, label_ast); - zend_resolve_goto_label(CG(active_op_array), &label_node, NULL); + + /* Label resolution and unwinding adjustments happen in pass two. */ + zend_handle_loops_and_finally(); + opline = zend_emit_op(NULL, ZEND_GOTO, NULL, &label_node); + opline->op1.num = get_next_op_number(CG(active_op_array)) - opnum_start - 1; + opline->extended_value = CG(context).current_brk_cont; } /* }}} */ @@ -3908,7 +3890,7 @@ void zend_compile_foreach(zend_ast *ast) /* {{{ */ zend_emit_assign_znode(key_ast, &key_node); } - reset_node.flag = 1; /* generate FE_FREE */ + reset_node.flag = ZEND_FE_FREE; zend_begin_loop(&reset_node); zend_compile_stmt(stmt_ast); @@ -3923,7 +3905,7 @@ void zend_compile_foreach(zend_ast *ast) /* {{{ */ zend_end_loop(opnum_fetch); - generate_free_loop_var(&reset_node); + zend_emit_op(NULL, ZEND_FE_FREE, &reset_node, NULL); } /* }}} */ @@ -4084,6 +4066,21 @@ void zend_compile_try(zend_ast *ast) /* {{{ */ try_catch_offset = zend_add_try_element(get_next_op_number(CG(active_op_array))); + if (finally_ast) { + zend_loop_var fast_call; + if (!(CG(active_op_array)->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) { + CG(active_op_array)->fn_flags |= ZEND_ACC_HAS_FINALLY_BLOCK; + CG(context).fast_call_var = get_temporary_variable(CG(active_op_array)); + } + + /* Push FAST_CALL on unwind stack */ + fast_call.opcode = ZEND_FAST_CALL; + fast_call.var.op_type = IS_TMP_VAR; + fast_call.var.u.op.var = CG(context).fast_call_var; + fast_call.try_catch_offset = try_catch_offset; + zend_stack_push(&CG(loop_var_stack), &fast_call); + } + zend_compile_stmt(try_ast); if (catches->children != 0) { @@ -4137,11 +4134,9 @@ void zend_compile_try(zend_ast *ast) /* {{{ */ if (finally_ast) { uint32_t opnum_jmp = get_next_op_number(CG(active_op_array)) + 1; - - if (!(CG(active_op_array)->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) { - CG(active_op_array)->fn_flags |= ZEND_ACC_HAS_FINALLY_BLOCK; - CG(context).fast_call_var = get_temporary_variable(CG(active_op_array)); - } + + /* Pop FAST_CALL from unwind stack */ + zend_stack_del_top(&CG(loop_var_stack)); opline = zend_emit_op(NULL, ZEND_FAST_CALL, NULL, NULL); opline->op1.opline_num = opnum_jmp + 1; @@ -4831,8 +4826,8 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */ { /* Push a separator to the loop variable stack */ - znode dummy_var; - dummy_var.op_type = IS_UNUSED; + zend_loop_var dummy_var; + dummy_var.opcode = ZEND_RETURN; zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 06796aab831..265a97db079 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -712,7 +712,7 @@ void zend_do_extended_fcall_end(void); void zend_verify_namespace(void); -void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline); +zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *pass2_opline); ZEND_API void function_add_ref(zend_function *function); @@ -946,6 +946,7 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, #define ZEND_FAST_RET_TO_FINALLY 2 #define ZEND_FAST_CALL_FROM_FINALLY 1 +#define ZEND_FAST_CALL_UNBOUND 2 #define ZEND_ARRAY_ELEMENT_REF (1<<0) #define ZEND_ARRAY_NOT_PACKED (1<<1) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 347761e9a8a..9ead5146505 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -561,7 +561,7 @@ static void zend_resolve_finally_call(zend_op_array *op_array, uint32_t op_num, zend_check_finally_breakout(op_array, op_num, dst_num); } - /* the backward order is mater */ + /* the backward order matters */ while (i > 0) { i--; if (op_array->try_catch_array[i].finally_op && @@ -591,22 +591,16 @@ static void zend_resolve_finally_call(zend_op_array *op_array, uint32_t op_num, zend_resolve_fast_call(op_array, start_op, op_array->try_catch_array[i].finally_op - 2); opline->op1.opline_num = op_array->try_catch_array[i].finally_op; - /* generate a sequence of FAST_CALL to upward finally block */ + /* We should not generate JMPs that require handling multiple finally blocks */ while (i > 0) { i--; if (op_array->try_catch_array[i].finally_op && op_num >= op_array->try_catch_array[i].try_op && op_num < op_array->try_catch_array[i].finally_op - 1 && (dst_num < op_array->try_catch_array[i].try_op || - dst_num > op_array->try_catch_array[i].finally_end)) { - - opline = get_next_op(op_array); - opline->opcode = ZEND_FAST_CALL; - opline->result_type = IS_TMP_VAR; - opline->result.var = fast_call_var; - SET_UNUSED(opline->op1); - SET_UNUSED(opline->op2); - opline->op1.opline_num = op_array->try_catch_array[i].finally_op; + dst_num > op_array->try_catch_array[i].finally_end) + ) { + ZEND_ASSERT(0); } } @@ -676,31 +670,27 @@ static void zend_resolve_finally_calls(zend_op_array *op_array) for (i = 0, j = op_array->last; i < j; i++) { opline = op_array->opcodes + i; switch (opline->opcode) { - case ZEND_RETURN: - case ZEND_RETURN_BY_REF: - case ZEND_GENERATOR_RETURN: - zend_resolve_finally_call(op_array, i, (uint32_t)-1); - break; case ZEND_BRK: case ZEND_CONT: - zend_resolve_finally_call(op_array, i, zend_get_brk_cont_target(op_array, opline)); + zend_check_finally_breakout(op_array, i, zend_get_brk_cont_target(op_array, opline)); break; case ZEND_GOTO: - if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) != IS_LONG) { - zend_resolve_goto_label(op_array, NULL, opline); - } - /* break omitted intentionally */ + zend_check_finally_breakout(op_array, i, + zend_resolve_goto_label(op_array, opline)->op1.opline_num); + break; case ZEND_JMP: zend_resolve_finally_call(op_array, i, opline->op1.opline_num); break; case ZEND_FAST_CALL: + if (opline->extended_value == ZEND_FAST_CALL_UNBOUND) { + opline->op1.opline_num = op_array->try_catch_array[opline->op1.num].finally_op; + opline->extended_value = 0; + } zend_resolve_fast_call(op_array, i, i); break; case ZEND_FAST_RET: zend_resolve_finally_ret(op_array, i); break; - default: - break; } } } @@ -756,9 +746,7 @@ ZEND_API int pass_two(zend_op_array *op_array) } break; case ZEND_GOTO: - if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) != IS_LONG) { - zend_resolve_goto_label(op_array, NULL, opline); - } + opline = zend_resolve_goto_label(op_array, opline); /* break omitted intentionally */ case ZEND_JMP: case ZEND_FAST_CALL: diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3e460d15903..10b947d7d2e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7075,6 +7075,19 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) int in_finally = 0; ZEND_VM_INTERRUPT_CHECK(); + + { + const zend_op *exc_opline = EG(opline_before_exception); + if ((exc_opline->opcode == ZEND_FREE || exc_opline->opcode == ZEND_FE_FREE) + && exc_opline->extended_value & ZEND_FREE_ON_RETURN) { + /* exceptions thrown because of loop var destruction on return/break/... + * are logically thrown at the end of the foreach loop, so adjust the + * op_num. + */ + op_num = EX(func)->op_array.brk_cont_array[exc_opline->op2.num].brk; + } + } + for (i = 0; i < EX(func)->op_array.last_try_catch; i++) { if (EX(func)->op_array.try_catch_array[i].try_op > op_num) { /* further blocks will not be relevant... */ @@ -7095,18 +7108,6 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) } } - if (catch_op_num) { - if ((EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - ) { - /* exceptions thrown because of TMP variable destruction on "return" - * statement should't be caught in the same function. - * See: Zend/tests/try_finally_012.phpt - */ - catch_op_num = 0; - } - } - i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index ca7120aa12a..c580ba04d7d 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1480,6 +1480,19 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER( int in_finally = 0; ZEND_VM_INTERRUPT_CHECK(); + + { + const zend_op *exc_opline = EG(opline_before_exception); + if ((exc_opline->opcode == ZEND_FREE || exc_opline->opcode == ZEND_FE_FREE) + && exc_opline->extended_value & ZEND_FREE_ON_RETURN) { + /* exceptions thrown because of loop var destruction on return/break/... + * are logically thrown at the end of the foreach loop, so adjust the + * op_num. + */ + op_num = EX(func)->op_array.brk_cont_array[exc_opline->op2.num].brk; + } + } + for (i = 0; i < EX(func)->op_array.last_try_catch; i++) { if (EX(func)->op_array.try_catch_array[i].try_op > op_num) { /* further blocks will not be relevant... */ @@ -1500,18 +1513,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER( } } - if (catch_op_num) { - if ((EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - ) { - /* exceptions thrown because of TMP variable destruction on "return" - * statement should't be caught in the same function. - * See: Zend/tests/try_finally_012.phpt - */ - catch_op_num = 0; - } - } - i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {