Added yield from operator

This commit is contained in:
Bob Weinand 2015-03-07 00:28:12 +01:00
parent fcdb6e0811
commit b4a142ab97
20 changed files with 2436 additions and 1221 deletions

View File

@ -0,0 +1,59 @@
--TEST--
Exceptions in linear yield from setup
--FILE--
<?php
function from($off) {
try {
yield $off + 1;
} catch (Exception $e) { print "catch in from()\n$e\n"; }
yield $off + 2;
}
function gen() {
try {
yield "gen" => 0;
} catch (Exception $e) { print "catch in gen()\n$e\n"; }
try {
yield from from(0);
} catch (Exception $e) { print "catch in gen()\n$e\n"; }
yield from from(2);
}
$i = 0;
try {
for ($gen = gen(); $gen->valid(); $gen->throw(new Exception((string) $i++))) {
var_dump($gen->current());
}
} catch (Exception $e) { print "catch in {main}\n$e\n"; }
var_dump($gen->valid());
?>
--EXPECTF--
int(0)
catch in gen()
exception 'Exception' with message '0' in %s:%d
Stack trace:
#0 {main}
int(1)
catch in from()
exception 'Exception' with message '1' in %s:%d
Stack trace:
#0 {main}
int(2)
catch in gen()
exception 'Exception' with message '2' in %s:%d
Stack trace:
#0 {main}
int(3)
catch in from()
exception 'Exception' with message '3' in %s:%d
Stack trace:
#0 {main}
int(4)
catch in {main}
exception 'Exception' with message '4' in %s:%d
Stack trace:
#0 {main}
bool(false)

View File

@ -0,0 +1,42 @@
--TEST--
Basic test if yield from works
--FILE--
<?php
function from() {
yield "from" => 1;
yield 2;
}
function gen() {
yield "gen" => 0;
yield from from();
yield 3;
}
/* foreach API */
foreach (gen() as $k => $v) {
var_dump($k, $v);
}
/* iterator API */
for ($gen = gen(); $gen->valid(); $gen->next()) {
var_dump($gen->key(), $gen->current());
}
?>
--EXPECT--
string(3) "gen"
int(0)
string(4) "from"
int(1)
int(0)
int(2)
int(0)
int(3)
string(3) "gen"
int(0)
string(4) "from"
int(1)
int(0)
int(2)
int(0)
int(3)

View File

@ -0,0 +1,41 @@
--TEST--
Multiple yield from on a same Generator instance
--FILE--
<?php
function gen($a = 0) {
yield 1 + $a;
if ($a < 1) {
var_dump(yield from gen($a + 1));
}
yield 3 + $a;
return 5 + $a;
}
function bar($gen) {
var_dump(yield from $gen);
}
/* Twice a Generator from bar() using yield from on $gen */
$gen = gen();
$gens[] = bar($gen);
$gens[] = bar($gen);
do {
foreach ($gens as $g) {
var_dump($g->current());
$g->next();
}
} while($gens[0]->valid());
var_dump($gens[1]->valid());
?>
--EXPECT--
int(1)
int(2)
int(4)
int(6)
int(3)
int(5)
bool(false)

View File

@ -0,0 +1,50 @@
--TEST--
Multiple yield from on a same Generator throwing an Exception
--FILE--
<?php
function from() {
yield 1;
throw new Exception();
}
function gen($gen) {
try {
var_dump(yield from $gen);
} catch (Exception $e) { print "Caught exception!\n$e\n"; }
}
$gen = from();
$gens[] = gen($gen);
$gens[] = gen($gen);
foreach ($gens as $g) {
$g->current(); // init.
}
do {
foreach ($gens as $i => $g) {
print "Generator $i\n";
var_dump($g->current());
$g->next();
}
} while($gens[0]->valid());
?>
--EXPECTF--
Generator 0
int(1)
Caught exception!
exception 'Exception' in %s:%d
Stack trace:
#0 %s(%d): from()
#1 [internal function]: gen(Object(Generator))
#2 %s(%d): Generator->next()
#3 {main}
Generator 1
Fatal error: Uncaught exception 'ClosedGeneratorException' with message 'Generator yielded from aborted, no return value available' in %s:%d
Stack trace:
#0 [internal function]: gen(Object(Generator))
#1 %s(%d): Generator->current()
#2 {main}
thrown in %s on line %d

View File

@ -0,0 +1,34 @@
--TEST--
Check if recursion with yield from works
--FILE--
<?php
function from($a = 0) {
yield 1 + $a;
if ($a <= 3) {
yield from from($a + 3);
yield from from($a + 6);
}
yield 2 + $a;
}
function gen() {
yield from from();
}
foreach(gen() as $v) {
var_dump($v);
}
?>
--EXPECT--
int(1)
int(4)
int(7)
int(8)
int(10)
int(11)
int(5)
int(7)
int(8)
int(2)

View File

@ -0,0 +1,22 @@
--TEST--
yielding values from an array
--FILE--
<?php
function from() {
yield 0;
yield from []; // must not yield anything
yield from [1,2];
}
function gen() {
yield from from();
}
foreach(gen() as $v) {
var_dump($v);
}
?>
--EXPECT--
int(0)
int(1)
int(2)

View File

@ -0,0 +1,49 @@
--TEST--
Exceptions in linear yield from setup
--FILE--
<?php
function from($off) {
debug_print_backtrace();
yield $off + 1;
}
function gen() {
yield 1;
debug_print_backtrace();
yield 2;
yield from from(2);
debug_print_backtrace();
}
print "\nImplicit foreach:\n";
foreach (gen() as $v) {
var_dump($v);
}
print "\nExplicit iterator:\n";
for ($gen = gen(); $gen->valid(); $gen->next()) {
var_dump($gen->current());
}
?>
--EXPECTF--
Implicit foreach:
int(1)
#0 gen() called at [%s:%d]
int(2)
#0 from(2) called at [%s:%d]
#1 gen() called at [%s:%d]
int(3)
#0 gen() called at [%s:%d]
Explicit iterator:
int(1)
#0 gen()
#1 Generator->next() called at [%s:%d]
int(2)
#0 from(2) called at [%s:%d]
#1 gen()
#2 Generator->next() called at [%s:%d]
int(3)
#0 gen()
#1 Generator->next() called at [%s:%d]

