Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit a31fa55b44bcb342c00e9ab2f4a851d054897a39 1 parent 6c135df
Nikita Popov authored September 22, 2012
39  Zend/tests/generators/nested_method_calls.phpt
... ...
@@ -0,0 +1,39 @@
  1
+--TEST--
  2
+Yield can be used in nested method calls
  3
+--FILE--
  4
+<?php
  5
+
  6
+class A {
  7
+    function foo() {
  8
+        echo "Called A::foo\n";
  9
+    }
  10
+}
  11
+
  12
+class B {
  13
+    function foo() {
  14
+        echo "Called B::foo\n";
  15
+    }
  16
+}
  17
+
  18
+function gen($obj) {
  19
+    $obj->foo($obj->foo(yield));
  20
+}
  21
+
  22
+$g1 = gen(new A);
  23
+$g1->current();
  24
+
  25
+$g2 = gen(new B);
  26
+$g2->current();
  27
+
  28
+$g1->next();
  29
+
  30
+$g3 = clone $g2;
  31
+unset($g2);
  32
+$g3->next();
  33
+
  34
+?>
  35
+--EXPECT--
  36
+Called A::foo
  37
+Called A::foo
  38
+Called B::foo
  39
+Called B::foo
57  Zend/zend_generators.c
@@ -132,6 +132,21 @@ void zend_generator_close(zend_generator *generator, zend_bool finished_executio
132 132
 			efree(generator->backed_up_stack);
133 133
 		}
134 134
 
  135
+		if (generator->backed_up_arg_types_stack) {
  136
+			/* The arg types stack contains three elements per call: fbc, object
  137
+			 * and called_scope. Here we traverse the stack from top to bottom
  138
+			 * and dtor the object. */
  139
+			int i = generator->backed_up_arg_types_stack_count / 3;
  140
+			while (i--) {
  141
+				zval *object = (zval *) generator->backed_up_arg_types_stack[3*i + 1];
  142
+				if (object) {
  143
+					zval_ptr_dtor(&object);
  144
+				}
  145
+			}
  146
+
  147
+			efree(generator->backed_up_arg_types_stack);
  148
+		} 
  149
+
135 150
 		/* We have added an additional stack frame in prev_execute_data, so we
136 151
 		 * have to free it. It also contains the arguments passed to the
137 152
 		 * 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 **
288 303
 			}
289 304
 		}
290 305
 
  306
+		if (orig->backed_up_arg_types_stack) {
  307
+			size_t stack_size = orig->backed_up_arg_types_stack_count * sizeof(void *);
  308
+
  309
+			clone->backed_up_arg_types_stack = emalloc(stack_size);
  310
+			memcpy(clone->backed_up_arg_types_stack, orig->backed_up_arg_types_stack, stack_size);
  311
+
  312
+			/* We have to add refs to the objects in the arg types stack (the
  313
+			 * object is always the second element of a three-pack. */
  314
+			{
  315
+				int i, stack_frames = clone->backed_up_arg_types_stack_count / 3;
  316
+				for (i = 0; i < stack_frames; i++) {
  317
+					zval *object = (zval *) clone->backed_up_arg_types_stack[3*i + 1];
  318
+					if (object) {
  319
+						Z_ADDREF_P(object);
  320
+					}
  321
+				}
  322
+			}
  323
+		}
  324
+
291 325
 		/* Update the send_target to use the temporary variable with the same
292 326
 		 * offset as the original generator, but in our temporary variable
293 327
 		 * memory segment. */
@@ -449,6 +483,7 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
449 483
 		zval *original_This = EG(This);
450 484
 		zend_class_entry *original_scope = EG(scope);
451 485
 		zend_class_entry *original_called_scope = EG(called_scope);
  486
+		int original_arg_types_stack_count = EG(arg_types_stack).top;
452 487
 
453 488
 		/* Remember the current stack position so we can back up pushed args */
454 489
 		generator->original_stack_top = zend_vm_stack_top(TSRMLS_C);
@@ -461,6 +496,16 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
461 496
 			generator->backed_up_stack = NULL;
462 497
 		}
463 498
 
  499
+		if (generator->backed_up_arg_types_stack) {
  500
+			zend_ptr_stack_push_from_memory(
  501
+				&EG(arg_types_stack),
  502
+				generator->backed_up_arg_types_stack_count,
  503
+				generator->backed_up_arg_types_stack
  504
+			);
  505
+			efree(generator->backed_up_arg_types_stack);
  506
+			generator->backed_up_arg_types_stack = NULL;
  507
+		}
  508
