. Fixed bug #71897 (ASCII 0x7F Delete control character permitted in
identifiers). (Andrea)
. Fixed bug #72188 (Nested try/finally blocks losing return value). (Dmitry)
+ . Fixed bug #72213 (Finally leaks on nested exceptions). (Dmitry, Nikita)
. Implemented the RFC `Support Class Constant Visibility`. (Sean DuBois,
Reeze Xia, Dmitry)
. Added void return type. (Andrea)
string(9) "not catch"
NULL
-Fatal error: Uncaught Error: Class 'NotExists' not found in %sbug65784.php:%d
+Fatal error: Uncaught Exception: not catched in %sbug65784.php:42
Stack trace:
-#0 %s(%d): foo3()
+#0 %sbug65784.php(52): foo3()
#1 {main}
- thrown in %sbug65784.php on line %d
+
+Next Error: Class 'NotExists' not found in %s/bug65784.php:46
+Stack trace:
+#0 %sbug65784.php(52): foo3()
+#1 {main}
+ thrown in %sbug65784.php on line 46
foo();
?>
--EXPECTF--
-Fatal error: Uncaught TypeError: Return value of foo() must be of the type array, none returned in %s29.php:%d
+Fatal error: Uncaught Exception: xxxx in %s:%d
+Stack trace:
+#0 %s(%d): foo()
+#1 {main}
+
+Next TypeError: Return value of foo() must be of the type array, none returned in %s29.php:%d
Stack trace:
#0 %s(%d): foo()
#1 {main}
--- /dev/null
+--TEST--
+Bug #70228 (memleak if return in finally block)
+--FILE--
+<?php
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ throw new Exception(2);
+ } finally {
+ return 42;
+ }
+ }
+}
+
+var_dump(test());
+?>
+--EXPECT--
+int(42)
--- /dev/null
+--TEST--
+Bug #70228 (memleak if return in finally block)
+--FILE--
+<?php
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ try {
+ } finally {
+ return 42;
+ }
+ } finally {
+ throw new Exception(2);
+ }
+ }
+}
+
+try {
+ var_dump(test());
+} catch (Exception $e) {
+ do {
+ echo $e->getMessage() . "\n";
+ $e = $e->getPrevious();
+ } while ($e);
+}
+?>
+--EXPECT--
+2
+1
--- /dev/null
+--TEST--
+Bug #70228 (memleak if return in finally block)
+--FILE--
+<?php
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ try {
+ try {
+ } finally {
+ return 42;
+ }
+ } finally {
+ throw new Exception(3);
+ }
+ } catch (Exception $e) {}
+ }
+}
+
+try {
+ var_dump(test());
+} catch (Exception $e) {
+ do {
+ echo $e->getMessage() . "\n";
+ $e = $e->getPrevious();
+ } while ($e);
+}
+?>
+--EXPECT--
+1
--TEST--
Bug #72213 (Finally leaks on nested exceptions)
---XFAIL--
-See https://bugs.php.net/bug.php?id=72213
--FILE--
<?php
function test() {
--- /dev/null
+--TEST--
+Loop var dtor throwing exception during return inside try/catch inside finally
+--FILE--
+<?php
+
+class Dtor {
+ public function __destruct() {
+ throw new Exception(2);
+ }
+}
+
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ foreach ([new Dtor] as $v) {
+ unset($v);
+ return 42;
+ }
+ } catch (Exception $e) {
+ }
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+
+?>
+--EXPECTF--
+Exception: 1 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
--- /dev/null
+--TEST--
+Exception in finally inside finally following try/catch containing throwing try/finally
+--FILE--
+<?php
+
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ try {
+ } finally {
+ throw new Exception(2);
+ }
+ } catch (Exception $e) {}
+ try {
+ } finally {
+ throw new Exception(3);
+ }
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+?>
+--EXPECTF--
+Exception: 1 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
+
+Next Exception: 3 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
--- /dev/null
+--TEST--
+Throw in try of try/finally inside catch
+--FILE--
+<?php
+
+function test() {
+ try {
+ throw new Exception(1);
+ } catch (Exception $e) {
+ try {
+ throw new Exception(2);
+ } finally {
+ }
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+
+?>
+--EXPECTF--
+Exception: 2 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
--- /dev/null
+--TEST--
+Throw in finally inside catch inside finally
+--FILE--
+<?php
+
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ throw new Exception(2);
+ } catch (Exception $e) {
+ try {
+ } finally {
+ throw new Exception(3);
+ }
+ }
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+
+?>
+--EXPECTF--
+Exception: 1 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
+
+Next Exception: 3 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
--- /dev/null
+--TEST--
+Return in try with throw in finally, inside other finally
+--FILE--
+<?php
+
+function test() {
+ try {
+ throw new Exception(1);
+ } finally {
+ try {
+ return 42;
+ } finally {
+ throw new Exception(2);
+ }
+ }
+}
+
+try {
+ test();
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+
+?>
+--EXPECTF--
+Exception: 1 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
+
+Next Exception: 2 in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
CG(context).backpatch_count = 0;
CG(context).in_finally = 0;
CG(context).fast_call_var = -1;
+ CG(context).try_catch_offset = -1;
CG(context).current_brk_cont = -1;
CG(context).last_brk_cont = 0;
CG(context).brk_cont_array = NULL;
SET_UNUSED(opline->op1);
SET_UNUSED(opline->op2);
opline->op1.num = loop_var->u.try_catch_offset;
+ } else if (loop_var->opcode == ZEND_DISCARD_EXCEPTION) {
+ zend_op *opline = get_next_op(CG(active_op_array));
+ opline->opcode = ZEND_DISCARD_EXCEPTION;
+ opline->op1_type = IS_TMP_VAR;
+ opline->op1.var = loop_var->var_num;
+ SET_UNUSED(opline->op2);
} else if (loop_var->opcode == ZEND_RETURN) {
/* Stack separator */
break;
zend_compile_expr(&expr_node, expr_ast);
}
- if (CG(context).in_finally) {
- opline = zend_emit_op(NULL, ZEND_DISCARD_EXCEPTION, NULL, NULL);
- opline->op1_type = IS_TMP_VAR;
- opline->op1.var = CG(context).fast_call_var;
- }
-
/* Generator return types are handled separately */
if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1);
uint32_t try_catch_offset;
uint32_t *jmp_opnums = safe_emalloc(sizeof(uint32_t), catches->children, 0);
uint32_t orig_fast_call_var = CG(context).fast_call_var;
+ uint32_t orig_try_catch_offset = CG(context).try_catch_offset;
if (catches->children == 0 && !finally_ast) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use try without catch or finally");
zend_stack_push(&CG(loop_var_stack), &fast_call);
}
+ CG(context).try_catch_offset = try_catch_offset;
+
zend_compile_stmt(try_ast);
if (catches->children != 0) {
}
if (finally_ast) {
+ zend_loop_var discard_exception;
uint32_t opnum_jmp = get_next_op_number(CG(active_op_array)) + 1;
/* Pop FAST_CALL from unwind stack */
zend_stack_del_top(&CG(loop_var_stack));
+ /* Push DISCARD_EXCEPTION on unwind stack */
+ discard_exception.opcode = ZEND_DISCARD_EXCEPTION;
+ discard_exception.var_type = IS_TMP_VAR;
+ discard_exception.var_num = CG(context).fast_call_var;
+ zend_stack_push(&CG(loop_var_stack), &discard_exception);
+
CG(zend_lineno) = finally_ast->lineno;
opline = zend_emit_op(NULL, ZEND_FAST_CALL, NULL, NULL);
opline = zend_emit_op(NULL, ZEND_FAST_RET, NULL, NULL);
opline->op1_type = IS_TMP_VAR;
opline->op1.var = CG(context).fast_call_var;
+ opline->op2.num = orig_try_catch_offset;
zend_update_jump_target_to_next(opnum_jmp);
CG(context).fast_call_var = orig_fast_call_var;
+
+ /* Pop DISCARD_EXCEPTION from unwind stack */
+ zend_stack_del_top(&CG(loop_var_stack));
}
+ CG(context).try_catch_offset = orig_try_catch_offset;
+
efree(jmp_opnums);
}
/* }}} */
int backpatch_count;
int in_finally;
uint32_t fast_call_var;
+ uint32_t try_catch_offset;
int current_brk_cont;
int last_brk_cont;
zend_brk_cont_element *brk_cont_array;
#define ZEND_RETURNS_FUNCTION 1<<0
#define ZEND_RETURNS_VALUE 1<<1
-#define ZEND_FAST_RET_TO_CATCH 1
-#define ZEND_FAST_RET_TO_FINALLY 2
-
-#define ZEND_FAST_CALL_FROM_FINALLY 1
-
#define ZEND_ARRAY_ELEMENT_REF (1<<0)
#define ZEND_ARRAY_NOT_PACKED (1<<1)
#define ZEND_ARRAY_SIZE_SHIFT 2
}
}
-static void zend_resolve_fast_call(zend_op_array *op_array, uint32_t op_num)
-{
- int i;
- uint32_t finally_num = (uint32_t)-1;
-
- for (i = 0; i < op_array->last_try_catch; i++) {
- if (op_num >= op_array->try_catch_array[i].finally_op
- && op_num < op_array->try_catch_array[i].finally_end) {
- finally_num = i;
- }
- }
-
- if (finally_num != (uint32_t)-1) {
- /* Must be ZEND_FAST_CALL */
- ZEND_ASSERT(op_array->opcodes[op_array->try_catch_array[finally_num].finally_op - 2].opcode == ZEND_FAST_CALL);
- op_array->opcodes[op_num].extended_value = ZEND_FAST_CALL_FROM_FINALLY;
- }
-}
-
-static void zend_resolve_finally_ret(zend_op_array *op_array, uint32_t op_num)
-{
- int i;
- uint32_t finally_num = (uint32_t)-1;
- uint32_t catch_num = (uint32_t)-1;
-
- for (i = 0; i < op_array->last_try_catch; i++) {
- if (op_array->try_catch_array[i].try_op > op_num) {
- break;
- }
- if (op_num < op_array->try_catch_array[i].finally_op) {
- finally_num = i;
- }
- if (op_num < op_array->try_catch_array[i].catch_op) {
- catch_num = i;
- }
- }
-
- if (finally_num != (uint32_t)-1 &&
- (catch_num == (uint32_t)-1 ||
- op_array->try_catch_array[catch_num].catch_op >=
- op_array->try_catch_array[finally_num].finally_op)) {
- /* in case of unhandled exception return to upward finally block */
- op_array->opcodes[op_num].extended_value = ZEND_FAST_RET_TO_FINALLY;
- op_array->opcodes[op_num].op2.num = finally_num;
- } else if (catch_num != (uint32_t)-1) {
- /* in case of unhandled exception return to upward catch block */
- op_array->opcodes[op_num].extended_value = ZEND_FAST_RET_TO_CATCH;
- op_array->opcodes[op_num].op2.num = catch_num;
- }
-}
-
static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const zend_op *opline) {
int nest_levels = opline->op2.num;
int array_offset = opline->op1.num;
switch (opline->opcode) {
case ZEND_FAST_CALL:
opline->op1.opline_num = op_array->try_catch_array[opline->op1.num].finally_op;
- 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_BRK:
case ZEND_CONT:
{
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
+ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_catch_offset, uint32_t op_num)
+{
+ zend_object *ex = EG(exception);
+
+ /* Walk try/catch/finally structures upwards, performing the necessary actions */
+ while (try_catch_offset != (uint32_t) -1) {
+ zend_try_catch_element *try_catch =
+ &EX(func)->op_array.try_catch_array[try_catch_offset];
+
+ if (op_num < try_catch->catch_op) {
+ /* Go to catch block */
+ cleanup_live_vars(execute_data, op_num, try_catch->catch_op);
+ ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[try_catch->catch_op]);
+ ZEND_VM_CONTINUE();
+
+ } else if (op_num < try_catch->finally_op) {
+ /* Go to finally block */
+ zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
+ cleanup_live_vars(execute_data, op_num, try_catch->finally_op);
+ Z_OBJ_P(fast_call) = EG(exception);
+ EG(exception) = NULL;
+ fast_call->u2.lineno = (uint32_t)-1;
+ ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[try_catch->finally_op]);
+ ZEND_VM_CONTINUE();
+
+ } else if (op_num < try_catch->finally_end) {
+ /* Chain potential exception from wrapping finally block */
+ zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
+ if (Z_OBJ_P(fast_call)) {
+ zend_exception_set_previous(ex, Z_OBJ_P(fast_call));
+ ex = Z_OBJ_P(fast_call);
+ }
+ }
+
+ try_catch_offset--;
+ }
+
+ /* Uncaught exception */
+ cleanup_live_vars(execute_data, op_num, 0);
+ if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
+ zend_generator *generator = zend_get_running_generator(execute_data);
+ zend_generator_close(generator, 1);
+ ZEND_VM_RETURN();
+ } else {
+ ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
+ }
+}
+
ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
{
- uint32_t op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes;
- int i;
- uint32_t catch_op_num = 0, finally_op_num = 0, finally_op_end = 0;
- int in_finally = 0;
+ uint32_t throw_op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes;
+ uint32_t i, current_try_catch_offset = (uint32_t) -1;
{
const zend_op *exc_opline = EG(opline_before_exception);
&& 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.
+ * throw_op_num.
*/
- op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
+ throw_op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
}
}
+ /* Find the innermost try/catch/finally the exception was thrown in */
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) {
+ zend_try_catch_element *try_catch = &EX(func)->op_array.try_catch_array[i];
+ if (try_catch->try_op > throw_op_num) {
/* further blocks will not be relevant... */
break;
}
- in_finally = 0;
- if (op_num < EX(func)->op_array.try_catch_array[i].catch_op) {
- catch_op_num = EX(func)->op_array.try_catch_array[i].catch_op;
- }
- if (op_num < EX(func)->op_array.try_catch_array[i].finally_op) {
- finally_op_num = EX(func)->op_array.try_catch_array[i].finally_op;
- finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end;
- }
- if (op_num >= EX(func)->op_array.try_catch_array[i].finally_op &&
- op_num < EX(func)->op_array.try_catch_array[i].finally_end) {
- finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end;
- in_finally = 1;
+ if (throw_op_num < try_catch->catch_op || throw_op_num < try_catch->finally_end) {
+ current_try_catch_offset = i;
}
}
- cleanup_unfinished_calls(execute_data, op_num);
-
- if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {
- zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var);
-
- cleanup_live_vars(execute_data, op_num, finally_op_num);
- if (in_finally && Z_OBJ_P(fast_call)) {
- zend_exception_set_previous(EG(exception), Z_OBJ_P(fast_call));
- }
- Z_OBJ_P(fast_call) = EG(exception);
- EG(exception) = NULL;
- fast_call->u2.lineno = (uint32_t)-1;
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[finally_op_num]);
- ZEND_VM_CONTINUE();
- } else {
- cleanup_live_vars(execute_data, op_num, catch_op_num);
- if (in_finally) {
- /* we are going out of current finally scope */
- zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var);
+ cleanup_unfinished_calls(execute_data, throw_op_num);
- if (Z_OBJ_P(fast_call)) {
- zend_exception_set_previous(EG(exception), Z_OBJ_P(fast_call));
- Z_OBJ_P(fast_call) = NULL;
- }
- }
- if (catch_op_num) {
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op_num]);
- ZEND_VM_CONTINUE();
- } else if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
- zend_generator *generator = zend_get_running_generator(execute_data);
- zend_generator_close(generator, 1);
- ZEND_VM_RETURN();
- } else {
- ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
- }
- }
+ ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, current_try_catch_offset, op_num, throw_op_num);
}
ZEND_VM_HANDLER(146, ZEND_VERIFY_ABSTRACT_CLASS, ANY, ANY)
/* discard the previously thrown exception */
OBJ_RELEASE(Z_OBJ_P(fast_call));
Z_OBJ_P(fast_call) = NULL;
+ ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_NEXT_OPCODE();
}
-ZEND_VM_HANDLER(162, ZEND_FAST_CALL, JMP_ADDR, ANY, FAST_CALL)
+ZEND_VM_HANDLER(162, ZEND_FAST_CALL, JMP_ADDR, ANY)
{
USE_OPLINE
zval *fast_call = EX_VAR(opline->result.var);
- if (opline->extended_value == ZEND_FAST_CALL_FROM_FINALLY && UNEXPECTED(Z_OBJ_P(fast_call) != NULL)) {
- fast_call->u2.lineno = (uint32_t)-1;
- } else {
- Z_OBJ_P(fast_call) = NULL;
- /* set return address */
- fast_call->u2.lineno = opline - EX(func)->op_array.opcodes;
- }
+ Z_OBJ_P(fast_call) = NULL;
+ /* set return address */
+ fast_call->u2.lineno = opline - EX(func)->op_array.opcodes;
ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline, opline->op1));
ZEND_VM_CONTINUE();
}
-ZEND_VM_HANDLER(163, ZEND_FAST_RET, ANY, TRY_CATCH, FAST_RET)
+ZEND_VM_HANDLER(163, ZEND_FAST_RET, ANY, TRY_CATCH)
{
USE_OPLINE
zval *fast_call = EX_VAR(opline->op1.var);
+ uint32_t current_try_catch_offset, current_op_num;
if (fast_call->u2.lineno != (uint32_t)-1) {
const zend_op *fast_ret = EX(func)->op_array.opcodes + fast_call->u2.lineno;
+
ZEND_VM_SET_OPCODE(fast_ret + 1);
ZEND_VM_CONTINUE();
- } else {
- /* special case for unhandled exceptions */
- USE_OPLINE
-
- if (opline->extended_value == ZEND_FAST_RET_TO_FINALLY) {
- uint32_t finally_op = EX(func)->op_array.try_catch_array[opline->op2.num].finally_op;
- uint32_t finally_end = EX(func)->op_array.try_catch_array[opline->op2.num].finally_end;
- zval *next_fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_end].op1.var);
-
- Z_OBJ_P(next_fast_call) = Z_OBJ_P(fast_call);
- next_fast_call->u2.lineno = (uint32_t)-1;
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, finally_op);
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[finally_op]);
- ZEND_VM_CONTINUE();
- } else {
- EG(exception) = Z_OBJ_P(fast_call);
- Z_OBJ_P(fast_call) = NULL;
- if (opline->extended_value == ZEND_FAST_RET_TO_CATCH) {
- uint32_t catch_op = EX(func)->op_array.try_catch_array[opline->op2.num].catch_op;
-
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, catch_op);
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op]);
- ZEND_VM_CONTINUE();
- } else {
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, 0);
- if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
- zend_generator *generator = zend_get_running_generator(execute_data);
- zend_generator_close(generator, 1);
- ZEND_VM_RETURN();
- } else {
- ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
- }
- }
- }
}
+
+ /* special case for unhandled exceptions */
+ EG(exception) = Z_OBJ_P(fast_call);
+ Z_OBJ_P(fast_call) = NULL;
+ current_try_catch_offset = opline->op2.num;
+ current_op_num = opline - EX(func)->op_array.opcodes;
+ ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, current_try_catch_offset, op_num, current_op_num);
}
ZEND_VM_HANDLER(168, ZEND_BIND_GLOBAL, CV, CONST)
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try_catch_finally_helper_SPEC(uint32_t try_catch_offset, uint32_t op_num ZEND_OPCODE_HANDLER_ARGS_DC)
+{
+ zend_object *ex = EG(exception);
+
+ /* Walk try/catch/finally structures upwards, performing the necessary actions */
+ while (try_catch_offset != (uint32_t) -1) {
+ zend_try_catch_element *try_catch =
+ &EX(func)->op_array.try_catch_array[try_catch_offset];
+
+ if (op_num < try_catch->catch_op) {
+ /* Go to catch block */
+ cleanup_live_vars(execute_data, op_num, try_catch->catch_op);
+ ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[try_catch->catch_op]);
+ ZEND_VM_CONTINUE();
+
+ } else if (op_num < try_catch->finally_op) {
+ /* Go to finally block */
+ zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
+ cleanup_live_vars(execute_data, op_num, try_catch->finally_op);
+ Z_OBJ_P(fast_call) = EG(exception);
+ EG(exception) = NULL;
+ fast_call->u2.lineno = (uint32_t)-1;
+ ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[try_catch->finally_op]);
+ ZEND_VM_CONTINUE();
+
+ } else if (op_num < try_catch->finally_end) {
+ /* Chain potential exception from wrapping finally block */
+ zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
+ if (Z_OBJ_P(fast_call)) {
+ zend_exception_set_previous(ex, Z_OBJ_P(fast_call));
+ ex = Z_OBJ_P(fast_call);
+ }
+ }
+
+ try_catch_offset--;
+ }
+
+ /* Uncaught exception */
+ cleanup_live_vars(execute_data, op_num, 0);
+ if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
+ zend_generator *generator = zend_get_running_generator(execute_data);
+ zend_generator_close(generator, 1);
+ ZEND_VM_RETURN();
+ } else {
+ ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
+ }
+}
+
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;
- int i;
- uint32_t catch_op_num = 0, finally_op_num = 0, finally_op_end = 0;
- int in_finally = 0;
+ uint32_t throw_op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes;
+ uint32_t i, current_try_catch_offset = (uint32_t) -1;
{
const zend_op *exc_opline = EG(opline_before_exception);
&& 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.
+ * throw_op_num.
*/
- op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
+ throw_op_num = EX(func)->op_array.live_range[exc_opline->op2.num].end;
}
}
+ /* Find the innermost try/catch/finally the exception was thrown in */
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) {
+ zend_try_catch_element *try_catch = &EX(func)->op_array.try_catch_array[i];
+ if (try_catch->try_op > throw_op_num) {
/* further blocks will not be relevant... */
break;
}
- in_finally = 0;
- if (op_num < EX(func)->op_array.try_catch_array[i].catch_op) {
- catch_op_num = EX(func)->op_array.try_catch_array[i].catch_op;
- }
- if (op_num < EX(func)->op_array.try_catch_array[i].finally_op) {
- finally_op_num = EX(func)->op_array.try_catch_array[i].finally_op;
- finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end;
- }
- if (op_num >= EX(func)->op_array.try_catch_array[i].finally_op &&
- op_num < EX(func)->op_array.try_catch_array[i].finally_end) {
- finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end;
- in_finally = 1;
+ if (throw_op_num < try_catch->catch_op || throw_op_num < try_catch->finally_end) {
+ current_try_catch_offset = i;
}
}
- cleanup_unfinished_calls(execute_data, op_num);
-
- if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) {
- zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var);
-
- cleanup_live_vars(execute_data, op_num, finally_op_num);
- if (in_finally && Z_OBJ_P(fast_call)) {
- zend_exception_set_previous(EG(exception), Z_OBJ_P(fast_call));
- }
- Z_OBJ_P(fast_call) = EG(exception);
- EG(exception) = NULL;
- fast_call->u2.lineno = (uint32_t)-1;
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[finally_op_num]);
- ZEND_VM_CONTINUE();
- } else {
- cleanup_live_vars(execute_data, op_num, catch_op_num);
- if (in_finally) {
- /* we are going out of current finally scope */
- zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var);
+ cleanup_unfinished_calls(execute_data, throw_op_num);
- if (Z_OBJ_P(fast_call)) {
- zend_exception_set_previous(EG(exception), Z_OBJ_P(fast_call));
- Z_OBJ_P(fast_call) = NULL;
- }
- }
- if (catch_op_num) {
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op_num]);
- ZEND_VM_CONTINUE();
- } else if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
- zend_generator *generator = zend_get_running_generator(execute_data);
- zend_generator_close(generator, 1);
- ZEND_VM_RETURN();
- } else {
- ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
- }
- }
+ ZEND_VM_TAIL_CALL(zend_dispatch_try_catch_finally_helper_SPEC(current_try_catch_offset, throw_op_num ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC));
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_ABSTRACT_CLASS_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
/* discard the previously thrown exception */
OBJ_RELEASE(Z_OBJ_P(fast_call));
Z_OBJ_P(fast_call) = NULL;
+ ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_NEXT_OPCODE();
USE_OPLINE
zval *fast_call = EX_VAR(opline->result.var);
- if (opline->extended_value == ZEND_FAST_CALL_FROM_FINALLY && UNEXPECTED(Z_OBJ_P(fast_call) != NULL)) {
- fast_call->u2.lineno = (uint32_t)-1;
- } else {
- Z_OBJ_P(fast_call) = NULL;
- /* set return address */
- fast_call->u2.lineno = opline - EX(func)->op_array.opcodes;
- }
+ Z_OBJ_P(fast_call) = NULL;
+ /* set return address */
+ fast_call->u2.lineno = opline - EX(func)->op_array.opcodes;
ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline, opline->op1));
ZEND_VM_CONTINUE();
}
{
USE_OPLINE
zval *fast_call = EX_VAR(opline->op1.var);
+ uint32_t current_try_catch_offset, current_op_num;
if (fast_call->u2.lineno != (uint32_t)-1) {
const zend_op *fast_ret = EX(func)->op_array.opcodes + fast_call->u2.lineno;
+
ZEND_VM_SET_OPCODE(fast_ret + 1);
ZEND_VM_CONTINUE();
- } else {
- /* special case for unhandled exceptions */
- USE_OPLINE
-
- if (opline->extended_value == ZEND_FAST_RET_TO_FINALLY) {
- uint32_t finally_op = EX(func)->op_array.try_catch_array[opline->op2.num].finally_op;
- uint32_t finally_end = EX(func)->op_array.try_catch_array[opline->op2.num].finally_end;
- zval *next_fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_end].op1.var);
-
- Z_OBJ_P(next_fast_call) = Z_OBJ_P(fast_call);
- next_fast_call->u2.lineno = (uint32_t)-1;
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, finally_op);
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[finally_op]);
- ZEND_VM_CONTINUE();
- } else {
- EG(exception) = Z_OBJ_P(fast_call);
- Z_OBJ_P(fast_call) = NULL;
- if (opline->extended_value == ZEND_FAST_RET_TO_CATCH) {
- uint32_t catch_op = EX(func)->op_array.try_catch_array[opline->op2.num].catch_op;
-
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, catch_op);
- ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op]);
- ZEND_VM_CONTINUE();
- } else {
- cleanup_live_vars(execute_data, opline - EX(func)->op_array.opcodes, 0);
- if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
- zend_generator *generator = zend_get_running_generator(execute_data);
- zend_generator_close(generator, 1);
- ZEND_VM_RETURN();
- } else {
- ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
- }
- }
- }
}
+
+ /* special case for unhandled exceptions */
+ EG(exception) = Z_OBJ_P(fast_call);
+ Z_OBJ_P(fast_call) = NULL;
+ current_try_catch_offset = opline->op2.num;
+ current_op_num = opline - EX(func)->op_array.opcodes;
+ ZEND_VM_TAIL_CALL(zend_dispatch_try_catch_finally_helper_SPEC(current_try_catch_offset, current_op_num ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC));
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSERT_CHECK_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
"ZEND_VM_EXT_CONST_FETCH" => 0x06000000,
"ZEND_VM_EXT_TYPE" => 0x07000000,
"ZEND_VM_EXT_EVAL" => 0x08000000,
- "ZEND_VM_EXT_FAST_CALL" => 0x09000000,
- "ZEND_VM_EXT_FAST_RET" => 0x0a000000,
+ // unused 0x09000000,
+ // unused 0x0a000000,
"ZEND_VM_EXT_SRC" => 0x0b000000,
"ZEND_VM_EXT_SEND" => 0x0c000000,
"ZEND_VM_NO_CONST_CONST" => 0x40000000,
"ARRAY_INIT" => ZEND_VM_EXT_ARRAY_INIT,
"TYPE" => ZEND_VM_EXT_TYPE,
"EVAL" => ZEND_VM_EXT_EVAL,
- "FAST_CALL" => ZEND_VM_EXT_FAST_CALL,
- "FAST_RET" => ZEND_VM_EXT_FAST_RET,
"ISSET" => ZEND_VM_EXT_ISSET,
"ARG_NUM" => ZEND_VM_EXT_ARG_NUM,
"REF" => ZEND_VM_EXT_REF,
0x00000000,
0x0b000303,
0x00000003,
- 0x09000020,
- 0x0a003000,
+ 0x00000020,
+ 0x00003000,
0x00000010,
0x00000000,
0x00000707,
#define ZEND_VM_EXT_CONST_FETCH 0x06000000
#define ZEND_VM_EXT_TYPE 0x07000000
#define ZEND_VM_EXT_EVAL 0x08000000
-#define ZEND_VM_EXT_FAST_CALL 0x09000000
-#define ZEND_VM_EXT_FAST_RET 0x0a000000
#define ZEND_VM_EXT_SRC 0x0b000000
#define ZEND_VM_EXT_SEND 0x0c000000
#define ZEND_VM_NO_CONST_CONST 0x40000000
zend_op *opline = new_opcodes;
zend_op *end = opline + len;
while (opline < end) {
- if ((opline->opcode == ZEND_FAST_CALL ||
- opline->opcode == ZEND_FAST_RET) &&
- opline->extended_value &&
+ if (opline->opcode == ZEND_FAST_RET &&
+ opline->op2.num != (uint32_t)-1 &&
opline->op2.num < (uint32_t)j) {
opline->op2.num = map[opline->op2.num];
}
if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) {
fprintf(stderr, " %u", op.num);
} else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) {
- if (opline->opcode != ZEND_FAST_RET || opline->extended_value) {
+ if (op.num != (uint32_t)-1) {
fprintf(stderr, " try-catch(%u)", op.num);
}
} else if (ZEND_VM_OP_LIVE_RANGE == (flags & ZEND_VM_OP_MASK)) {
fprintf(stderr, " (\?\?\?)");
break;
}
- } else if (ZEND_VM_EXT_FAST_CALL == (flags & ZEND_VM_EXT_MASK)) {
- if (opline->extended_value == ZEND_FAST_CALL_FROM_FINALLY) {
- fprintf(stderr, " (from-finally)");
- }
- } else if (ZEND_VM_EXT_FAST_RET == (flags & ZEND_VM_EXT_MASK)) {
- if (opline->extended_value == ZEND_FAST_RET_TO_CATCH) {
- fprintf(stderr, " (to-catch)");
- } else if (opline->extended_value == ZEND_FAST_RET_TO_FINALLY) {
- fprintf(stderr, " (to-finally)");
- }
} else if (ZEND_VM_EXT_SRC == (flags & ZEND_VM_EXT_MASK)) {
if (opline->extended_value == ZEND_RETURNS_VALUE) {
fprintf(stderr, " (value)");
} else if (ZEND_VM_OP_NUM == (flags & ZEND_VM_OP_MASK)) {
spprintf(&result, 0, "%" PRIu32, op.num);
} else if (ZEND_VM_OP_TRY_CATCH == (flags & ZEND_VM_OP_MASK)) {
- if (opline->opcode != ZEND_FAST_RET || opline->extended_value) {
+ if (op.num != (uint32_t)-1) {
spprintf(&result, 0, "try-catch(%" PRIu32 ")", op.num);
}
} else if (ZEND_VM_OP_LIVE_RANGE == (flags & ZEND_VM_OP_MASK)) {
uint32_t flags = zend_get_opcode_flags(opline->opcode);
char *result, *decode[4] = {NULL, NULL, NULL, NULL};
- /* EX */
- switch (opline->opcode) {
- case ZEND_FAST_CALL:
- if (opline->extended_value == ZEND_FAST_CALL_FROM_FINALLY) {
- decode[0] = estrdup("FAST_CALL<FROM_FINALLY>");
- }
- break;
- case ZEND_FAST_RET:
- if (opline->extended_value != 0) {
- spprintf(&decode[0], 0, "FAST_RET<%s>",
- opline->extended_value == ZEND_FAST_RET_TO_CATCH ? "TO_CATCH" : "TO_FINALLY");
- }
- break;
- }
-
/* OP1 */
decode[1] = phpdbg_decode_input_op(
ops, opline, opline->op1, opline->op1_type, ZEND_VM_OP1_FLAGS(flags));
00008: }
00009: } catch (Error $e) {
prompt> ok
-[L7 %s FAST_RET<TO_CATCH> ~%d try-catch(0) %s]
+[L7 %s FAST_RET ~%d try-catch(0) %s]
[L9 %s CATCH "Error" $e 1 %s]
>00005: x();
00006: } finally {