Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Allow yielding during function calls

During function calls arguments are pushed onto the stack. Now these are
backed up on yield and restored on resume. This requires memcpy'ing them,
but there doesn't seem to be any better way to do it.

Also this fixes the issue with exceptions thrown during function calls.
  • Loading branch information...
commit ee89e228f6f684555dd219d8a46d173cfed3230a 1 parent 0033a52
Nikita Popov nikic authored
32 Zend/tests/generators/generator_throwing_during_function_call.phpt
View
@@ -0,0 +1,32 @@
+--TEST--
+Stack is cleaned up properly when an exception is thrown during a function call
+--FILE--
+<?php
+
+function throwException() {
+ throw new Exception('test');
+}
+
+function *gen() {
+ yield 'foo';
+ strlen("foo", "bar", throwException());
+ yield 'bar';
+}
+
+$gen = gen();
+
+var_dump($gen->current());
+
+try {
+ $gen->next();
+} catch (Exception $e) {
+ echo 'Caught exception with message "', $e->getMessage(), '"', "\n";
+}
+
+var_dump($gen->current());
+
+?>
+--EXPECT--
+string(3) "foo"
+Caught exception with message "test"
+NULL
15 Zend/tests/generators/yield_during_function_call.phpt
View
@@ -0,0 +1,15 @@
+--TEST--
+"yield" can occur during a function call
+--FILE--
+<?php
+
+function *gen() {
+ var_dump(str_repeat("x", yield));
+}
+
+$gen = gen();
+$gen->send(10);
+
+?>
+--EXPECT--
+string(10) "xxxxxxxxxx"
33 Zend/zend_generators.c
View
@@ -83,6 +83,19 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
}
}
+ /* Clear any backed up stack arguments */
+ if (generator->backed_up_stack) {
+ zval **zvals = (zval **) generator->backed_up_stack;
+ size_t zval_num = generator->backed_up_stack_size / sizeof(zval *);
+ int i;
+
+ for (i = 0; i < zval_num; i++) {
+ zval_ptr_dtor(&zvals[i]);
+ }
+
+ efree(generator->backed_up_stack);
+ }
+
efree(execute_data);
generator->execute_data = NULL;
}
@@ -158,6 +171,17 @@ static void zend_generator_resume(zval *object, zend_generator *generator TSRMLS
zend_class_entry *original_scope = EG(scope);
zend_class_entry *original_called_scope = EG(called_scope);
+ /* Remember the current stack position so we can back up pushed args */
+ generator->original_stack_top = zend_vm_stack_top(TSRMLS_C);
+
+ /* If there is a backed up stack copy it to the VM stack */
+ if (generator->backed_up_stack) {
+ void *stack = zend_vm_stack_alloc(generator->backed_up_stack_size TSRMLS_CC);
+ memcpy(stack, generator->backed_up_stack, generator->backed_up_stack_size);
+ efree(generator->backed_up_stack);
+ generator->backed_up_stack = NULL;
+ }
+
/* We (mis)use the return_value_ptr_ptr to provide the generator object
* to the executor, so YIELD will be able to set the yielded value */
EG(return_value_ptr_ptr) = &object;
@@ -186,6 +210,15 @@ static void zend_generator_resume(zval *object, zend_generator *generator TSRMLS
EG(This) = original_This;
EG(scope) = original_scope;
EG(called_scope) = original_called_scope;
+
+ /* The stack top before and after the execution differ, i.e. there are
+ * arguments pushed to the stack. */
+ if (generator->original_stack_top != zend_vm_stack_top(TSRMLS_C)) {
+ generator->backed_up_stack_size = (zend_vm_stack_top(TSRMLS_C) - generator->original_stack_top) * sizeof(void *);
+ generator->backed_up_stack = emalloc(generator->backed_up_stack_size);
+ memcpy(generator->backed_up_stack, generator->original_stack_top, generator->backed_up_stack_size);
+ zend_vm_stack_free(generator->original_stack_top TSRMLS_CC);
+ }
}
}
/* }}} */
10 Zend/zend_generators.h
View
@@ -28,6 +28,16 @@ typedef struct _zend_generator {
/* The suspended execution context. */
zend_execute_data *execute_data;
+
+ /* If the execution is suspended during a function call there may be
+ * arguments pushed to the stack, so it has to be backed up. */
+ void *backed_up_stack;
+ size_t backed_up_stack_size;
+
+ /* The original stack top before resuming the generator. This is required
+ * for proper cleanup during exception handling. */
+ void **original_stack_top;
+
/* Current value */
zval *value;
/* Current key */
11 Zend/zend_vm_def.h
View
@@ -4997,11 +4997,12 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY)
/* Figure out where the next stack frame (which maybe contains pushed
* arguments that have to be dtor'ed) starts */
if (EX(op_array)->fn_flags & ZEND_ACC_GENERATOR) {
- /* For generators the execution context is not stored on the stack so
- * I don't know yet how to figure out where the next stack frame
- * starts. For now I'll just use the stack top to ignore argument
- * dtor'ing altogether. */
- stack_frame = zend_vm_stack_top(TSRMLS_C);
+ /* The generator object is stored in return_value_ptr_ptr */
+ zend_generator *generator = (zend_generator *) zend_object_store_get_object(*EG(return_value_ptr_ptr) TSRMLS_CC);
+
+ /* For generators the next stack frame is conveniently stored in the
+ * generator object. */
+ stack_frame = generator->original_stack_top;
} else {
/* In all other cases the next stack frame starts after the temporary
* variables section of the current execution context */
11 Zend/zend_vm_execute.h
View
@@ -1060,11 +1060,12 @@ static int ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER
/* Figure out where the next stack frame (which maybe contains pushed
* arguments that have to be dtor'ed) starts */
if (EX(op_array)->fn_flags & ZEND_ACC_GENERATOR) {
- /* For generators the execution context is not stored on the stack so
- * I don't know yet how to figure out where the next stack frame
- * starts. For now I'll just use the stack top to ignore argument
- * dtor'ing altogether. */
- stack_frame = zend_vm_stack_top(TSRMLS_C);
+ /* The generator object is stored in return_value_ptr_ptr */
+ zend_generator *generator = (zend_generator *) zend_object_store_get_object(*EG(return_value_ptr_ptr) TSRMLS_CC);
+
+ /* For generators the next stack frame is conveniently stored in the
+ * generator object. */
+ stack_frame = generator->original_stack_top;
} else {
/* In all other cases the next stack frame starts after the temporary
* variables section of the current execution context */
Please sign in to comment.
Something went wrong with that request. Please try again.