From a16aa4c42c0483406b53518e28259162c1b7699f Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 4 Aug 2015 07:37:06 +0300 Subject: [PATCH] Move most "finally" related code-generation from pass_two() to compiler. --- Zend/tests/break_error_001.phpt | 10 ++ Zend/tests/break_error_002.phpt | 10 ++ Zend/tests/break_error_003.phpt | 10 ++ Zend/tests/break_error_004.phpt | 12 +++ Zend/zend_compile.c | 167 +++++++++++++++----------------- Zend/zend_compile.h | 2 +- Zend/zend_execute.c | 25 ----- Zend/zend_opcode.c | 155 +++++------------------------ 8 files changed, 143 insertions(+), 248 deletions(-) create mode 100644 Zend/tests/break_error_001.phpt create mode 100644 Zend/tests/break_error_002.phpt create mode 100644 Zend/tests/break_error_003.phpt create mode 100644 Zend/tests/break_error_004.phpt diff --git a/Zend/tests/break_error_001.phpt b/Zend/tests/break_error_001.phpt new file mode 100644 index 0000000000..eb8ce2efef --- /dev/null +++ b/Zend/tests/break_error_001.phpt @@ -0,0 +1,10 @@ +--TEST-- +'break' error (non positive numbers) +--FILE-- + +--EXPECTF-- +Fatal error: 'break' operator accepts only positive numbers in %sbreak_error_001.php on line 3 diff --git a/Zend/tests/break_error_002.phpt b/Zend/tests/break_error_002.phpt new file mode 100644 index 0000000000..a1c172d198 --- /dev/null +++ b/Zend/tests/break_error_002.phpt @@ -0,0 +1,10 @@ +--TEST-- +'break' error (operator with non-constant operand) +--FILE-- + +--EXPECTF-- +Fatal error: 'break' operator with non-constant operand is no longer supported in %sbreak_error_002.php on line 3 diff --git a/Zend/tests/break_error_003.phpt b/Zend/tests/break_error_003.phpt new file mode 100644 index 0000000000..fa782343b1 --- /dev/null +++ b/Zend/tests/break_error_003.phpt @@ -0,0 +1,10 @@ +--TEST-- +'break' error (not in the loop context) +--FILE-- + +--EXPECTF-- +Fatal error: 'break' not in the 'loop' or 'switch' context in %sbreak_error_003.php on line 3 diff --git a/Zend/tests/break_error_004.phpt b/Zend/tests/break_error_004.phpt new file mode 100644 index 0000000000..1b99a99ee9 --- /dev/null +++ b/Zend/tests/break_error_004.phpt @@ -0,0 +1,12 @@ +--TEST-- +'break' error (wrong level) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot 'break' 2 levels in %sbreak_error_004.php on line 4 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 63bbee717a..6deea135d6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -56,9 +56,12 @@ 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) { @@ -565,7 +568,7 @@ void zend_stop_lexing(void) 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; @@ -576,9 +579,10 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */ 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; @@ -888,49 +892,6 @@ static void str_dtor(zval *zv) /* {{{ */ { 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); @@ -3497,13 +3458,55 @@ void zend_compile_unset(zend_ast *ast) /* {{{ */ } /* }}} */ -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); } /* }}} */ @@ -3602,24 +3605,11 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ 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; @@ -3627,7 +3617,7 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } /* }}} */ -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; @@ -3672,12 +3662,6 @@ zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline) /* {{ } } - 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; @@ -3685,7 +3669,12 @@ zend_op *zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline) /* {{ 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); + } } /* }}} */ @@ -3734,7 +3723,7 @@ void zend_compile_while(zend_ast *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); @@ -3757,7 +3746,7 @@ void zend_compile_do_while(zend_ast *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); @@ -3808,7 +3797,7 @@ void zend_compile_for(zend_ast *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); @@ -3890,8 +3879,7 @@ void zend_compile_foreach(zend_ast *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); @@ -3966,8 +3954,7 @@ void zend_compile_switch(zend_ast *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)); @@ -4075,9 +4062,9 @@ void zend_compile_try(zend_ast *ast) /* {{{ */ /* 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); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 265a97db07..ae9a2806ff 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); -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); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 3009ddfcff..b9a7ecfe1b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1944,31 +1944,6 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } } -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() \ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 9ead514650..237e7cd6f6 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -508,28 +508,7 @@ static void zend_check_finally_breakout(zend_op_array *op_array, uint32_t op_num } } -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; @@ -544,78 +523,9 @@ static void zend_resolve_fast_call(zend_op_array *op_array, uint32_t fast_call, 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; } } } @@ -662,39 +572,6 @@ static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const ze 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; @@ -702,9 +579,6 @@ ZEND_API int pass_two(zend_op_array *op_array) 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); } @@ -728,6 +602,17 @@ ZEND_API int pass_two(zend_op_array *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 */ @@ -739,6 +624,10 @@ ZEND_API int pass_two(zend_op_array *op_array) 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; @@ -746,10 +635,12 @@ ZEND_API int pass_two(zend_op_array *op_array) } 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; -- 2.40.0