]> granicus.if.org Git - php/commitdiff
Try to fix finally issue
authorNikita Popov <nikic@php.net>
Fri, 10 Jul 2015 11:30:25 +0000 (13:30 +0200)
committerDmitry Stogov <dmitry@zend.com>
Tue, 4 Aug 2015 04:42:28 +0000 (07:42 +0300)
12 files changed:
Zend/tests/goto_in_foreach.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_013.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_014.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_015.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_016.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_017.phpt [new file with mode: 0644]
Zend/tests/try/try_finally_018.phpt [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_opcode.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

diff --git a/Zend/tests/goto_in_foreach.phpt b/Zend/tests/goto_in_foreach.phpt
new file mode 100644 (file)
index 0000000..e618772
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+goto inside foreach
+--FILE--
+<?php
+
+foreach ([0] as $x) {
+    goto a;
+a:
+    echo "loop\n";
+}
+
+echo "done\n";
+?>
+--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 (file)
index 0000000..a37df51
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+Return in try and finally inside loop
+--FILE--
+<?php
+
+function foo() {
+    $array = [1, 2, $n = 3];
+    foreach ($array as $value) {
+        try {
+            echo "try\n";
+            return;
+        } finally {
+            echo "finally\n";
+            return;
+        }
+    }
+}
+
+foo();
+?>
+===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 (file)
index 0000000..284892a
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+Break 2 in try and return in finally inside nested loop
+--FILE--
+<?php
+
+function foo() {
+    $array = [1, 2, $n = 3];
+    foreach ($array as $value) {
+        foreach ($array as $value) {
+            try {
+                echo "try\n";
+                break 2;
+            } finally {
+                echo "finally\n";
+                return;
+            }
+        }
+    }
+}
+
+foo();
+?>
+===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 (file)
index 0000000..d2580a2
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+Ignoring return inside loop using finally
+--FILE--
+<?php
+
+function foo() {
+    $array = [1, 2, $n = 3];
+    foreach ($array as $value) {
+        var_dump($value);
+        try {
+            try {
+                foreach ($array as $_) {
+                    return;
+                }
+            } finally {
+                throw new Exception;
+            }
+        } catch (Exception $e) { }
+    }
+}
+
+foo();
+?>
+===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 (file)
index 0000000..dc91b42
--- /dev/null
@@ -0,0 +1,39 @@
+--TEST--
+Exception during break 2
+--FILE--
+<?php
+
+class A {
+    public $a = 1;
+    public $b = 2;
+
+    public function __destruct() {
+        throw new Exception;
+    }
+}
+
+function foo() {
+    foreach ([0] as $_) {
+        foreach (new A as $value) {
+            try {
+                break 2;
+            } catch (Exception $e) {
+                echo "catch\n";
+            } finally {
+                echo "finally\n";
+            }
+        }
+    }
+}
+
+try {
+    foo();
+} catch (Exception $e) {
+    echo "outer catch\n";
+}
+?>
+===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 (file)
index 0000000..5ba58af
--- /dev/null
@@ -0,0 +1,42 @@
+--TEST--
+Exception during break 2 with multiple try/catch
+--FILE--
+<?php
+
+class A {
+    public $a = 1;
+    public $b = 2;
+
+    public function __destruct() {
+        throw new Exception;
+    }
+}
+
+function foo() {
+    foreach ([0] as $_) {
+        try {
+            foreach (new A as $value) {
+                try {
+                    break 2;
+                } catch (Exception $e) {
+                    echo "catch1\n";
+                } finally {
+                    echo "finally1\n";
+                }
+            }
+        } catch (Exception $e) {
+            echo "catch2\n";
+        } finally {
+            echo "finally2\n";
+        }
+    }
+}
+
+foo();
+?>
+===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 (file)
index 0000000..383b448
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+Combination of foreach, finally and goto
+--FILE--
+<?php
+foreach ([new stdClass] as $a) {
+    try {
+        foreach ([new stdClass] as $a) {
+            try {
+                foreach ([new stdClass] as $a) {
+                    goto out;
+                }
+            } finally {
+                echo "finally1\n";
+            }
+out: ;
+        }
+    } finally {
+        echo "finally2\n";
+    }
+}
+?>
+--EXPECT--
+finally1
+finally2
index 0e9f478f973a8faa9afd5d88835fa07fb22d87e4..63bbee717a5109bca4afe9324a3c9f57a8213065 100644 (file)
 
 #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;
                }
-               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;
+               if (elem->finally_op && opnum < elem->finally_op - 1
+                       && (dest->opline_num > elem->finally_end || dest->opline_num < elem->try_op)
+               ) {
+                       remove_oplines--;
+               }
+       }
+
+       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);
        }
index 06796aab831fc6d1362594041d649d44e1c440c7..265a97db079e31e94191cd078c52cb85076e776f 100644 (file)
@@ -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)
index 347761e9a8a736f2ff73270e2aacda4982fb32bf..9ead51465056b31094fdf324e9c18331d72e2652 100644 (file)
@@ -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:
index 3e460d15903cd33d633e63faa0f46c405fb82581..10b947d7d2e18c45a52c1f73008adaa113549896 100644 (file)
@@ -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)) {
index ca7120aa12a6a3bfaf2df67733d7cebeb211367f..c580ba04d7df80d173a0782658646a291a607797 100644 (file)
@@ -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)) {