Permalink
Browse files

Fix throwing of exceptions within a generator

If a generator threw an exception and was iterated using foreach (i.e. not
manually) an infinite loop was triggered. The reason was that the exception
was not properly rethrown using zend_throw_exception_internal.
  • Loading branch information...
1 parent de80e3c commit 94b2ccae9ce95c4c71bb8db8ce75dcdf26df7d7a @nikic nikic committed Jul 22, 2012
@@ -22,6 +22,6 @@ f3($gen);
?>
--EXPECTF--
#0 f1() called at [%s:%d]
-#1 f2(foo, bar) called at [%s:%d]
+#1 f2(foo, bar)
#2 Generator->rewind() called at [%s:%d]
#3 f3(Generator Object ()) called at [%s:%d]
@@ -0,0 +1,20 @@
+--TEST--
+Exceptions throwing by generators during foreach iteration are properly handled
+--FILE--
+<?php
+
+function gen() {
+ throw new Exception("foo");
+ yield; // force generator
+}
+
+foreach (gen() as $value) { }
+
+?>
+--EXPECTF--
+Fatal error: Uncaught exception 'Exception' with message 'foo' in %s:%d
+Stack trace:
+#0 %s(%d): gen()
+#1 {main}
+ thrown in %s on line %d
+
View
@@ -21,6 +21,7 @@
#include "zend.h"
#include "zend_API.h"
#include "zend_interfaces.h"
+#include "zend_exceptions.h"
#include "zend_generators.h"
ZEND_API zend_class_entry *zend_ce_generator;
@@ -352,12 +353,11 @@ zval *zend_generator_create_zval(zend_op_array *op_array TSRMLS_DC) /* {{{ */
* up in backtraces and func_get_all() can access the function
* arguments. */
execute_data->prev_execute_data = emalloc(sizeof(zend_execute_data));
+ memset(execute_data->prev_execute_data, 0, sizeof(zend_execute_data));
+ execute_data->prev_execute_data->function_state.function = (zend_function *) op_array;
if (EG(current_execute_data)) {
- memcpy(execute_data->prev_execute_data, EG(current_execute_data), sizeof(zend_execute_data));
execute_data->prev_execute_data->function_state.arguments = zend_copy_arguments(EG(current_execute_data)->function_state.arguments);
} else {
- memset(execute_data->prev_execute_data, 0, sizeof(zend_execute_data));
- execute_data->prev_execute_data->function_state.function = (zend_function *) op_array;
execute_data->prev_execute_data->function_state.arguments = NULL;
}
@@ -450,6 +450,12 @@ static void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
memcpy(generator->backed_up_stack, generator->original_stack_top, generator->backed_up_stack_size);
zend_vm_stack_free(generator->original_stack_top TSRMLS_CC);
}
+
+ /* If an exception was thrown in the generator we have to internally
+ * rethrow it in the parent scope. */
+ if (UNEXPECTED(EG(exception) != NULL)) {
+ zend_throw_exception_internal(NULL TSRMLS_CC);
+ }
}
}
/* }}} */
View
@@ -5088,7 +5088,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
EX(old_error_reporting) = NULL;
if (!catched) {
- /* For generators skip the leave handler return directly */
+ /* For generators skip the leave handler and return directly */
if (EX(op_array)->fn_flags & ZEND_ACC_GENERATOR) {
/* The generator object is stored in return_value_ptr_ptr */
zend_generator *generator = (zend_generator *) EG(return_value_ptr_ptr);
View
@@ -1151,7 +1151,7 @@ static int ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER
EX(old_error_reporting) = NULL;
if (!catched) {
- /* For generators skip the leave handler return directly */
+ /* For generators skip the leave handler and return directly */
if (EX(op_array)->fn_flags & ZEND_ACC_GENERATOR) {
/* The generator object is stored in return_value_ptr_ptr */
zend_generator *generator = (zend_generator *) EG(return_value_ptr_ptr);

0 comments on commit 94b2cca

Please sign in to comment.