diff options
author | Nikita Popov <nikic@php.net> | 2012-09-22 19:12:21 +0200 |
---|---|---|
committer | Nikita Popov <nikic@php.net> | 2012-09-22 19:15:53 +0200 |
commit | a31fa55b44bcb342c00e9ab2f4a851d054897a39 (patch) | |
tree | c1dfe040f3d51b7ab97d16094772d0b24e170f3f | |
parent | 6c135dff975f111ec5a84af93c1b98e9ae84fcd1 (diff) | |
download | php-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.phpt | 39 | ||||
-rw-r--r-- | Zend/zend_generators.c | 57 | ||||
-rw-r--r-- | Zend/zend_generators.h | 5 | ||||
-rw-r--r-- | Zend/zend_ptr_stack.c | 16 | ||||
-rw-r--r-- | Zend/zend_ptr_stack.h | 2 |
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) \ |