Permalink
Browse files

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
  • Loading branch information...
nikic committed Aug 25, 2012
1 parent 4d8edda commit f53225a99ebae56c7a20d6e3ad4efe6772dda3f9
@@ -0,0 +1,17 @@
+--TEST--
+It is not possible to resume an already running generator
+--FILE--
+<?php
+
+function gen() {
+ $gen = yield;
+ $gen->next();
+}
+
+$gen = gen();
+$gen->send($gen);
+$gen->next();
+
+?>
+--EXPECTF--
+Fatal error: Cannot resume an already running generator in %s on line %d
@@ -0,0 +1,29 @@
+--TEST--
+yield cannot be used in a finally block when the generator is force-closed
+--FILE--
+<?php
+
+function gen() {
+ try {
+ echo "before yield\n";
+ yield;
+ echo "after yield\n";
+ } finally {
+ echo "before yield in finally\n";
+ yield;
+ echo "after yield in finally\n";
+ }
+
+ echo "after finally\n";
+}
+
+$gen = gen();
+$gen->rewind();
+unset($gen);
+
+?>
+--EXPECTF--
+before yield
+before yield in finally
+
+Fatal error: Cannot yield from finally in a force-closed generator in %s on line %d
@@ -0,0 +1,43 @@
+--TEST--
+A generator can only be rewinded before or at the first yield
+--FILE--
+<?php
+
+function gen() {
+ echo "before yield\n";
+ yield;
+ echo "after yield\n";
+ yield;
+}
+
+$gen = gen();
+$gen->rewind();
+$gen->rewind();
+$gen->next();
+
+try {
+ $gen->rewind();
+} catch (Exception $e) {
+ echo "\n", $e, "\n\n";
+}
+
+function gen2() {
+ echo "in generator\n";
+
+ if (false) yield;
+}
+
+$gen = gen2();
+$gen->rewind();
+
+?>
+--EXPECTF--
+before yield
+after yield
+
+exception 'Exception' with message 'Cannot rewind a generator that was already run' in %s:%d
+Stack trace:
+#0 %s(%d): Generator->rewind()
+#1 {main}
+
+in generator
@@ -0,0 +1,29 @@
+--TEST--
+yield can be used in finally (apart from forced closes)
+--FILE--
+<?php
+
+function gen() {
+ try {
+ echo "before return\n";
+ return;
+ echo "after return\n";
+ } finally {
+ echo "before yield\n";
+ yield "yielded value";
+ echo "after yield\n";
+ }
+
+ echo "after finally\n";
+}
+
+$gen = gen();
+var_dump($gen->current());
+$gen->next();
+
+?>
+--EXPECTF--
+before return
+before yield
+string(%d) "yielded value"
+after yield
View
@@ -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,14 +61,15 @@ 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;
}
}
}
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) /* {{{ */
View
@@ -22,6 +22,8 @@
#define ZEND_GENERATORS_H
BEGIN_EXTERN_C()
+extern ZEND_API zend_class_entry *zend_ce_generator;
+END_EXTERN_C()
typedef struct _zend_generator {
zend_object std;
@@ -46,17 +48,20 @@ typedef struct _zend_generator {
temp_variable *send_target;
/* Largest used integer key for auto-incrementing keys */
long largest_used_integer_key;
+
+ /* ZEND_GENERATOR_* flags */
+ zend_uchar flags;
} zend_generator;
-extern ZEND_API zend_class_entry *zend_ce_generator;
+static const zend_uchar ZEND_GENERATOR_CURRENTLY_RUNNING = 0x1;
+static const zend_uchar ZEND_GENERATOR_FORCED_CLOSE = 0x2;
+static const zend_uchar ZEND_GENERATOR_AT_FIRST_YIELD = 0x4;
void zend_register_generator_ce(TSRMLS_D);
zval *zend_generator_create_zval(zend_op_array *op_array TSRMLS_DC);
void zend_generator_close(zend_generator *generator, zend_bool finished_execution TSRMLS_DC);
void zend_generator_resume(zend_generator *generator TSRMLS_DC);
-END_EXTERN_C()
-
#endif
/*
View
@@ -5402,6 +5402,10 @@ ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMP|VAR|CV|UNUSE
/* The generator object is stored in return_value_ptr_ptr */
zend_generator *generator = (zend_generator *) EG(return_value_ptr_ptr);
+ if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
+ zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
+ }
+
/* Destroy the previously yielded value */
if (generator->value) {
zval_ptr_dtor(&generator->value);
Oops, something went wrong.

0 comments on commit f53225a

Please sign in to comment.