Skip to content

Commit 6c157dc

Browse files
committed
Fix crashes with unproper cleaning of repeated yield from
1 parent 71de8fe commit 6c157dc

File tree

2 files changed

+108
-11
lines changed

2 files changed

+108
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
A generator can be yielded from multiple times, testing immediate release of the yield from'ing generator
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
yield 42;
8+
}
9+
function yield_from($gen) {
10+
yield from $gen;
11+
}
12+
$gen = gen();
13+
var_dump(yield_from($gen)->current());
14+
var_dump(yield_from($gen)->current());
15+
16+
?>
17+
--EXPECT--
18+
int(42)
19+
int(42)

Zend/zend_generators.c

+89-11
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,52 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
163163

164164
static zend_generator *zend_generator_get_child(zend_generator_node *node, zend_generator *leaf);
165165

166+
static void zend_generator_update_leaf_of_child(zend_generator_node *node, zend_generator *from_leaf, zend_generator *to_leaf)
167+
{
168+
if (node->ptr.leaf == from_leaf) {
169+
node->ptr.leaf = to_leaf;
170+
}
171+
if (node->children <= 1) {
172+
node->child.single.leaf = to_leaf;
173+
} else {
174+
HashTable *ht = node->child.ht;
175+
zend_generator *child = zend_hash_index_find_ptr(ht, (zend_ulong) from_leaf);
176+
if (child) {
177+
zend_hash_index_del(ht, (zend_ulong) from_leaf);
178+
zend_hash_index_add_ptr(ht, (zend_ulong) to_leaf, child);
179+
}
180+
}
181+
}
182+
183+
static void zend_generator_remove_leaf_child(zend_generator_node *node, zend_generator *leaf, zend_generator *replace_leaf) {
184+
if (node->children > 1) {
185+
HashTable *ht = node->child.ht;
186+
zend_ulong child_leaf;
187+
zend_generator *child_generator;
188+
zend_hash_index_del(ht, (zend_ulong) leaf);
189+
if (--node->children == 1) {
190+
ZEND_HASH_FOREACH_NUM_KEY_PTR(ht, child_leaf, child_generator) {
191+
node->child.single.leaf = (zend_generator *) child_leaf;
192+
node->child.single.child = child_generator;
193+
if (node->ptr.leaf == leaf) {
194+
node->ptr.leaf = (zend_generator *) child_leaf;
195+
}
196+
break;
197+
} ZEND_HASH_FOREACH_END();
198+
zend_hash_destroy(ht);
199+
efree(ht);
200+
} else if (node->ptr.leaf == leaf) {
201+
ZEND_HASH_FOREACH_NUM_KEY_PTR(ht, child_leaf, child_generator) {
202+
node->ptr.leaf = (zend_generator *) child_leaf;
203+
break;
204+
} ZEND_HASH_FOREACH_END();
205+
}
206+
} else if (node->ptr.leaf == leaf) {
207+
ZEND_ASSERT(replace_leaf != leaf);
208+
node->ptr.leaf = replace_leaf;
209+
}
210+
}
211+
166212
static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
167213
{
168214
zend_generator *generator = (zend_generator*) object;
@@ -177,14 +223,43 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
177223
}
178224

179225
if (EXPECTED(generator->node.children == 0)) {
180-
zend_generator *root = generator->node.ptr.root, *next;
181-
while (UNEXPECTED(root != generator)) {
182-
next = zend_generator_get_child(&root->node, generator);
183-
generator->node.ptr.root = next;
184-
next->node.parent = NULL;
185-
OBJ_RELEASE(&root->std);
186-
root = next;
226+
run_normal_destruction:
227+
zend_generator_update_current(generator, generator); /* ensure we remove it from a *live* root */
228+
zend_generator *root = generator->node.ptr.root, *parent = generator->node.parent, *next, *toproot = root;
229+
if (parent) {
230+
zend_bool parent_becomes_leaf = parent->node.children == 1;
231+
while (UNEXPECTED(root != generator)) {
232+
next = zend_generator_get_child(&root->node, generator);
233+
if (parent_becomes_leaf) {
234+
zend_generator_update_leaf_of_child(&root->node, generator, parent);
235+
} else {
236+
zend_generator_remove_leaf_child(&root->node, generator, parent->node.ptr.leaf);
237+
OBJ_RELEASE(&root->std);
238+
}
239+
root = next;
240+
}
241+
if (parent_becomes_leaf) {
242+
parent->node.ptr.root = toproot;
243+
parent->node.children = 0;
244+
OBJ_RELEASE(&parent->std);
245+
}
246+
/* Reset for resuming in finally */
247+
generator->node.parent = NULL;
248+
generator->node.ptr.root = generator;
187249
}
250+
} else if (generator->node.parent) {
251+
/* we're called out of order - this must only happen during shutdown sequence: we call our (direct) child nodes destructors first, to clean it from the bottom up */
252+
while (generator->node.children != 0) {
253+
zend_generator *child;
254+
if (generator->node.children == 1) {
255+
child = generator->node.child.single.child;
256+
} else {
257+
child = (zend_generator *) Z_PTR_P(zend_hash_get_current_data(generator->node.child.ht));
258+
}
259+
GC_ADD_FLAGS(&child->std, IS_OBJ_DESTRUCTOR_CALLED);
260+
zend_generator_dtor_storage(&child->std);
261+
}
262+
goto run_normal_destruction;
188263
}
189264

190265
if (EXPECTED(!ex) || EXPECTED(!(ex->func->op_array.fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK))
@@ -465,10 +540,13 @@ static void zend_generator_add_single_child(zend_generator_node *node, zend_gene
465540
node->child.ht = ht;
466541
}
467542

468-
zend_hash_index_add_ptr(node->child.ht, (zend_ulong) leaf, child);
543+
if (zend_hash_index_add_ptr(node->child.ht, (zend_ulong) leaf, child) == NULL) {
544+
ZEND_ASSERT(node->children > 1);
545+
return;
546+
}
469547
}
470548

471-
node->children++;
549+
++node->children;
472550
}
473551

474552
static void zend_generator_merge_child_nodes(zend_generator_node *dest, zend_generator_node *src, zend_generator *child)
@@ -507,7 +585,6 @@ static void zend_generator_add_child(zend_generator *generator, zend_generator *
507585
} else if (generator->node.children == 1) {
508586
multi_children_node = zend_generator_search_multi_children_node(&generator->node);
509587
if (multi_children_node) {
510-
generator->node.children = 0;
511588
zend_generator_merge_child_nodes(&generator->node, multi_children_node, generator->node.child.single.child);
512589
}
513590
}
@@ -518,6 +595,7 @@ static void zend_generator_add_child(zend_generator *generator, zend_generator *
518595
multi_children_node = (zend_generator_node *) 0x1;
519596
}
520597

598+
/* for allowing zend_generator_get_child() to work, we need every multi children node to have ALL its leaf descendents present, linking to their respective child */
521599
{
522600
zend_generator *parent = generator->node.parent, *cur = generator;
523601

@@ -574,7 +652,7 @@ ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator
574652

575653
if (root->node.parent) {
576654
if (root->node.parent->execute_data == NULL) {
577-
if (EXPECTED(EG(exception) == NULL)) {
655+
if (EXPECTED(EG(exception) == NULL) && EXPECTED((OBJ_FLAGS(&generator->std) & IS_OBJ_DESTRUCTOR_CALLED) == 0)) {
578656
zend_op *yield_from = (zend_op *) root->execute_data->opline - 1;
579657

580658
if (yield_from->opcode == ZEND_YIELD_FROM) {

0 commit comments

Comments
 (0)