]> granicus.if.org Git - php/commitdiff
Support partial GC for unfinished generators
authorNikita Popov <nikic@php.net>
Thu, 11 Feb 2016 15:38:30 +0000 (16:38 +0100)
committerNikita Popov <nikic@php.net>
Thu, 11 Feb 2016 15:38:30 +0000 (16:38 +0100)
This doesn't cover everything yet, but should be a good start for
cycled in unfinished generators.

Zend/tests/bug69989_2.phpt [new file with mode: 0644]
Zend/zend_generators.c
Zend/zend_generators.h

diff --git a/Zend/tests/bug69989_2.phpt b/Zend/tests/bug69989_2.phpt
new file mode 100644 (file)
index 0000000..a6f320d
--- /dev/null
@@ -0,0 +1,42 @@
+--TEST--
+Collection of some cycles on unfinished generators
+--FILE--
+<?php
+
+// CV
+function gen1() {
+    $gen = yield;
+    yield;
+}
+
+$gen = gen1();
+$gen->send($gen);
+
+// This
+class Test {
+    public $gen;
+    public function gen2() {
+        yield;
+    }
+}
+
+$test = new Test;
+$test->gen = $test->gen2();
+
+// Closure object
+$gen3 = (function() use (&$gen3) {
+    yield;
+})();
+
+// Yield from array
+function gen4() {
+    yield from [yield];
+}
+
+$gen = gen4();
+$gen->send($gen);
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
index f8087fc943a457162780d66342dc025b5886da61..17e58b9a7b258a4fa02f07ddb957f31b34ba1b32 100644 (file)
@@ -98,6 +98,12 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
                        OBJ_RELEASE((zend_object *) EX(func)->common.prototype);
                }
 
+               /* Free GC buffer. GC for closed generators doesn't need an allocated buffer */
+               if (generator->gc_buffer) {
+                       efree(generator->gc_buffer);
+                       generator->gc_buffer = NULL;
+               }
+
                efree(generator->stack);
                generator->execute_data = NULL;
        }
@@ -188,11 +194,63 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */
 }
 /* }}} */
 
+static uint32_t calc_gc_buffer_size(zend_generator *generator) /* {{{ */
+{
+       uint32_t size = 4; /* value, key, retval, values */
+       if (generator->execute_data) {
+               zend_execute_data *execute_data = generator->execute_data;
+               zend_op_array *op_array = &EX(func)->op_array;
+
+               size += op_array->last_var; /* CVs */
+               size += Z_OBJ(execute_data->This) != NULL; /* $this */
+               size += (EX_CALL_INFO() & ZEND_CALL_CLOSURE) != 0; /* Closure object */
+       }
+       return size;
+}
+/* }}} */
+
 static HashTable *zend_generator_get_gc(zval *object, zval **table, int *n) /* {{{ */
 {
        zend_generator *generator = (zend_generator*) Z_OBJ_P(object);
-       *table = &generator->value;
-       *n = 3;
+       zend_execute_data *execute_data = generator->execute_data;
+       zval *gc_buffer;
+       uint32_t gc_buffer_size;
+
+       if (!execute_data) {
+               /* If the generator has been closed, it can only hold on to three values: The value, key
+                * and retval. These three zvals are stored sequentially starting at &generator->value. */
+               *table = &generator->value;
+               *n = 3;
+               return NULL;
+       }
+
+       gc_buffer_size = calc_gc_buffer_size(generator);
+       if (!generator->gc_buffer) {
+               generator->gc_buffer = safe_emalloc(sizeof(zval), gc_buffer_size, 0);
+       }
+
+       *n = gc_buffer_size;
+       *table = gc_buffer = generator->gc_buffer;
+
+       ZVAL_COPY_VALUE(gc_buffer++, &generator->value);
+       ZVAL_COPY_VALUE(gc_buffer++, &generator->key);
+       ZVAL_COPY_VALUE(gc_buffer++, &generator->retval);
+       ZVAL_COPY_VALUE(gc_buffer++, &generator->values);
+
+       {
+               uint32_t i, num_cvs = EX(func)->op_array.last_var;
+               for (i = 0; i < num_cvs; i++) {
+                       ZVAL_COPY_VALUE(gc_buffer++, EX_VAR_NUM(i));
+               }
+       }
+
+       if (Z_OBJ(execute_data->This)) {
+               ZVAL_OBJ(gc_buffer++, Z_OBJ(execute_data->This));
+       }
+       if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
+               ZVAL_OBJ(gc_buffer++, (zend_object *) EX(func)->common.prototype);
+       }
+
        return NULL;
 }
 /* }}} */
index 26ee646b65908357a1f7b3abc188e22717e4f23f..89f0e3c2fb518abe4a61f07e93ee769abe11d5a3 100644 (file)
@@ -82,7 +82,7 @@ struct _zend_generator {
         * by-value foreach. */
        zval values;
 
-       /* Node of waiting generators when multiple "yield *" expressions
+       /* Node of waiting generators when multiple "yield from" expressions
         * are nested. */
        zend_generator_node node;
 
@@ -91,6 +91,8 @@ struct _zend_generator {
 
        /* ZEND_GENERATOR_* flags */
        zend_uchar flags;
+
+       zval *gc_buffer;
 };
 
 static const zend_uchar ZEND_GENERATOR_CURRENTLY_RUNNING = 0x1;