View File

@ -0,0 +1,26 @@
--TEST--
Deep recursion with yield from
--FILE--
<?php
ini_set("memory_limit", "60G");
function from($i) {
yield $i;
}
function gen($i = 0) {
if ($i < 1000) {
yield from gen(++$i);
} else {
yield $i;
yield from from(++$i);
}
}
foreach (gen() as $v) {
var_dump($v);
}
?>
--EXPECT--
int(1000)
int(1001)

View File

@ -0,0 +1,332 @@
--TEST--
yield from on multiple trees needing merge
--FILE--
<?php
function from($levels) {
foreach (range(0, 2 << $levels) as $v) {
yield $v;
}
}
function gen($gen, $level) {
if ($level % 2) {
yield $gen->current();
}
yield from $gen;
}
foreach (range(0, 6) as $levels) {
print "$levels level".($levels == 1 ? "" : "s")."\n\n";
$all = array();
$all[] = $gens[0][0] = from($levels);
for ($level = 1; $level < $levels; $level++) {
for ($i = 0; $i < (1 << $level); $i++) {
$all[] = $gens[$level][$i] = gen($gens[$level-1][$i >> 1], $level);
}
}
while (1) {
foreach ($all as $gen) {
var_dump($gen->current());
$gen->next();
if (!$gen->valid()) {
break 2;
}
}
}
print "\n\n";
}
?>
--EXPECT--
0 levels
int(0)
int(1)
int(2)
1 level
int(0)
int(1)
int(2)
int(3)
int(4)
2 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
3 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
int(11)
int(12)
int(13)
int(14)
int(15)
int(16)
4 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
int(11)
int(12)
int(13)
int(14)
int(15)
int(16)
int(17)
int(18)
int(19)
int(20)
int(21)
int(22)
int(23)
int(24)
int(25)
int(26)
int(27)
int(28)
int(29)
int(30)
int(31)
int(32)
5 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
int(11)
int(12)
int(13)
int(14)
int(15)
int(16)
int(17)
int(18)
int(19)
int(20)
int(21)
int(22)
int(23)
int(24)
int(25)
int(26)
int(27)
int(28)
int(29)
int(30)
int(31)
int(32)
int(33)
int(34)
int(35)
int(36)
int(37)
int(38)
int(39)
int(40)
int(41)
int(42)
int(43)
int(44)
int(45)
int(46)
int(47)
int(48)
int(49)
int(50)
int(51)
int(52)
int(53)
int(54)
int(55)
int(56)
int(57)
int(58)
int(59)
int(60)
int(61)
int(62)
int(63)
int(64)
6 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
int(11)
int(12)
int(13)
int(14)
int(15)
int(16)
int(17)
int(18)
int(19)
int(20)
int(21)
int(22)
int(23)
int(24)
int(25)
int(26)
int(27)
int(28)
int(29)
int(30)
int(31)
int(32)
int(33)
int(34)
int(35)
int(36)
int(37)
int(38)
int(39)
int(40)
int(41)
int(42)
int(43)
int(44)
int(45)
int(46)
int(47)
int(48)
int(49)
int(50)
int(51)
int(52)
int(53)
int(54)
int(55)
int(56)
int(57)
int(58)
int(59)
int(60)
int(61)
int(62)
int(63)
int(64)
int(65)
int(66)
int(67)
int(68)
int(69)
int(70)
int(71)
int(72)
int(73)
int(74)
int(75)
int(76)
int(77)
int(78)
int(79)
int(80)
int(81)
int(82)
int(83)
int(84)
int(85)
int(86)
int(87)
int(88)
int(89)
int(90)
int(91)
int(92)
int(93)
int(94)
int(95)
int(96)
int(97)
int(98)
int(99)
int(100)
int(101)
int(102)
int(103)
int(104)
int(105)
int(106)
int(107)
int(108)
int(109)
int(110)
int(111)
int(112)
int(113)
int(114)
int(115)
int(116)
int(117)
int(118)
int(119)
int(120)
int(121)
int(122)
int(123)
int(124)
int(125)
int(126)
int(127)
int(128)

View File

@ -0,0 +1,78 @@
--TEST--
yield from on multiple trees needing merge
--FILE--
<?php
function from($levels) {
foreach (range(0, 2 << $levels) as $v) {
yield $v;
if ($v == (1 << ($levels - 1)) - 2) {
throw new Exception();
}
}
}
function gen($gen, $level) {
yield from $gen;
}
$levels = 5;
print "$levels levels\n\n";
$all = array();
$all[] = $gens[0][0] = from($levels);
for ($level = 1; $level < $levels; $level++) {
for ($i = 0; $i < (1 << $level); $i++) {
$all[] = $gens[$level][$i] = gen($gens[$level-1][$i >> 1], $level);
}
}
for ($i = 0; $i < 2; $i++) {
try {
foreach ($all as $gen) {
var_dump($gen->current());
$gen->next();
if (!$gen->valid()) {
break;
}
}
} catch(Exception $e) {
print "$e\n";
unset($all[array_search($gen, $all)]);
}
}
?>
--EXPECTF--
5 levels
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
int(11)
int(12)
int(13)
int(14)
exception 'Exception' in %s:%d
Stack trace:
#0 %s(%d): from(5)
#1 %s(%d): gen(Object(Generator), 1)
#2 %s(%d): gen(Object(Generator), 2)
#3 [internal function]: gen(Object(Generator), 3)
#4 %s(%d): Generator->next()
#5 {main}
exception 'ClosedGeneratorException' with message 'Generator yielded from aborted, no return value available' in %s:%d
Stack trace:
#0 [internal function]: gen(Object(Generator), 1)
#1 %s(%d): Generator->current()
#2 {main}