+
464 509
 		/* We (mis)use the return_value_ptr_ptr to provide the generator object
465 510
 		 * to the executor, so YIELD will be able to set the yielded value */
466 511
 		EG(return_value_ptr_ptr) = (zval **) generator;
@@ -506,6 +551,18 @@ void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
506 551
 			zend_vm_stack_free(generator->original_stack_top TSRMLS_CC);
507 552
 		}
508 553
 
  554
+		if (original_arg_types_stack_count != EG(arg_types_stack).top) {
  555
+			generator->backed_up_arg_types_stack_count =
  556
+				EG(arg_types_stack).top - original_arg_types_stack_count;
  557
+
  558
+			generator->backed_up_arg_types_stack = emalloc(generator->backed_up_arg_types_stack_count * sizeof(void *));
  559
+			zend_ptr_stack_pop_into_memory(
  560
+				&EG(arg_types_stack),
  561
+				generator->backed_up_arg_types_stack_count,
  562
+				generator->backed_up_arg_types_stack
  563
+			);
  564
+		}
  565
+
509 566
 		/* If an exception was thrown in the generator we have to internally
510 567
 		 * rethrow it in the parent scope. */
511 568
 		if (UNEXPECTED(EG(exception) != NULL)) {
5  Zend/zend_generators.h
@@ -36,6 +36,11 @@ typedef struct _zend_generator {
36 36
 	void *backed_up_stack;
37 37
 	size_t backed_up_stack_size;
38 38
 
  39
+	/* For method calls PHP also pushes various type information on a second
  40
+	 * stack, which also needs to be backed up. */
  41
+	void **backed_up_arg_types_stack;
  42
+	int backed_up_arg_types_stack_count;
  43
+
39 44
 	/* The original stack top before resuming the generator. This is required
40 45
 	 * for proper cleanup during exception handling. */
41 46
 	void **original_stack_top;
16  Zend/zend_ptr_stack.c
@@ -111,6 +111,22 @@ ZEND_API int zend_ptr_stack_num_elements(zend_ptr_stack *stack)
111 111
 	return stack->top;
112 112
 }
113 113
 
  114
+ZEND_API void zend_ptr_stack_push_from_memory(zend_ptr_stack *stack, int count, void **pointers)
  115
+{
  116
+	ZEND_PTR_STACK_RESIZE_IF_NEEDED(stack, count);
  117
+
  118
+	memcpy(stack->top_element, pointers, count * sizeof(void *));
  119
+	stack->top_element += count;
  120
+	stack->top += count;
  121
+}
  122
+
  123
+ZEND_API void zend_ptr_stack_pop_into_memory(zend_ptr_stack *stack, int count, void **pointers)
  124
+{
  125
+	memcpy(pointers, stack->top_element - count, count * sizeof(void *));
  126
+	stack->top_element -= count;
  127
+	stack->top -= count;
  128
+}
  129
+
114 130
 /*
115 131
  * Local variables:
116 132
  * tab-width: 4
2  Zend/zend_ptr_stack.h
@@ -41,6 +41,8 @@ ZEND_API void zend_ptr_stack_destroy(zend_ptr_stack *stack);
41 41
 ZEND_API void zend_ptr_stack_apply(zend_ptr_stack *stack, void (*func)(void *));
42 42
 ZEND_API void zend_ptr_stack_clean(zend_ptr_stack *stack, void (*func)(void *), zend_bool free_elements);
43 43
 ZEND_API int zend_ptr_stack_num_elements(zend_ptr_stack *stack);
  44
+ZEND_API void zend_ptr_stack_push_from_memory(zend_ptr_stack *stack, int count, void **pointers);
  45
+ZEND_API void zend_ptr_stack_pop_into_memory(zend_ptr_stack *stack, int count, void **pointers);
44 46
 END_EXTERN_C()
45 47
 
46 48
 #define ZEND_PTR_STACK_RESIZE_IF_NEEDED(stack, count)		\

0 notes on commit a31fa55

Xinchen Hui

Why a new function? Cannot use the n_push APIs?

Nikita Popov

The n_push APIs are varargs functions, they can't be used to push a variable number of pointers programatically (or can they?)

Please sign in to comment.
Something went wrong with that request. Please try again.