Commit:    a31fa55b44bcb342c00e9ab2f4a851d054897a39
Author:    Nikita Popov <ni...@php.net>         Sat, 22 Sep 2012 19:12:21 +0200
Parents:   6c135dff975f111ec5a84af93c1b98e9ae84fcd1
Branches:  master

Link:       
http://git.php.net/?p=php-src.git;a=commitdiff;h=a31fa55b44bcb342c00e9ab2f4a851d054897a39

Log:
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

Bugs:
https://bugs.php.net/63132

Changed paths:
  A  Zend/tests/generators/nested_method_calls.phpt
  M  Zend/zend_generators.c
  M  Zend/zend_generators.h
  M  Zend/zend_ptr_stack.c
  M  Zend/zend_ptr_stack.h


Diff:
diff --git a/Zend/tests/generators/nested_method_calls.phpt 
b/Zend/tests/generators/nested_method_calls.phpt
new file mode 100644
index 0000000..98aee2e
--- /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 01b33a3..fba62dd 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 e47b7ad..3dc3e6f 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 aefa91f..d178cc0 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 9f6fc13..fe93e93 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)          \


--
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to