--- /dev/null
+--TEST--
+Live range & free on return
+--FILE--
+<?php
+class bar {
+ public $foo = 1;
+ public $bar = 1;
+
+ function __destruct() {
+ throw $this->foo;
+ }
+}
+foreach (new bar as &$foo) {
+ try {
+ $foo = new Exception;
+ return; // frees the loop variable
+ } catch (Exception $e) {
+ echo "exception\n";
+ }
+}
+echo "end\n";
+?>
+--EXPECTF--
+Fatal error: Uncaught Exception in %stemporary_cleaning_009.php:12
+Stack trace:
+#0 {main}
+ thrown in %stemporary_cleaning_009.php on line 12
--- /dev/null
+--TEST--
+Live range & throw from finally
+--XFAIL--
+See Bug #62210 and attempt to fix it in "tmp_livelibess" branch
+--FILE--
+<?php
+function test() {
+ try {
+ $a = [1, 2, 3];
+ return $a + [];
+ } finally {
+ throw new Exception;
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo "exception\n";
+}
+?>
+--EXPECT--
+exception
--- /dev/null
+--TEST--
+Live range & return from finally
+--FILE--
+<?php
+$array = [1];
+foreach ([0] as $_) {
+ foreach ($array as $v) {
+ try {
+ echo "ok\n";
+ return;
+ } finally {
+ echo "ok\n";
+ return;
+ }
+ }
+}
+?>
+--EXPECT--
+ok
+ok
--- /dev/null
+--TEST--
+Try finally (exception in "return" statement)
+--FILE--
+<?php
+class A {
+ public $x = 1;
+ public $y = 2;
+ function __destruct() {
+ throw new Exception();
+ }
+}
+try{
+ $a = 0;
+ switch ($a) {
+ case 0:
+ }
+ switch ($a) {
+ case 0:
+ }
+ switch ($a) {
+ case 0:
+ }
+ foreach([new stdClass()] as $x) {
+ foreach(new A() as $a) {
+ foreach([new stdClass()] as $y) {
+ try {
+ if (0) { echo "0" . (int)5; }
+ return $a;
+ } catch (Exception $e) {
+ echo "exception1\n";
+ }
+ }
+ }
+ }
+} catch (Exception $e) {
+ echo "exception2\n";
+}
+?>
+--EXPECT--
+exception2
+
zend_uchar opcode;
zend_uchar var_type;
uint32_t var_num;
- uint32_t try_catch_offset;
+ union {
+ uint32_t try_catch_offset;
+ uint32_t live_range_offset;
+ } u;
} zend_loop_var;
static inline void zend_alloc_cache_slot(uint32_t literal) {
LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit);
}
-static void zend_add_live_range(zend_op_array *op_array, uint32_t start, uint32_t end) /* {{{ */
+static uint32_t zend_start_live_range(zend_op_array *op_array, uint32_t start) /* {{{ */
{
zend_live_range *range;
- if (start != end) {
- op_array->last_live_range++;
- op_array->live_range = erealloc(op_array->live_range, sizeof(zend_live_range) * op_array->last_live_range);
- range = op_array->live_range + op_array->last_live_range - 1;
- range->start = start;
+ op_array->last_live_range++;
+ op_array->live_range = erealloc(op_array->live_range, sizeof(zend_live_range) * op_array->last_live_range);
+ range = op_array->live_range + op_array->last_live_range - 1;
+ range->start = start;
+ return op_array->last_live_range - 1;
+}
+/* }}} */
+
+static void zend_end_live_range(zend_op_array *op_array, uint32_t offset, uint32_t end) /* {{{ */
+{
+ zend_live_range *range = op_array->live_range + offset;
+
+ if (range->start == end && offset == op_array->last_live_range - 1) {
+ op_array->last_live_range--;
+ } else {
range->end = end;
}
}
brk_cont_element->parent = parent;
if (loop_var && (loop_var->op_type & (IS_VAR|IS_TMP_VAR))) {
+ uint32_t start = get_next_op_number(CG(active_op_array));
+
info.opcode = free_opcode;
info.var_type = loop_var->op_type;
info.var_num = loop_var->u.op.var;
- info.try_catch_offset = CG(active_op_array)->last_try_catch;
- brk_cont_element->start = get_next_op_number(CG(active_op_array));
+ info.u.live_range_offset = zend_start_live_range(CG(active_op_array), start);
+ brk_cont_element->start = start;
} else {
info.opcode = ZEND_NOP;
/* The start field is used to free temporary variables in case of exceptions.
static inline void zend_end_loop(int cont_addr) /* {{{ */
{
+ uint32_t end = get_next_op_number(CG(active_op_array));
zend_brk_cont_element *brk_cont_element
= &CG(context).brk_cont_array[CG(context).current_brk_cont];
brk_cont_element->cont = cont_addr;
- brk_cont_element->brk = get_next_op_number(CG(active_op_array));
+ brk_cont_element->brk = end;
CG(context).current_brk_cont = brk_cont_element->parent;
+ if (brk_cont_element->start != -1) {
+ zend_loop_var *loop_var = zend_stack_top(&CG(loop_var_stack));
+ zend_end_live_range(CG(active_op_array), loop_var->u.live_range_offset, end);
+ }
+
zend_stack_del_top(&CG(loop_var_stack));
}
/* }}} */
opline->result.var = loop_var->var_num;
SET_UNUSED(opline->op1);
SET_UNUSED(opline->op2);
- opline->op1.num = loop_var->try_catch_offset;
+ opline->op1.num = loop_var->u.try_catch_offset;
} else if (loop_var->opcode == ZEND_RETURN) {
/* Stack separator */
break;
opline->op1_type = loop_var->var_type;
opline->op1.var = loop_var->var_num;
SET_UNUSED(opline->op2);
- opline->op2.num = loop_var->try_catch_offset;
+ opline->op2.num = loop_var->u.live_range_offset;
opline->extended_value = ZEND_FREE_ON_RETURN;
depth--;
}
zend_end_loop(opnum_fetch);
opline = zend_emit_op(NULL, ZEND_FE_FREE, &reset_node, NULL);
- zend_add_live_range(CG(active_op_array),
- opnum_fetch, opline - CG(active_op_array)->opcodes);
}
/* }}} */
znode expr_node, case_node;
zend_op *opline;
uint32_t *jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
- uint32_t opnum_default_jmp, opnum_start;
+ uint32_t opnum_default_jmp;
zend_compile_expr(&expr_node, expr_ast);
- opnum_start = get_next_op_number(CG(active_op_array));
zend_begin_loop(ZEND_FREE, &expr_node);
case_node.op_type = IS_TMP_VAR;
if (expr_node.op_type == IS_VAR || expr_node.op_type == IS_TMP_VAR) {
opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL);
- zend_add_live_range(CG(active_op_array),
- opnum_start, opline - CG(active_op_array)->opcodes);
} else if (expr_node.op_type == IS_CONST) {
zval_dtor(&expr_node.u.constant);
}
fast_call.opcode = ZEND_FAST_CALL;
fast_call.var_type = IS_TMP_VAR;
fast_call.var_num = CG(context).fast_call_var;
- fast_call.try_catch_offset = try_catch_offset;
+ fast_call.u.try_catch_offset = try_catch_offset;
zend_stack_push(&CG(loop_var_stack), &fast_call);
}
{
zend_ast *expr_ast = ast->child[0];
znode silence_node;
- uint32_t begin_opline_num;
- zend_op *opline;
+ uint32_t range;
- begin_opline_num = get_next_op_number(CG(active_op_array));
+ range = zend_start_live_range(CG(active_op_array), get_next_op_number(CG(active_op_array)));
zend_emit_op_tmp(&silence_node, ZEND_BEGIN_SILENCE, NULL, NULL);
if (expr_ast->kind == ZEND_AST_VAR) {
zend_compile_expr(result, expr_ast);
}
- opline = zend_emit_op(NULL, ZEND_END_SILENCE, &silence_node, NULL);
-
/* Store BEGIN_SILENCE/END_SILENCE pair to restore previous
* EG(error_reporting) value on exception */
- zend_add_live_range(CG(active_op_array),
- begin_opline_num + 1, opline - CG(active_op_array)->opcodes);
+ zend_end_live_range(CG(active_op_array), range, get_next_op_number(CG(active_op_array)));
+
+ zend_emit_op(NULL, ZEND_END_SILENCE, &silence_node, NULL);
}
/* }}} */
GET_NODE(result, opline->result);
} else {
uint32_t var;
+ uint32_t range = zend_start_live_range(CG(active_op_array), rope_init_lineno);
init_opline->extended_value = j;
opline->opcode = ZEND_ROPE_END;
i--;
}
- zend_add_live_range(CG(active_op_array),
- rope_init_lineno, opline - CG(active_op_array)->opcodes);
+ zend_end_live_range(CG(active_op_array), range, opline - CG(active_op_array)->opcodes);
/* Update all the previous opcodes to use the same variable */
while (opline != init_opline) {
{
int i;
- i = EX(func)->op_array.last_live_range;
- while (i) {
- const zend_live_range *range;
-
- i--;
- range = &EX(func)->op_array.live_range[i];
- if (range->end <= op_num) {
+ for (i = 0; i < EX(func)->op_array.last_live_range; i++) {
+ const zend_live_range *range = &EX(func)->op_array.live_range[i];
+ if (range->start > op_num) {
/* further blocks will not be relevant... */
break;
- } else if (op_num >= range->start) {
+ } else if (op_num < range->end) {
if (!catch_op_num || catch_op_num >= range->end) {
zend_op *opline = &EX(func)->op_array.opcodes[range->end];
uint32_t var_num = opline->op1.var;
ZEND_VM_JMP(opline);
}
-ZEND_VM_HANDLER(70, ZEND_FREE, TMPVAR, TRY_CATCH)
+ZEND_VM_HANDLER(70, ZEND_FREE, TMPVAR, LIVE_RANGE)
{
USE_OPLINE
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
-ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, TRY_CATCH)
+ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, LIVE_RANGE)
{
zval *var;
USE_OPLINE
ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
{
uint32_t op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes;
- uint32_t last_try_catch = EX(func)->op_array.last_try_catch;
int i;
uint32_t catch_op_num = 0, finally_op_num = 0, finally_op_end = 0;
int in_finally = 0;
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 don't check the inner exception regions
+ * are logically thrown at the end of the foreach loop, so adjust the
+ * op_num.
*/
- last_try_catch = exc_opline->op2.num;
+ op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
}
}
- for (i = 0; i < last_try_catch; i++) {
+ 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... */
break;
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
uint32_t op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes;
- uint32_t last_try_catch = EX(func)->op_array.last_try_catch;
int i;
uint32_t catch_op_num = 0, finally_op_num = 0, finally_op_end = 0;
int in_finally = 0;
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 don't check the inner exception regions
+ * are logically thrown at the end of the foreach loop, so adjust the
+ * op_num.
*/
- last_try_catch = exc_opline->op2.num;
+ op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
}
}
- for (i = 0; i < last_try_catch; i++) {
+ 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... */
break;
"ZEND_VM_OP1_NUM" => 1<<3,
"ZEND_VM_OP1_JMP_ADDR" => 1<<4,
"ZEND_VM_OP1_TRY_CATCH" => 1<<5,
+ "ZEND_VM_OP1_LIVE_RANGE" => 1<<6,
"ZEND_VM_OP2_SPEC" => 1<<8,
"ZEND_VM_OP2_CONST" => 1<<9,
"ZEND_VM_OP2_NUM" => 1<<11,
"ZEND_VM_OP2_JMP_ADDR" => 1<<12,
"ZEND_VM_OP2_TRY_CATCH" => 1<<13,
+ "ZEND_VM_OP2_LIVE_RANGE" => 1<<14,
"ZEND_VM_EXT_NUM" => 1<<16,
"ZEND_VM_EXT_VAR" => 1<<17,
"NUM" => ZEND_VM_OP1_NUM,
"JMP_ADDR" => ZEND_VM_OP1_JMP_ADDR,
"TRY_CATCH" => ZEND_VM_OP1_TRY_CATCH,
+ "LIVE_RANGE" => ZEND_VM_OP1_LIVE_RANGE,
);
$vm_ext_decode = array(
0x00000801,
0x00011003,
0x00010300,
- 0x00002005,
+ 0x00004005,
0x00800703,
0x00010703,
0x02000007,
0x00000103,
0x00001003,
0x00040001,
- 0x00002005,
+ 0x00004005,
0x00010700,
0x00000000,
0x00000000,
#define ZEND_VM_OP1_NUM 0x00000008
#define ZEND_VM_OP1_JMP_ADDR 0x00000010
#define ZEND_VM_OP1_TRY_CATCH 0x00000020
+#define ZEND_VM_OP1_LIVE_RANGE 0x00000040
#define ZEND_VM_OP2_SPEC 0x00000100
#define ZEND_VM_OP2_CONST 0x00000200
#define ZEND_VM_OP2_TMPVAR 0x00000400
#define ZEND_VM_OP2_NUM 0x00000800
#define ZEND_VM_OP2_JMP_ADDR 0x00001000
#define ZEND_VM_OP2_TRY_CATCH 0x00002000
+#define ZEND_VM_OP2_LIVE_RANGE 0x00004000
#define ZEND_VM_EXT_NUM 0x00010000
#define ZEND_VM_EXT_VAR 0x00020000
#define ZEND_VM_EXT_JMP_ADDR 0x00040000
if (op_array->last_live_range) {
int i = 0;
int j = 0;
+ uint32_t *map;
+ ALLOCA_FLAG(use_heap);
+
+ map = (uint32_t *)DO_ALLOCA(sizeof(uint32_t) * op_array->last_live_range);
do {
if (op_array->opcodes[op_array->live_range[i].end].op1.var != var) {
+ map[i] = j;
if (i != j) {
op_array->live_range[j] = op_array->live_range[i];
}
}
i++;
} while (i < op_array->last_live_range);
- op_array->last_live_range = j;
+ if (i != j) {
+ zend_op *opline = op_array->opcodes;
+ zend_op *end = opline + op_array->last;
+
+ op_array->last_live_range = j;
+ while (opline != end) {
+ if ((opline->opcode == ZEND_FREE || opline->opcode == ZEND_FE_FREE) &&
+ opline->extended_value == ZEND_FREE_ON_RETURN) {
+ opline->op2.num = map[opline->op2.num];
+ }
+ opline++;
+ }
+ }
}
}