summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-12-20 17:46:43 +0100
committerNikita Popov <nikita.ppv@gmail.com>2019-12-20 17:46:43 +0100
commit1d6325fb017236a3765ed2bcb306039576cbb189 (patch)
tree4ef2a8b50d8021525adcb6c517c7a884d444df8a /Zend
parentd9eb97d745cb7b91f7a262b6ef3f3cb5051e5a63 (diff)
parentb829ea5f7410ae570cc8b06c11a466344dd7fd45 (diff)
downloadphp-git-1d6325fb017236a3765ed2bcb306039576cbb189.tar.gz
Merge branch 'PHP-7.4'
* PHP-7.4: Fix leak when generator closed during yield in finally
Diffstat (limited to 'Zend')
-rw-r--r--Zend/tests/generators/yield_in_finally_cleanup.phpt51
-rw-r--r--Zend/zend_generators.c67
2 files changed, 95 insertions, 23 deletions
diff --git a/Zend/tests/generators/yield_in_finally_cleanup.phpt b/Zend/tests/generators/yield_in_finally_cleanup.phpt
new file mode 100644
index 0000000000..9a76261e0d
--- /dev/null
+++ b/Zend/tests/generators/yield_in_finally_cleanup.phpt
@@ -0,0 +1,51 @@
+--TEST--
+Free pending exceptions / return values on clone on yield in finally
+--FILE--
+<?php
+function gen1() {
+ try {
+ throw new Exception();
+ } finally {
+ yield;
+ }
+}
+function gen2() {
+ try {
+ $bar = "bar";
+ return "foo" . $bar;
+ } finally {
+ yield;
+ }
+}
+function gen3() {
+ try {
+ throw new Exception();
+ } finally {
+ try {
+ $bar = "bar";
+ return "foo" . $bar;
+ } finally {
+ yield;
+ }
+ }
+}
+function gen4() {
+ try {
+ try {
+ $bar = "bar";
+ return "foo" . $bar;
+ } finally {
+ yield;
+ }
+ } finally {
+ }
+}
+gen1()->rewind();
+gen2()->rewind();
+gen3()->rewind();
+gen4()->rewind();
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index d086892c3a..068e1ceb13 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -96,9 +96,10 @@ ZEND_API zend_execute_data* zend_generator_freeze_call_stack(zend_execute_data *
static void zend_generator_cleanup_unfinished_execution(
zend_generator *generator, zend_execute_data *execute_data, uint32_t catch_op_num) /* {{{ */
{
- if (execute_data->opline != execute_data->func->op_array.opcodes) {
+ zend_op_array *op_array = &execute_data->func->op_array;
+ if (execute_data->opline != op_array->opcodes) {
/* -1 required because we want the last run opcode, not the next to-be-run one. */
- uint32_t op_num = execute_data->opline - execute_data->func->op_array.opcodes - 1;
+ uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
if (UNEXPECTED(generator->frozen_call_stack)) {
/* Temporarily restore generator->execute_data if it has been NULLed out already. */
@@ -107,6 +108,7 @@ static void zend_generator_cleanup_unfinished_execution(
zend_generator_restore_call_stack(generator);
generator->execute_data = save_ex;
}
+
zend_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
}
}
@@ -167,7 +169,7 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
{
zend_generator *generator = (zend_generator*) object;
zend_execute_data *ex = generator->execute_data;
- uint32_t op_num, finally_op_num, finally_op_end;
+ uint32_t op_num, try_catch_offset;
int i;
/* leave yield from mode to properly allow finally execution */
@@ -195,38 +197,57 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
/* -1 required because we want the last run opcode, not the
* next to-be-run one. */
op_num = ex->opline - ex->func->op_array.opcodes - 1;
+ try_catch_offset = -1;
- /* Find next finally block */
- finally_op_num = 0;
- finally_op_end = 0;
+ /* Find the innermost try/catch that we are inside of. */
for (i = 0; i < ex->func->op_array.last_try_catch; i++) {
zend_try_catch_element *try_catch = &ex->func->op_array.try_catch_array[i];
-
if (op_num < try_catch->try_op) {
break;
}
-
- if (op_num < try_catch->finally_op) {
- finally_op_num = try_catch->finally_op;
- finally_op_end = try_catch->finally_end;
+ if (op_num < try_catch->catch_op || op_num < try_catch->finally_end) {
+ try_catch_offset = i;
}
}
- /* If a finally block was found we jump directly to it and
- * resume the generator. */
- if (finally_op_num) {
- zval *fast_call;
+ /* 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];
- zend_generator_cleanup_unfinished_execution(generator, ex, finally_op_num);
+ if (op_num < try_catch->finally_op) {
+ /* Go to finally block */
+ zval *fast_call =
+ ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[try_catch->finally_end].op1.var);
- fast_call = ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[finally_op_end].op1.var);
- Z_OBJ_P(fast_call) = EG(exception);
- EG(exception) = NULL;
- Z_OPLINE_NUM_P(fast_call) = (uint32_t)-1;
+ zend_generator_cleanup_unfinished_execution(generator, ex, try_catch->finally_op);
+ Z_OBJ_P(fast_call) = EG(exception);
+ EG(exception) = NULL;
+ Z_OPLINE_NUM_P(fast_call) = (uint32_t)-1;
- ex->opline = &ex->func->op_array.opcodes[finally_op_num];
- generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
- zend_generator_resume(generator);
+ ex->opline = &ex->func->op_array.opcodes[try_catch->finally_op];
+ generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
+ zend_generator_resume(generator);
+
+ /* TODO: If we hit another yield inside try/finally,
+ * should we also jump to the next finally block? */
+ return;
+ } else if (op_num < try_catch->finally_end) {
+ zval *fast_call =
+ ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[try_catch->finally_end].op1.var);
+ /* Clean up incomplete return statement */
+ if (Z_OPLINE_NUM_P(fast_call) != (uint32_t) -1) {
+ zend_op *retval_op = &ex->func->op_array.opcodes[Z_OPLINE_NUM_P(fast_call)];
+ if (retval_op->op2_type & (IS_TMP_VAR | IS_VAR)) {
+ zval_ptr_dtor(ZEND_CALL_VAR(ex, retval_op->op2.var));
+ }
+ }
+ /* Clean up backed-up exception */
+ if (Z_OBJ_P(fast_call)) {
+ OBJ_RELEASE(Z_OBJ_P(fast_call));
+ }
+ }
+
+ try_catch_offset--;
}
}
/* }}} */