summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikic@php.net>2012-09-22 19:12:21 +0200
committerNikita Popov <nikic@php.net>2012-09-22 19:15:53 +0200
commita31fa55b44bcb342c00e9ab2f4a851d054897a39 (patch)
treec1dfe040f3d51b7ab97d16094772d0b24e170f3f
parent6c135dff975f111ec5a84af93c1b98e9ae84fcd1 (diff)
downloadphp-git-a31fa55b44bcb342c00e9ab2f4a851d054897a39.tar.gz
Fixed bug #63132
EG(arg_types_stack) is now also backed up when generators are used. This allows the use of yield in nested method calls. This commit adds two new functions to the zend_ptr_stack API: zend_ptr_stack_push_from_memory zend_ptr_stack_pop_into_memory both taking the following arguments: zend_ptr_stack *stack, int count, void **pointers
-rw-r--r--Zend/tests/generators/nested_method_calls.phpt39
-rw-r--r--Zend/zend_generators.c57
-rw-r--r--Zend/zend_generators.h5
-rw-r--r--Zend/zend_ptr_stack.c16
-rw-r--r--Zend/zend_ptr_stack.h2
5 files changed, 119 insertions, 0 deletions
diff --git a/Zend/tests/generators/nested_method_calls.phpt b/Zend/tests/generators/nested_method_calls.phpt
new file mode 100644
index 0000000000..98aee2e60b
--- /dev/null
+++ b/Zend/tests/generators/nested_method_calls.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Yield can be used in nested method calls
+--FILE--
+<?php
+
+class A {
+ function foo() {
+ echo "Called A::foo\n";
+ }
+}
+
+class B {
+ function foo() {
+ echo "Called B::foo\n";
+ }
+}
+
+function gen($obj) {
+ $obj->foo($obj->foo(yield));
+}
+
+$g1 = gen(new A);
+$g1->current();
+
+$g2 = gen(new B);
+$g2->current();
+
+$g1->next();
+
+$g3 = clone $g2;
+unset($g2);
+$g3->next();
+
+?>
+--EXPECT--
+Called A::foo
+Called A::foo
+Called B::foo
+Called B::foo
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c
index 01b33a3a3c..fba62dd83a 100644
--- a/Zend/zend_generators.c
+++ b/Zend/zend_generators.c
@@ -132,6 +132,21 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
efree(generator->backed_up_stack);
}
+ if (generator->backed_up_arg_types_stack) {
+ /* The arg types stack contains three elements per call: fbc, object
+ * and called_scope. Here we traverse the stack from top to bottom
+ * and dtor the object. */
+ int i = generator->backed_up_arg_types_stack_count / 3;
+ while (i--) {
+ zval *object = (zval *) generator->backed_up_arg_types_stack[3*i + 1];
+ if (object) {
+ zval_ptr_dtor(&object);
+ }
+ }
+
+ efree(generator->backed_up_arg_types_stack);
+ }
+
/* We have added an additional stack frame in prev_execute_data, so we
* have to free it. It also contains the arguments passed to the
* generator (for func_get_args) so those have to be freed too. */
@@ -288,6 +303,25 @@ static void zend_generator_clone_storage(zend_generator *orig, zend_generator **
}
}
+ if (orig->backed_up_arg_types_stack) {
+ size_t stack_size = orig->backed_up_arg_types_stack_count * sizeof(void *);
+
+ clone->backed_up_arg_types_stack = emalloc(stack_size);
+ memcpy(clone->backed_up_arg_types_stack, orig->backed_up_arg_types_stack, stack_size);
+
+ /* We have to add refs to the objects in the arg types stack (the
+ * object is always the second element of a three-pack. */
+ {
+ int i, stack_frames = clone->backed_up_arg_types_stack_count / 3;
+ for (i = 0; i < stack_frames; i++) {
+ zval *object = (zval *) clone->backed_up_arg_types_stack[3*i + 1];
+ if (object) {
+ Z_ADDREF_P(object);
+ }
+ }
+ }
+ }
+
/* Update the send_target to use the temporary variable with the same
* offset as the original generator, but in our temporary variable
* memory segment. */
@@ -449,6 +483,7 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
zval *original_This = EG(This);
zend_class_entry *original_scope = EG(scope);
zend_class_entry *original_called_scope = EG(called_scope);
+ int original_arg_types_stack_count = EG(arg_types_stack).top;
/* Remember the current stack position so we can back up pushed args */
generator->original_stack_top = zend_vm_stack_top(TSRMLS_C);
@@ -461,6 +496,16 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
generator->backed_up_stack = NULL;
}
+ if (generator->backed_up_arg_types_stack) {
+ zend_ptr_stack_push_from_memory(
+ &EG(arg_types_stack),
+ generator->backed_up_arg_types_stack_count,
+ generator->backed_up_arg_types_stack
+ );
+ efree(generator->backed_up_arg_types_stack);
+ generator->backed_up_arg_types_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) = (zval **) generator;
@@ -506,6 +551,18 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
zend_vm_stack_free(generator->original_stack_top TSRMLS_CC);
}
+ if (original_arg_types_stack_count != EG(arg_types_stack).top) {
+ generator->backed_up_arg_types_stack_count =
+ EG(arg_types_stack).top - original_arg_types_stack_count;
+
+ generator->backed_up_arg_types_stack = emalloc(generator->backed_up_arg_types_stack_count * sizeof(void *));
+ zend_ptr_stack_pop_into_memory(
+ &EG(arg_types_stack),
+ generator->backed_up_arg_types_stack_count,
+ generator->backed_up_arg_types_stack
+ );
+ }
+
/* If an exception was thrown in the generator we have to internally
* rethrow it in the parent scope. */
if (UNEXPECTED(EG(exception) != NULL)) {
diff --git a/Zend/zend_generators.h b/Zend/zend_generators.h
index e47b7ad885..3dc3e6fecd 100644
--- a/Zend/zend_generators.h
+++ b/Zend/zend_generators.h
@@ -36,6 +36,11 @@ typedef struct _zend_generator {
void *backed_up_stack;
size_t backed_up_stack_size;
+ /* For method calls PHP also pushes various type information on a second
+ * stack, which also needs to be backed up. */
+ void **backed_up_arg_types_stack;
+ int backed_up_arg_types_stack_count;
+
/* The original stack top before resuming the generator. This is required
* for proper cleanup during exception handling. */
void **original_stack_top;
diff --git a/Zend/zend_ptr_stack.c b/Zend/zend_ptr_stack.c
index aefa91f73d..d178cc0184 100644
--- a/Zend/zend_ptr_stack.c
+++ b/Zend/zend_ptr_stack.c
@@ -111,6 +111,22 @@ ZEND_API int zend_ptr_stack_num_elements(zend_ptr_stack *stack)
return stack->top;
}
+ZEND_API void zend_ptr_stack_push_from_memory(zend_ptr_stack *stack, int count, void **pointers)
+{
+ ZEND_PTR_STACK_RESIZE_IF_NEEDED(stack, count);
+
+ memcpy(stack->top_element, pointers, count * sizeof(void *));
+ stack->top_element += count;
+ stack->top += count;
+}
+
+ZEND_API void zend_ptr_stack_pop_into_memory(zend_ptr_stack *stack, int count, void **pointers)
+{
+ memcpy(pointers, stack->top_element - count, count * sizeof(void *));
+ stack->top_element -= count;
+ stack->top -= count;
+}
+
/*
* Local variables:
* tab-width: 4
diff --git a/Zend/zend_ptr_stack.h b/Zend/zend_ptr_stack.h
index 9f6fc13161..fe93e93b5a 100644
--- a/Zend/zend_ptr_stack.h
+++ b/Zend/zend_ptr_stack.h
@@ -41,6 +41,8 @@ ZEND_API void zend_ptr_stack_destroy(zend_ptr_stack *stack);
ZEND_API void zend_ptr_stack_apply(zend_ptr_stack *stack, void (*func)(void *));
ZEND_API void zend_ptr_stack_clean(zend_ptr_stack *stack, void (*func)(void *), zend_bool free_elements);
ZEND_API int zend_ptr_stack_num_elements(zend_ptr_stack *stack);
+ZEND_API void zend_ptr_stack_push_from_memory(zend_ptr_stack *stack, int count, void **pointers);
+ZEND_API void zend_ptr_stack_pop_into_memory(zend_ptr_stack *stack, int count, void **pointers);
END_EXTERN_C()
#define ZEND_PTR_STACK_RESIZE_IF_NEEDED(stack, count) \