View File

@ -27,6 +27,7 @@
#include "zend_exceptions.h"
#include "zend_extensions.h"
#include "zend_closures.h"
#include "zend_generators.h"
#undef ZEND_TEST_EXCEPTIONS
@ -2249,6 +2250,8 @@ ZEND_FUNCTION(debug_print_backtrace)
call_type = NULL;
ZVAL_UNDEF(&arg_array);
ptr = zend_generator_check_placeholder_frame(ptr);
skip = ptr;
/* skip internal handler */
if ((!skip->func || !ZEND_USER_CODE(skip->func->common.type)) &&
@ -2444,6 +2447,8 @@ ZEND_API void zend_fetch_debug_backtrace(zval *return_value, int skip_last, int
frameno++;
array_init(&stack_frame);
ptr = zend_generator_check_placeholder_frame(ptr);
skip = ptr;
/* skip internal handler */
if ((!skip->func || !ZEND_USER_CODE(skip->func->common.type)) &&

View File

@ -2028,7 +2028,7 @@ static zend_always_inline zend_generator *zend_get_running_generator(zend_execut
zend_generator *generator = (zend_generator *) EX(return_value);
/* However control may currently be delegated to another generator.
* That's the one we're interested in. */
return generator->current_generator;
return generator;
}
/* }}} */

View File

@ -13,6 +13,7 @@
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Nikita Popov <nikic@php.net> |
| Bob Weinand <bobwei9@hotmail.com> |
+----------------------------------------------------------------------+
*/
@ -25,6 +26,7 @@
#include "zend_generators.h"
ZEND_API zend_class_entry *zend_ce_generator;
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
static zend_object_handlers zend_generator_handlers;
static zend_object *zend_generator_create(zend_class_entry *class_type);
@ -195,6 +197,10 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */
zval_ptr_dtor(&generator->retval);
}
if (generator->node.children > 4) {
zend_hash_destroy(&generator->node.child.ht);
}
zend_object_std_dtor(&generator->std);
if (generator->iterator) {
@ -216,7 +222,10 @@ static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ *
ZVAL_UNDEF(&generator->retval);
ZVAL_UNDEF(&generator->values);
zend_ptr_stack_init(&generator->generator_stack);
/* By default we have a tree of only one node */
generator->node.parent = NULL;
generator->node.children = 0;
generator->node.ptr.root = generator;
zend_object_std_init(&generator->std, class_type);
generator->std.handlers = &zend_generator_handlers;
@ -283,7 +292,6 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array
/* Save execution context in generator object. */
generator = (zend_generator *) Z_OBJ_P(return_value);
execute_data->prev_execute_data = NULL;
generator->execute_data = execute_data;
generator->stack = EG(vm_stack);
generator->stack->top = EG(vm_stack_top);
@ -291,11 +299,11 @@ ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array
EG(vm_stack_end) = current_stack->end;
EG(vm_stack) = current_stack;
/* By default we obviously execute the generator itself. */
generator->current_generator = generator;
/* EX(return_value) keeps pointer to zend_object (not a real zval) */
execute_data->return_value = (zval*)generator;
memset(&generator->execute_fake, 0, sizeof(zend_execute_data));
Z_OBJ(generator->execute_fake.This) = (zend_object *) generator;
}
/* }}} */
@ -307,6 +315,261 @@ static zend_function *zend_generator_get_constructor(zend_object *object) /* {{{
}
/* }}} */
ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr)
{
if (!ptr->func && ptr->prev_execute_data && Z_OBJ(ptr->This)) {
if (Z_OBJCE(ptr->This) == zend_ce_generator) {
zend_generator *generator = (zend_generator *) Z_OBJ(ptr->This);
zend_generator *root = (generator->node.children < 1 ? generator : generator->node.ptr.leaf)->node.ptr.root;
zend_execute_data *prev = ptr->prev_execute_data;
if (generator->node.parent != root) {
do {
generator->execute_data->prev_execute_data = prev;
prev = generator->execute_data;
generator = generator->node.parent;
} while (generator->node.parent != root);
}
generator->execute_data->prev_execute_data = prev;
ptr = generator->execute_data;
}
}
return ptr;
}
static void zend_generator_throw_exception(zend_generator *generator, zval *exception)
{
/* Throw the exception in the context of the generator */
zend_execute_data *original_execute_data = EG(current_execute_data);
EG(current_execute_data) = generator->execute_data;
if (exception) {
zend_throw_exception_object(exception);
} else {
zend_throw_exception_internal(NULL);
}
EG(current_execute_data) = original_execute_data;
}
static zend_generator *zend_generator_get_child(zend_generator_node *node, zend_generator *leaf)
{
switch (node->children) {
case 0:
return NULL;
case 1:
return node->child.array[0].child;
#define ZEND_GEN_GET_CHILD(x) \
if (node->child.array[x].leaf == leaf) { \
return node->child.array[x].child; \
}
case 4:
ZEND_GEN_GET_CHILD(3)
case 3:
ZEND_GEN_GET_CHILD(2)
case 2:
ZEND_GEN_GET_CHILD(1)
ZEND_GEN_GET_CHILD(0)
ZEND_ASSERT(0); // we never should have no matching child
}
return zend_hash_index_find_ptr(&node->child.ht, (zend_ulong) leaf);
}
static zend_generator_node *zend_generator_search_multi_children_node(zend_generator_node *node)
{
while (node->children == 1) {
node = &node->child.array[0].child->node;
}
return node->children > 1 ? node : NULL;
}
static void zend_generator_add_single_child(zend_generator_node *node, zend_generator *child, zend_generator *leaf)
{
if (node->children < 4) {
node->child.array[node->children].leaf = leaf;
node->child.array[node->children].child = child;
} else if (node->children > 4) {
zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) leaf, child);
} else {
struct {
zend_generator *leaf;
zend_generator *child;
} array[4];
int i;
memcpy(&array, &node->child.array, sizeof(array));
zend_hash_init(&node->child.ht, 5, sigh, NULL, 0);
for (i = 0; i < 4; i++) {
zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) array[i].leaf, array[i].child);
}
zend_hash_index_add_ptr(&node->child.ht, (zend_ulong) leaf, child);
}
node->children++;
}
static void zend_generator_merge_child_nodes(zend_generator_node *dest, zend_generator_node *src, zend_generator *child)
{
if (src->children <= 4) {
int i = src->children;
while (i--) {
zend_generator_add_single_child(dest, child, src->child.array[i].leaf);
}
} else {
zend_ulong leaf;
ZEND_HASH_FOREACH_NUM_KEY(&src->child.ht, leaf) {
zend_generator_add_single_child(dest, child, (zend_generator *) leaf);
} ZEND_HASH_FOREACH_END();
}
}
static void zend_generator_add_child(zend_generator *generator, zend_generator *child)
{
zend_generator *leaf = child->node.children ? child->node.ptr.leaf : child;
zend_generator_node *multi_children_node;
zend_bool was_leaf = generator->node.children == 0;
if (was_leaf) {
zend_generator *next = generator->node.parent;
leaf->node.ptr.root = generator->node.ptr.root;
generator->node.ptr.leaf = leaf;
while (next) {
if (next->node.children > 1) {
if (next->node.children > 4) {
zend_generator *child = zend_hash_index_find_ptr(&next->node.child.ht, (zend_ulong) generator);
zend_hash_index_del(&next->node.child.ht, (zend_ulong) generator);
zend_hash_index_add_ptr(&next->node.child.ht, (zend_ulong) leaf, child);
} else {
switch (next->node.children) {
#define ZEND_GEN_UPDATE_CHILD(x) \
if (next->node.child.array[x].leaf == generator) { \
next->node.child.array[x].leaf = leaf; \
break; \
}
case 4:
ZEND_GEN_UPDATE_CHILD(3)
case 3:
ZEND_GEN_UPDATE_CHILD(2)
case 2:
ZEND_GEN_UPDATE_CHILD(1)
ZEND_GEN_UPDATE_CHILD(0)
ZEND_ASSERT(0); // we never should have no matching child
}
}
}
next->node.ptr.leaf = leaf;
next = next->node.parent;
}
zend_generator_add_single_child(&generator->node, child, leaf);
} else if (generator->node.children == 1) {
multi_children_node = zend_generator_search_multi_children_node(&generator->node);
if (multi_children_node) {
generator->node.children = 0;
zend_generator_merge_child_nodes(&generator->node, multi_children_node, generator->node.child.array[0].child);
}
}
if (!was_leaf) {
multi_children_node = zend_generator_search_multi_children_node(&child->node);
} else {
multi_children_node = (zend_generator_node *) 0x1;
}
{
zend_generator *parent = generator->node.parent, *cur = generator;
if (multi_children_node > (zend_generator_node *) 0x1) {
zend_generator_merge_child_nodes(&generator->node, multi_children_node, child);
} else {
zend_generator_add_single_child(&generator->node, child, leaf);
}
while (parent) {
if (parent->node.children > 1) {
if (multi_children_node == (zend_generator_node *) 0x1) {
multi_children_node = zend_generator_search_multi_children_node(&child->node);
}
if (multi_children_node) {
zend_generator_merge_child_nodes(&parent->node, multi_children_node, cur);
} else {
zend_generator_add_single_child(&parent->node, cur, leaf);
}
}
cur = parent;
parent = parent->node.parent;
}
}
}
void zend_generator_yield_from(zend_generator *this, zend_generator *from)
{
zend_generator_add_child(from, this);
this->node.parent = from;
}
ZEND_API zend_generator *zend_generator_get_current(zend_generator *generator)
{
zend_generator *leaf;
zend_generator *root;
if (generator->node.parent == NULL) {
/* we're not in yield from mode */
return generator;
}
leaf = generator->node.children ? generator->node.ptr.leaf : generator;
root = leaf->node.ptr.root;
if (root->execute_data && root->node.parent == NULL) {
/* generator still running */
return root;
}
while (!root->execute_data && root != generator) {
/* generator at the root had stopped */
root = zend_generator_get_child(&root->node, leaf);
}
if (root->node.parent) {
if (root->node.parent->execute_data == NULL) {
if (EXPECTED(EG(exception) == NULL)) {
zend_op *yield_from = (zend_op *) root->execute_data->opline - 1;
if (yield_from->opcode == ZEND_YIELD_FROM && !(yield_from->result_type & EXT_TYPE_UNUSED)) {
if (Z_ISUNDEF(root->node.parent->retval)) {
/* Throw the exception in the context of the generator */
zend_execute_data *original_execute_data = EG(current_execute_data);
EG(current_execute_data) = root->execute_data;
if (root == generator) {
root->execute_data->prev_execute_data = original_execute_data;
} else {
root->execute_data->prev_execute_data = &generator->execute_fake;
generator->execute_fake.prev_execute_data = original_execute_data;
}
zend_throw_exception(zend_ce_ClosedGeneratorException, "Generator yielded from aborted, no return value available", 0);
EG(current_execute_data) = original_execute_data;
} else {
ZVAL_COPY(ZEND_CALL_VAR(root->execute_data, yield_from->result.var), &root->node.parent->retval);
}
}
}
root->node.parent = NULL;
} else {
do {
root = root->node.parent;
} while (root->node.parent);
}
}
return leaf->node.ptr.root = root;
}
static int zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
{
zval *value;
@ -383,29 +646,37 @@ failure:
}
/* }}} */
ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */
ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
{
zend_generator *generator;
/* The generator is already closed, thus can't resume */
if (!generator->execute_data) {
if (!orig_generator->execute_data) {
return;
}
generator = zend_generator_get_current(orig_generator);
try_again:
if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
zend_error(E_ERROR, "Cannot resume an already running generator");
}
try_again:
if (!Z_ISUNDEF(generator->values)) {
if (zend_generator_get_next_delegated_value(generator) == SUCCESS) {
return;
}
/* If there are no more deletegated values, resume the generator
* at the "yield *" expression. */
// TODO: Handle exceptions
* after the "yield from" expression. */
}
if ((orig_generator->flags & ZEND_GENERATOR_DO_INIT) && !Z_ISUNDEF(generator->value)) {
/* We must not advance Generator if we yield from a Generator being currently run */
return;
}
/* Drop the AT_FIRST_YIELD flag */
generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
{
/* Backup executor globals */
@ -424,7 +695,14 @@ try_again:
/* We want the backtrace to look as if the generator function was
* called from whatever method we are current running (e.g. next()).
* So we have to link generator call frame with caller call frame. */
generator->execute_data->prev_execute_data = original_execute_data;
if (generator == orig_generator) {
generator->execute_data->prev_execute_data = original_execute_data;
} else {
/* We need some execute_data placeholder in stacktrace to be replaced
* by the real stack trace when needed */
generator->execute_data->prev_execute_data = &orig_generator->execute_fake;
orig_generator->execute_fake.prev_execute_data = original_execute_data;
}
/* Resume execution */
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
@ -435,7 +713,6 @@ try_again:
if (generator->execute_data) {
generator->stack = EG(vm_stack);
generator->stack->top = EG(vm_stack_top);
generator->execute_data->prev_execute_data = NULL;
}
/* Restore executor globals */
@ -446,13 +723,24 @@ try_again:
EG(vm_stack) = original_stack;
/* If an exception was thrown in the generator we have to internally
* rethrow it in the parent scope. */
* rethrow it in the parent scope.
* In case we did yield from, the Exception must be rethrown into
* its calling frame (see above in if (check_yield_from). */
if (UNEXPECTED(EG(exception) != NULL)) {
zend_throw_exception_internal(NULL);
zend_generator_close(generator, 0);
if (generator == orig_generator) {
zend_throw_exception_internal(NULL);
} else {
generator = zend_generator_get_current(orig_generator);
zend_generator_throw_exception(generator, NULL);
goto try_again;
}
}
/* Yield-from was used, try another resume. */
if (!Z_ISUNDEF(generator->values)) {
/* yield from was used, try another resume. */
if ((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM)) {
generator = zend_generator_get_current(orig_generator);
goto try_again;
}
}
@ -461,8 +749,10 @@ try_again:
static void zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */
{
if (generator->execute_data && Z_TYPE(generator->value) == IS_UNDEF) {
if (generator->execute_data && Z_TYPE(generator->value) == IS_UNDEF && generator->node.parent == NULL) {
generator->flags |= ZEND_GENERATOR_DO_INIT;
zend_generator_resume(generator);
generator->flags &= ~ZEND_GENERATOR_DO_INIT;
generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
}
}
@ -508,7 +798,9 @@ ZEND_METHOD(Generator, valid)
zend_generator_ensure_initialized(generator);
RETURN_BOOL(Z_TYPE(generator->value) != IS_UNDEF);
zend_generator_get_current(generator);
RETURN_BOOL(Z_TYPE(generator->value) != IS_UNDEF || generator->node.parent != NULL);
}
/* }}} */
@ -516,7 +808,7 @@ ZEND_METHOD(Generator, valid)
* Get the current value */
ZEND_METHOD(Generator, current)
{
zend_generator *generator;
zend_generator *generator, *root;
if (zend_parse_parameters_none() == FAILURE) {
return;
@ -526,8 +818,9 @@ ZEND_METHOD(Generator, current)
zend_generator_ensure_initialized(generator);
if (Z_TYPE(generator->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&generator->value);
root = zend_generator_get_current(generator);
if (Z_TYPE(root->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&root->value);
}
}
/* }}} */
@ -536,7 +829,7 @@ ZEND_METHOD(Generator, current)
* Get the current key */
ZEND_METHOD(Generator, key)
{
zend_generator *generator;
zend_generator *generator, *root;
if (zend_parse_parameters_none() == FAILURE) {
return;
@ -546,8 +839,9 @@ ZEND_METHOD(Generator, key)
zend_generator_ensure_initialized(generator);
if (Z_TYPE(generator->key) != IS_UNDEF) {
RETURN_ZVAL_FAST(&generator->key);
root = zend_generator_get_current(generator);
if (Z_TYPE(root->key) != IS_UNDEF) {
RETURN_ZVAL_FAST(&root->key);
}
}
/* }}} */
@ -575,7 +869,7 @@ ZEND_METHOD(Generator, next)
ZEND_METHOD(Generator, send)
{
zval *value;
zend_generator *generator;
zend_generator *generator, *root;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) {
return;
@ -590,16 +884,18 @@ ZEND_METHOD(Generator, send)
return;
}
root = zend_generator_get_current(generator);
/* Put sent value in the target VAR slot, if it is used */
if (generator->send_target) {
if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target);
ZVAL_COPY(generator->send_target, value);
if (root->send_target) {
if (Z_REFCOUNTED_P(root->send_target)) Z_DELREF_P(root->send_target);
ZVAL_COPY(root->send_target, value);
}
zend_generator_resume(generator);
if (Z_TYPE(generator->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&generator->value);
root = zend_generator_get_current(generator);
if (Z_TYPE(root->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&root->value);
}
}
/* }}} */
@ -622,18 +918,15 @@ ZEND_METHOD(Generator, throw)
zend_generator_ensure_initialized(generator);
if (generator->execute_data) {
/* Throw the exception in the context of the generator */
zend_execute_data *current_execute_data = EG(current_execute_data);
EG(current_execute_data) = generator->execute_data;
zend_generator *root = zend_generator_get_current(generator);
zend_throw_exception_object(&exception_copy);
EG(current_execute_data) = current_execute_data;
zend_generator_throw_exception(root, &exception_copy);
zend_generator_resume(generator);
if (Z_TYPE(generator->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&generator->value);
root = zend_generator_get_current(generator);
if (Z_TYPE(root->value) != IS_UNDEF) {
RETURN_ZVAL_FAST(&root->value);
}
} else {
/* If the generator is already closed throw the exception in the
@ -704,28 +997,34 @@ static int zend_generator_iterator_valid(zend_object_iterator *iterator) /* {{{
zend_generator_ensure_initialized(generator);
return Z_TYPE(generator->value) != IS_UNDEF ? SUCCESS : FAILURE;
zend_generator_get_current(generator);
return Z_TYPE(generator->value) != IS_UNDEF || generator->node.parent != NULL ? SUCCESS : FAILURE;
}
/* }}} */
static zval *zend_generator_iterator_get_data(zend_object_iterator *iterator) /* {{{ */
{
zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root;
zend_generator_ensure_initialized(generator);
return &generator->value;
root = zend_generator_get_current(generator);
return &root->value;
}
/* }}} */
static void zend_generator_iterator_get_key(zend_object_iterator *iterator, zval *key) /* {{{ */
{
zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root;
zend_generator_ensure_initialized(generator);
if (Z_TYPE(generator->key) != IS_UNDEF) {
ZVAL_ZVAL(key, &generator->key, 1, 0);
root = zend_generator_get_current(generator);
if (Z_TYPE(root->key) != IS_UNDEF) {
ZVAL_ZVAL(key, &root->key, 1, 0);
} else {
ZVAL_NULL(key);
}
@ -830,6 +1129,9 @@ void zend_register_generator_ce(void) /* {{{ */
zend_generator_handlers.dtor_obj = zend_generator_dtor_storage;
zend_generator_handlers.clone_obj = NULL;
zend_generator_handlers.get_constructor = zend_generator_get_constructor;
INIT_CLASS_ENTRY(ce, "ClosedGeneratorException", NULL);
zend_ce_ClosedGeneratorException = zend_register_internal_class_ex(&ce, zend_exception_get_default());
}
/* }}} */

View File

@ -13,6 +13,7 @@
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Nikita Popov <nikic@php.net> |
| Bob Weinand <bobwei9@hotmail.com> |
+----------------------------------------------------------------------+
*/
@ -24,8 +25,28 @@
BEGIN_EXTERN_C()
extern ZEND_API zend_class_entry *zend_ce_generator;
extern ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
typedef struct _zend_generator {
typedef struct _zend_generator_node zend_generator_node;
typedef struct _zend_generator zend_generator;
struct _zend_generator_node {
zend_generator *parent; /* NULL for root */
uint32_t children;
union {
HashTable ht; /* if > 4 children */
struct {
zend_generator *leaf;
zend_generator *child;
} array[4]; /* if <= 4 children */
} child;
union {
zend_generator *leaf; /* if > 0 children */
zend_generator *root; /* if 0 children */
} ptr;
};
struct _zend_generator {
zend_object std;
zend_object_iterator *iterator;
@ -48,33 +69,37 @@ typedef struct _zend_generator {
/* Largest used integer key for auto-incrementing keys */
zend_long largest_used_integer_key;
/* Values specified by "yield *" to yield from this generator.
/* Values specified by "yield from" to yield from this generator.
* This is only used for arrays or non-generator Traversables.
* This zval also uses the u2 structure in the same way as
* by-value foreach. */
zval values;
/* Generator that is currently yielding values. This will differ
* from the surrounding structure if "yield *" is used on a generator. */
struct _zend_generator *current_generator;
/* Stack of waiting generators when multiple "yield *" expressions
/* Node of waiting generators when multiple "yield *" expressions
* are nested. */
zend_ptr_stack generator_stack;
zend_generator_node node;
/* Fake execute_data for stacktraces */
zend_execute_data execute_fake;
/* ZEND_GENERATOR_* flags */
zend_uchar flags;
} zend_generator;
};
static const zend_uchar ZEND_GENERATOR_CURRENTLY_RUNNING = 0x1;
static const zend_uchar ZEND_GENERATOR_FORCED_CLOSE = 0x2;
static const zend_uchar ZEND_GENERATOR_AT_FIRST_YIELD = 0x4;
static const zend_uchar ZEND_GENERATOR_DO_INIT = 0x8;
void zend_register_generator_ce(void);
ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array *op_array, zval *return_value);
ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished_execution);
ZEND_API void zend_generator_resume(zend_generator *generator);
void zend_generator_yield_from(zend_generator *this, zend_generator *from);
ZEND_API zend_generator *zend_generator_get_current(zend_generator *generator);
ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr);
END_EXTERN_C()
#endif

View File

@ -545,6 +545,15 @@ static zend_always_inline void *zend_hash_str_update_mem(HashTable *ht, const ch
return zend_hash_str_update_ptr(ht, str, len, p);
}
static zend_always_inline void *zend_hash_index_add_ptr(HashTable *ht, zend_ulong h, void *pData)
{
zval tmp, *zv;
ZVAL_PTR(&tmp, pData);
zv = zend_hash_index_add(ht, h, &tmp);
return zv ? Z_PTR_P(zv) : NULL;
}
static zend_always_inline void *zend_hash_index_update_ptr(HashTable *ht, zend_ulong h, void *pData)
{
zval tmp, *zv;

View File

@ -67,6 +67,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%right T_PRINT
%right T_YIELD
%right T_DOUBLE_ARROW
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':'
%right T_COALESCE
@ -112,6 +113,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_LOGICAL_AND "and (T_LOGICAL_AND)"
%token T_PRINT "print (T_PRINT)"
%token T_YIELD "yield (T_YIELD)"
%token T_YIELD_FROM "yield from (T_YIELD_FROM)"
%token T_PLUS_EQUAL "+= (T_PLUS_EQUAL)"
%token T_MINUS_EQUAL "-= (T_MINUS_EQUAL)"
%token T_MUL_EQUAL "*= (T_MUL_EQUAL)"
@ -872,7 +874,7 @@ expr_without_variable:
| T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); }
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); }
| T_YIELD '*' expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $3); }
| T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); }
| function returns_ref '(' parameter_list ')' lexical_vars return_type
backup_doc_comment '{' inner_statement_list '}'
{ $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $8,

File diff suppressed because it is too large Load Diff

View File

@ -1119,6 +1119,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
return T_RETURN;
}
<ST_IN_SCRIPTING>"yield"{WHITESPACE}"from" {
return T_YIELD_FROM;
}
<ST_IN_SCRIPTING>"yield" {
return T_YIELD;
}

View File

@ -6392,13 +6392,27 @@ ZEND_VM_HANDLER(170, ZEND_YIELD_FROM, CONST|TMP|VAR|CV, ANY)
if (ce == zend_ce_generator) {
zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val);
zend_ptr_stack_push(&generator->generator_stack, generator->current_generator);
generator->current_generator = new_gen;
if (OP1_TYPE != IS_TMP_VAR) {
Z_ADDREF_P(val);
}
FREE_OP1_IF_VAR();
if (Z_ISUNDEF(new_gen->retval)) {
zend_generator_yield_from(generator, new_gen);
} else if (new_gen->execute_data == NULL) {
// TODO: Should be an engine exception
zend_error(E_RECOVERABLE_ERROR, "Generator passed to yield from was aborted without proper return and is unable to continue");
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
if (RETURN_VALUE_USED(opline)) {
ZVAL_COPY(EX_VAR(opline->result.var), &new_gen->retval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else {
zend_object_iterator *iter = ce->get_iterator(ce, val, 0);
FREE_OP1();
@ -6425,12 +6439,13 @@ ZEND_VM_HANDLER(170, ZEND_YIELD_FROM, CONST|TMP|VAR|CV, ANY)
}
} else {
// TODO: Should be an engine exception
zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0);
zend_throw_exception(NULL, "Can use \"yield from\" only with arrays and Traversables", 0);
HANDLE_EXCEPTION();
}
/* This is the default return value
* when the expression is a Generator, it will be overwritten in zend_generator_resume() */
if (RETURN_VALUE_USED(opline)) {
// TODO
ZVAL_NULL(EX_VAR(opline->result.var));
}

View File

@ -1384,8 +1384,7 @@ static int ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER
ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op_num]);
ZEND_VM_CONTINUE();
} else if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) {
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
zend_generator_close(generator, 1);
ZEND_VM_RETURN();
} else {
@ -1418,8 +1417,7 @@ static int ZEND_FASTCALL ZEND_USER_OPCODE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS
ZEND_VM_CONTINUE();
case ZEND_USER_OPCODE_RETURN:
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) {
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
zend_generator_close(generator, 1);
ZEND_VM_RETURN();
} else {
@ -1496,8 +1494,7 @@ static int ZEND_FASTCALL ZEND_FAST_RET_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[opline->op2.opline_num]);
ZEND_VM_CONTINUE();
} else if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) {
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
zend_generator_close(generator, 1);
ZEND_VM_RETURN();
} else {
@ -2611,8 +2608,7 @@ static int ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_H
zval *retval;
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
SAVE_OPLINE();
retval = EX_CONSTANT(opline->op1);
@ -3507,7 +3503,28 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER
} else if (IS_CONST != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(val);
if (ce == zend_ce_generator) {
zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val);
if (IS_CONST != IS_TMP_VAR) {
Z_ADDREF_P(val);
}
if (Z_ISUNDEF(new_gen->retval)) {
zend_generator_yield_from(generator, new_gen);
} else if (new_gen->execute_data == NULL) {
// TODO: Should be an engine exception
zend_error(E_RECOVERABLE_ERROR, "Generator passed to yield from was aborted without proper return and is unable to continue");
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
if (RETURN_VALUE_USED(opline)) {
ZVAL_COPY(EX_VAR(opline->result.var), &new_gen->retval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else {
zend_object_iterator *iter = ce->get_iterator(ce, val, 0);
@ -3533,10 +3550,12 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER
}
} else {
// TODO: Should be an engine exception
zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0);
zend_throw_exception(NULL, "Can use \"yield from\" only with arrays and Traversables", 0);
HANDLE_EXCEPTION();
}
/* This is the default return value
* when the expression is a Generator, it will be overwritten in zend_generator_resume() */
if (RETURN_VALUE_USED(opline)) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
@ -5149,8 +5168,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLE
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -5326,8 +5344,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_TMP_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -5834,8 +5851,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_VAR_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -6633,8 +6649,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDL
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -7785,8 +7800,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_A
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -8985,8 +8999,7 @@ static int ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_TMP_HANDLER(ZEND_OPCODE_HAN
zval *retval;
zend_free_op free_op1;
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
SAVE_OPLINE();
retval = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
@ -9589,7 +9602,28 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_A
} else if (IS_TMP_VAR != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(val);
if (ce == zend_ce_generator) {
zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val);
if (IS_TMP_VAR != IS_TMP_VAR) {
Z_ADDREF_P(val);
}
if (Z_ISUNDEF(new_gen->retval)) {
zend_generator_yield_from(generator, new_gen);
} else if (new_gen->execute_data == NULL) {
// TODO: Should be an engine exception
zend_error(E_RECOVERABLE_ERROR, "Generator passed to yield from was aborted without proper return and is unable to continue");
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
if (RETURN_VALUE_USED(opline)) {
ZVAL_COPY(EX_VAR(opline->result.var), &new_gen->retval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else {
zend_object_iterator *iter = ce->get_iterator(ce, val, 0);
zval_ptr_dtor_nogc(free_op1);
@ -9616,10 +9650,12 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_A
}
} else {
// TODO: Should be an engine exception
zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0);
zend_throw_exception(NULL, "Can use \"yield from\" only with arrays and Traversables", 0);
HANDLE_EXCEPTION();
}
/* This is the default return value
* when the expression is a Generator, it will be overwritten in zend_generator_resume() */
if (RETURN_VALUE_USED(opline)) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
@ -10007,8 +10043,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_CONST_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -10169,8 +10204,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_TMP_HANDLER(ZEND_OPCODE_HANDLER_AR
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -10331,8 +10365,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_VAR_HANDLER(ZEND_OPCODE_HANDLER_AR
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -10635,8 +10668,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -11089,8 +11121,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_TMP_CV_HANDLER(ZEND_OPCODE_HANDLER_ARG
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -11767,8 +11798,7 @@ static int ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_VAR_HANDLER(ZEND_OPCODE_HAN
zval *retval;
zend_free_op free_op1;
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
SAVE_OPLINE();
retval = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
@ -12926,7 +12956,29 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_A
} else if (IS_VAR != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(val);
if (ce == zend_ce_generator) {
zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val);
if (IS_VAR != IS_TMP_VAR) {
Z_ADDREF_P(val);
}
zval_ptr_dtor_nogc(free_op1);
if (Z_ISUNDEF(new_gen->retval)) {
zend_generator_yield_from(generator, new_gen);
} else if (new_gen->execute_data == NULL) {
// TODO: Should be an engine exception
zend_error(E_RECOVERABLE_ERROR, "Generator passed to yield from was aborted without proper return and is unable to continue");
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
if (RETURN_VALUE_USED(opline)) {
ZVAL_COPY(EX_VAR(opline->result.var), &new_gen->retval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else {
zend_object_iterator *iter = ce->get_iterator(ce, val, 0);
zval_ptr_dtor_nogc(free_op1);
@ -12953,10 +13005,12 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_A
}
} else {
// TODO: Should be an engine exception
zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0);
zend_throw_exception(NULL, "Can use \"yield from\" only with arrays and Traversables", 0);
HANDLE_EXCEPTION();
}
/* This is the default return value
* when the expression is a Generator, it will be overwritten in zend_generator_resume() */
if (RETURN_VALUE_USED(opline)) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
@ -14479,8 +14533,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_VAR_CONST_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -14679,8 +14732,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_VAR_TMP_HANDLER(ZEND_OPCODE_HANDLER_AR
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -14930,8 +14982,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_VAR_VAR_HANDLER(ZEND_OPCODE_HANDLER_AR
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -15772,8 +15823,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -17316,8 +17366,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_VAR_CV_HANDLER(ZEND_OPCODE_HANDLER_ARG
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -20205,8 +20254,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_UNUSED_CONST_HANDLER(ZEND_OPCODE_HANDL
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -20336,8 +20384,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_UNUSED_TMP_HANDLER(ZEND_OPCODE_HANDLER
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -20467,8 +20514,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_UNUSED_VAR_HANDLER(ZEND_OPCODE_HANDLER
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -20929,8 +20975,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HAND
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -22313,8 +22358,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_UNUSED_CV_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -24192,8 +24236,7 @@ static int ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_CV_HANDLER(ZEND_OPCODE_HAND
zval *retval;
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
SAVE_OPLINE();
retval = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var);
@ -25176,7 +25219,28 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_AR
} else if (IS_CV != IS_CONST && Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val)->get_iterator) {
zend_class_entry *ce = Z_OBJCE_P(val);
if (ce == zend_ce_generator) {
zend_generator *new_gen = (zend_generator *) Z_OBJ_P(val);
if (IS_CV != IS_TMP_VAR) {
Z_ADDREF_P(val);
}
if (Z_ISUNDEF(new_gen->retval)) {
zend_generator_yield_from(generator, new_gen);
} else if (new_gen->execute_data == NULL) {
// TODO: Should be an engine exception
zend_error(E_RECOVERABLE_ERROR, "Generator passed to yield from was aborted without proper return and is unable to continue");
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
} else {
if (RETURN_VALUE_USED(opline)) {
ZVAL_COPY(EX_VAR(opline->result.var), &new_gen->retval);
}
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
} else {
zend_object_iterator *iter = ce->get_iterator(ce, val, 0);
@ -25202,10 +25266,12 @@ static int ZEND_FASTCALL ZEND_YIELD_FROM_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_AR
}
} else {
// TODO: Should be an engine exception
zend_throw_exception(NULL, "Can use \"yield *\" only with arrays and Traversables", 0);
zend_throw_exception(NULL, "Can use \"yield from\" only with arrays and Traversables", 0);
HANDLE_EXCEPTION();
}
/* This is the default return value
* when the expression is a Generator, it will be overwritten in zend_generator_resume() */
if (RETURN_VALUE_USED(opline)) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
@ -27601,8 +27667,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_A
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -27871,8 +27936,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARG
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -28509,8 +28573,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CV_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARG
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -29554,8 +29617,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");
@ -31597,8 +31659,7 @@ static int ZEND_FASTCALL ZEND_YIELD_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS
{
USE_OPLINE
/* The generator object is stored in EX(return_value) */
zend_generator *generator = (zend_generator *) EX(return_value);
zend_generator *generator = zend_get_running_generator(execute_data);
if (generator->flags & ZEND_GENERATOR_FORCED_CLOSE) {
zend_error_noreturn(E_ERROR, "Cannot yield from finally in a force-closed generator");