--- /dev/null
+--TEST--
+'break' error (non positive numbers)
+--FILE--
+<?php
+function foo () {
+ break 0;
+}
+?>
+--EXPECTF--
+Fatal error: 'break' operator accepts only positive numbers in %sbreak_error_001.php on line 3
--- /dev/null
+--TEST--
+'break' error (operator with non-constant operand)
+--FILE--
+<?php
+function foo () {
+ break $x;
+}
+?>
+--EXPECTF--
+Fatal error: 'break' operator with non-constant operand is no longer supported in %sbreak_error_002.php on line 3
--- /dev/null
+--TEST--
+'break' error (not in the loop context)
+--FILE--
+<?php
+function foo () {
+ break;
+}
+?>
+--EXPECTF--
+Fatal error: 'break' not in the 'loop' or 'switch' context in %sbreak_error_003.php on line 3
--- /dev/null
+--TEST--
+'break' error (wrong level)
+--FILE--
+<?php
+function foo () {
+ while (1) {
+ break 2;
+ }
+}
+?>
+--EXPECTF--
+Fatal error: Cannot 'break' 2 levels in %sbreak_error_004.php on line 4
typedef struct _zend_loop_var {
zend_uchar opcode;
- uint32_t try_catch_offset;
- uint32_t brk_cont_offset;
- znode var;
+ zend_uchar var_type;
+ uint32_t var_num;
+ union {
+ uint32_t try_catch_offset;
+ uint32_t brk_cont_offset;
+ } u;
} zend_loop_var;
static inline void zend_alloc_cache_slot(uint32_t literal) {
LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit);
}
-static inline void zend_begin_loop(const znode *loop_var) /* {{{ */
+static inline void zend_begin_loop(zend_uchar free_opcode, const znode *loop_var) /* {{{ */
{
zend_brk_cont_element *brk_cont_element;
int parent = CG(context).current_brk_cont;
brk_cont_element->parent = parent;
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;
+ info.opcode = free_opcode;
+ info.var_type = loop_var->op_type;
+ info.var_num = loop_var->u.op.var;
+ info.u.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;
static zend_bool zend_is_call(zend_ast *ast);
-static zend_loop_var *generate_fast_calls(zend_loop_var *var) /* {{{ */
-{
- 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;
-}
-/* }}} */
-
-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;
- }
-
- 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;
-}
-/* }}} */
-
static uint32_t zend_add_try_element(uint32_t try_op) /* {{{ */
{
zend_op_array *op_array = CG(active_op_array);
}
/* }}} */
-static void zend_handle_loops_and_finally() /* {{{ */
+static int zend_handle_loops_and_finally_ex(zend_long depth) /* {{{ */
{
+ zend_loop_var *base;
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);
+
+ if (!loop_var) {
+ return 1;
}
+ base = zend_stack_base(&CG(loop_var_stack));
+ for (; loop_var >= base; loop_var--) {
+ if (loop_var->opcode == ZEND_FAST_CALL) {
+ zend_op *opline = get_next_op(CG(active_op_array));
+
+ opline->opcode = ZEND_FAST_CALL;
+ opline->result_type = IS_TMP_VAR;
+ opline->result.var = loop_var->var_num;
+ SET_UNUSED(opline->op1);
+ SET_UNUSED(opline->op2);
+ opline->op1.num = loop_var->u.try_catch_offset;
+ opline->extended_value = ZEND_FAST_CALL_UNBOUND;
+ } else if (loop_var->opcode == ZEND_RETURN) {
+ /* Stack separator */
+ break;
+ } else if (depth <= 1) {
+ return 1;
+ } else if (loop_var->opcode == ZEND_NOP) {
+ /* Loop doesn't have freeable variable */
+ depth--;
+ } else {
+ zend_op *opline;
+
+ ZEND_ASSERT(loop_var->var_type == IS_VAR || loop_var->var_type == IS_TMP_VAR);
+ opline = get_next_op(CG(active_op_array));
+ opline->opcode = loop_var->opcode;
+ opline->op1_type = loop_var->var_type;
+ opline->op1.var = loop_var->var_num;
+ SET_UNUSED(opline->op2);
+ opline->op2.num = loop_var->u.brk_cont_offset;
+ opline->extended_value = ZEND_FREE_ON_RETURN;
+ depth--;
+ }
+ }
+ return (depth == 0);
+}
+/* }}} */
+
+static int zend_handle_loops_and_finally(void) /* {{{ */
+{
+ zend_handle_loops_and_finally_ex(zend_stack_count(&CG(loop_var_stack)) + 1);
}
/* }}} */
zend_error_noreturn(E_COMPILE_ERROR, "'%s' not in the 'loop' or 'switch' context",
ast->kind == ZEND_AST_BREAK ? "break" : "continue");
} else {
- int array_offset = CG(context).current_brk_cont;
- zend_long nest_level = depth;
- zend_loop_var *loop_var = zend_stack_top(&CG(loop_var_stack));
-
- do {
- if (array_offset == -1) {
- zend_error_noreturn(E_COMPILE_ERROR, "Cannot '%s' %d level%s",
- ast->kind == ZEND_AST_BREAK ? "break" : "continue",
- depth, depth == 1 ? "" : "s");
- }
-
- 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;
- } while (--nest_level > 0);
+ if (!zend_handle_loops_and_finally_ex(depth)) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Cannot '%s' %d level%s",
+ ast->kind == ZEND_AST_BREAK ? "break" : "continue",
+ depth, depth == 1 ? "" : "s");
+ }
}
opline = zend_emit_op(NULL, ast->kind == ZEND_AST_BREAK ? ZEND_BRK : ZEND_CONT, NULL, NULL);
opline->op1.num = CG(context).current_brk_cont;
}
/* }}} */
-zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline) /* {{{ */
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline) /* {{{ */
{
zend_label *dest;
int current, remove_oplines = opline->op1.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->op2);
SET_UNUSED(opline->result);
- return opline;
+ ZEND_ASSERT(remove_oplines >= 0);
+ while (remove_oplines--) {
+ opline--;
+ MAKE_NOP(opline);
+ ZEND_VM_SET_OPCODE_HANDLER(opline);
+ }
}
/* }}} */
opnum_jmp = zend_emit_jump(0);
- zend_begin_loop(NULL);
+ zend_begin_loop(ZEND_NOP, NULL);
opnum_start = get_next_op_number(CG(active_op_array));
zend_compile_stmt(stmt_ast);
znode cond_node;
uint32_t opnum_start, opnum_cond;
- zend_begin_loop(NULL);
+ zend_begin_loop(ZEND_NOP, NULL);
opnum_start = get_next_op_number(CG(active_op_array));
zend_compile_stmt(stmt_ast);
opnum_jmp = zend_emit_jump(0);
- zend_begin_loop(NULL);
+ zend_begin_loop(ZEND_NOP, NULL);
opnum_start = get_next_op_number(CG(active_op_array));
zend_compile_stmt(stmt_ast);
zend_emit_assign_znode(key_ast, &key_node);
}
- reset_node.flag = ZEND_FE_FREE;
- zend_begin_loop(&reset_node);
+ zend_begin_loop(ZEND_FE_FREE, &reset_node);
zend_compile_stmt(stmt_ast);
zend_compile_expr(&expr_node, expr_ast);
- expr_node.flag = 0; /* Generate normal FREE */
- zend_begin_loop(&expr_node);
+ zend_begin_loop(ZEND_FREE, &expr_node);
case_node.op_type = IS_TMP_VAR;
case_node.u.op.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;
+ fast_call.var_type = IS_TMP_VAR;
+ fast_call.var_num = CG(context).fast_call_var;
+ fast_call.u.try_catch_offset = try_catch_offset;
zend_stack_push(&CG(loop_var_stack), &fast_call);
}
void zend_verify_namespace(void);
-zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *pass2_opline);
+void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline);
ZEND_API void function_add_ref(zend_function *function);
}
}
-static inline zend_brk_cont_element* zend_brk_cont(int nest_levels, int array_offset, const zend_op_array *op_array, const zend_execute_data *execute_data)
-{
- zend_brk_cont_element *jmp_to;
-
- do {
- ZEND_ASSERT(array_offset != -1);
- jmp_to = &op_array->brk_cont_array[array_offset];
- if (nest_levels > 1 && jmp_to->start >= 0) {
- zend_op *brk_opline = &op_array->opcodes[jmp_to->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);
- }
- }
- array_offset = jmp_to->parent;
- } while (--nest_levels > 0);
- return jmp_to;
-}
-
#if ZEND_INTENSIVE_DEBUGGING
#define CHECK_SYMBOL_TABLES() \
}
}
-static void zend_adjust_fast_call(zend_op_array *op_array, uint32_t fast_call, uint32_t start, uint32_t end)
-{
- int i;
- uint32_t op_num = 0;
-
- for (i = 0; i < op_array->last_try_catch; i++) {
- if (op_array->try_catch_array[i].finally_op > start
- && op_array->try_catch_array[i].finally_end < end) {
- op_num = op_array->try_catch_array[i].finally_op;
- start = op_array->try_catch_array[i].finally_end;
- }
- }
-
- if (op_num) {
- /* Must be ZEND_FAST_CALL */
- ZEND_ASSERT(op_array->opcodes[op_num - 2].opcode == ZEND_FAST_CALL);
- op_array->opcodes[op_num - 2].extended_value = ZEND_FAST_CALL_FROM_FINALLY;
- op_array->opcodes[op_num - 2].op2.opline_num = fast_call;
- }
-}
-
-static void zend_resolve_fast_call(zend_op_array *op_array, uint32_t fast_call, uint32_t op_num)
+static void zend_resolve_fast_call(zend_op_array *op_array, uint32_t op_num)
{
int i;
uint32_t finally_op_num = 0;
if (finally_op_num) {
/* Must be ZEND_FAST_CALL */
ZEND_ASSERT(op_array->opcodes[finally_op_num - 2].opcode == ZEND_FAST_CALL);
- if (op_array->opcodes[fast_call].extended_value == 0) {
- op_array->opcodes[fast_call].extended_value = ZEND_FAST_CALL_FROM_FINALLY;
- op_array->opcodes[fast_call].op2.opline_num = finally_op_num - 2;
- }
- }
-}
-
-static void zend_resolve_finally_call(zend_op_array *op_array, uint32_t op_num, uint32_t dst_num)
-{
- uint32_t start_op;
- zend_op *opline;
- uint32_t i = op_array->last_try_catch;
-
- if (dst_num != (uint32_t)-1) {
- zend_check_finally_breakout(op_array, op_num, dst_num);
- }
-
- /* the backward order matters */
- 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)) {
- /* we have a jump out of try block that needs executing finally */
- uint32_t fast_call_var;
-
- /* Must be ZEND_FAST_RET */
- ZEND_ASSERT(op_array->opcodes[op_array->try_catch_array[i].finally_end].opcode == ZEND_FAST_RET);
- fast_call_var = op_array->opcodes[op_array->try_catch_array[i].finally_end].op1.var;
-
- /* generate a FAST_CALL to finally block */
- start_op = get_next_op_number(op_array);
-
- 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);
- zend_adjust_fast_call(op_array, start_op,
- op_array->try_catch_array[i].finally_op,
- op_array->try_catch_array[i].finally_end);
- 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;
-
- /* 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)
- ) {
- ZEND_ASSERT(0);
- }
- }
-
- /* Finish the sequence with original opcode */
- opline = get_next_op(op_array);
- *opline = op_array->opcodes[op_num];
-
- /* Replace original opcode with jump to this sequence */
- opline = op_array->opcodes + op_num;
- opline->opcode = ZEND_JMP;
- SET_UNUSED(opline->op1);
- SET_UNUSED(opline->op2);
- opline->op1.opline_num = start_op;
-
- break;
+ if (op_array->opcodes[op_num].extended_value == 0) {
+ op_array->opcodes[op_num].extended_value = ZEND_FAST_CALL_FROM_FINALLY;
+ op_array->opcodes[op_num].op2.opline_num = finally_op_num - 2;
}
}
}
return opline->opcode == ZEND_BRK ? jmp_to->brk : jmp_to->cont;
}
-static void zend_resolve_finally_calls(zend_op_array *op_array)
-{
- uint32_t i, j;
- zend_op *opline;
-
- for (i = 0, j = op_array->last; i < j; i++) {
- opline = op_array->opcodes + i;
- switch (opline->opcode) {
- case ZEND_BRK:
- case ZEND_CONT:
- zend_check_finally_breakout(op_array, i, zend_get_brk_cont_target(op_array, opline));
- break;
- case ZEND_GOTO:
- 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;
- }
- }
-}
-
ZEND_API int pass_two(zend_op_array *op_array)
{
zend_op *opline, *end;
if (!ZEND_USER_CODE(op_array->type)) {
return 0;
}
- if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
- zend_resolve_finally_calls(op_array);
- }
if (CG(compiler_options) & ZEND_COMPILE_EXTENDED_INFO) {
zend_update_extended_info(op_array);
}
end = opline + op_array->last;
while (opline < end) {
switch (opline->opcode) {
+ 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, opline - op_array->opcodes);
+ ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
+ break;
+ case ZEND_FAST_RET:
+ zend_resolve_finally_ret(op_array, opline - op_array->opcodes);
+ break;
case ZEND_DECLARE_ANON_INHERITED_CLASS:
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
/* break omitted intentionally */
case ZEND_CONT:
{
uint32_t jmp_target = zend_get_brk_cont_target(op_array, opline);
+
+ if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
+ zend_check_finally_breakout(op_array, opline - op_array->opcodes, jmp_target);
+ }
opline->opcode = ZEND_JMP;
opline->op1.opline_num = jmp_target;
opline->op2.num = 0;
}
break;
case ZEND_GOTO:
- opline = zend_resolve_goto_label(op_array, opline);
+ zend_resolve_goto_label(op_array, opline);
+ if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
+ zend_check_finally_breakout(op_array, opline - op_array->opcodes, opline->op1.opline_num);
+ }
/* break omitted intentionally */
case ZEND_JMP:
- case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
break;