summaryrefslogtreecommitdiff
path: root/Zend/zend_generators.c
diff options
context:
space:
mode:
authorNikita Popov <nikic@php.net>2012-08-25 17:40:08 +0200
committerNikita Popov <nikic@php.net>2012-08-25 17:40:08 +0200
commitf53225a99ebae56c7a20d6e3ad4efe6772dda3f9 (patch)
tree8b77248bca9fb041abb14947a77fb4eada624783 /Zend/zend_generators.c
parent4d8edda341efef1901365f10213c027e745ac7ab (diff)
downloadphp-git-f53225a99ebae56c7a20d6e3ad4efe6772dda3f9.tar.gz
Fix several issues and allow rewind only at/before first yield
* Trying to resume a generator while it is already running now throws a fatal error. * Trying to use yield in finally while the generator is being force-closed (by GC) throws a fatal error. * Rewinding after the first yield now throws an Exception
Diffstat (limited to 'Zend/zend_generators.c')
-rw-r--r--Zend/zend_generators.c49
1 files changed, 39 insertions, 10 deletions
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index 3170ec9c33..03294f7f0e 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -31,11 +31,13 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
{
if (generator->execute_data) {
zend_execute_data *execute_data = generator->execute_data;
+ zend_op_array *op_array = execute_data->op_array;
if (!finished_execution) {
- zend_op_array *op_array = execute_data->op_array;
if (op_array->has_finally_block) {
- zend_uint op_num = execute_data->opline - op_array->opcodes;
+ /* -1 required because we want the last run opcode, not the
+ * next to-be-run one. */
+ zend_uint op_num = execute_data->opline - op_array->opcodes - 1;
zend_uint finally_op_num = 0;
/* Find next finally block */
@@ -59,6 +61,7 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
if (finally_op_num) {
execute_data->opline = &op_array->opcodes[finally_op_num];
execute_data->leaving = ZEND_RETURN;
+ generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
zend_generator_resume(generator TSRMLS_CC);
return;
}
@@ -66,7 +69,7 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
}
if (!execute_data->symbol_table) {
- zend_free_compiled_variables(execute_data->CVs, execute_data->op_array->last_var);
+ zend_free_compiled_variables(execute_data->CVs, op_array->last_var);
} else {
zend_clean_and_cache_symbol_table(execute_data->symbol_table TSRMLS_CC);
}
@@ -83,8 +86,9 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
* a return statement) we have to free loop variables manually, as
* we don't know whether the SWITCH_FREE / FREE opcodes have run */
if (!finished_execution) {
- zend_op_array *op_array = execute_data->op_array;
- zend_uint op_num = execute_data->opline - op_array->opcodes;
+ /* -1 required because we want the last run opcode, not the
+ * next to-be-run one. */
+ zend_uint op_num = execute_data->opline - op_array->opcodes - 1;
int i;
for (i = 0; i < op_array->last_brk_cont; ++i) {
@@ -411,6 +415,13 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
return;
}
+ if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
+ zend_error(E_ERROR, "Cannot resume an already running generator");
+ }
+
+ /* Drop the AT_FIRST_YIELD flag */
+ generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
+
{
/* Backup executor globals */
zval **original_return_value_ptr_ptr = EG(return_value_ptr_ptr);
@@ -455,7 +466,9 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
generator->execute_data->prev_execute_data->prev_execute_data = original_execute_data;
/* Resume execution */
+ generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
execute_ex(generator->execute_data TSRMLS_CC);
+ generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
/* Restore executor globals */
EG(return_value_ptr_ptr) = original_return_value_ptr_ptr;
@@ -489,6 +502,17 @@ static void zend_generator_ensure_initialized(zend_generator *generator TSRMLS_D
{
if (!generator->value) {
zend_generator_resume(generator TSRMLS_CC);
+ generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
+ }
+}
+/* }}} */
+
+static void zend_generator_rewind(zend_generator *generator TSRMLS_DC) /* {{{ */
+{
+ zend_generator_ensure_initialized(generator TSRMLS_CC);
+
+ if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
+ zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0 TSRMLS_CC);
}
}
/* }}} */
@@ -505,10 +529,7 @@ ZEND_METHOD(Generator, rewind)
generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
- zend_generator_ensure_initialized(generator TSRMLS_CC);
-
- /* Generators aren't rewindable, so rewind() only has to make sure that
- * the generator is initialized, nothing more */
+ zend_generator_rewind(generator TSRMLS_CC);
}
/* }}} */
@@ -721,13 +742,21 @@ static void zend_generator_iterator_move_forward(zend_object_iterator *iterator
}
/* }}} */
+static void zend_generator_iterator_rewind(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
+{
+ zend_generator *generator = (zend_generator *) iterator->data;
+
+ zend_generator_rewind(generator TSRMLS_CC);
+}
+/* }}} */
+
static zend_object_iterator_funcs zend_generator_iterator_functions = {
zend_generator_iterator_dtor,
zend_generator_iterator_valid,
zend_generator_iterator_get_data,
zend_generator_iterator_get_key,
zend_generator_iterator_move_forward,
- NULL
+ zend_generator_iterator_rewind
};
zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) /* {{{ */