1 /*
2 +----------------------------------------------------------------------+
3 | Zend Engine |
4 +----------------------------------------------------------------------+
5 | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 2.00 of the Zend license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.zend.com/license/2_00.txt. |
11 | If you did not receive a copy of the Zend license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@zend.com so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Nikita Popov <nikic@php.net> |
16 | Bob Weinand <bobwei9@hotmail.com> |
17 +----------------------------------------------------------------------+
18 */
19
20 #include "zend.h"
21 #include "zend_API.h"
22 #include "zend_hash.h"
23 #include "zend_interfaces.h"
24 #include "zend_exceptions.h"
25 #include "zend_generators.h"
26 #include "zend_closures.h"
27 #include "zend_generators_arginfo.h"
28 #include "zend_observer.h"
29 #include "zend_vm_opcodes.h"
30
31 ZEND_API zend_class_entry *zend_ce_generator;
32 ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
33 static zend_object_handlers zend_generator_handlers;
34
35 static zend_object *zend_generator_create(zend_class_entry *class_type);
36
zend_generator_restore_call_stack(zend_generator * generator)37 ZEND_API void zend_generator_restore_call_stack(zend_generator *generator) /* {{{ */
38 {
39 zend_execute_data *call, *new_call, *prev_call = NULL;
40
41 call = generator->frozen_call_stack;
42 do {
43 new_call = zend_vm_stack_push_call_frame(
44 (ZEND_CALL_INFO(call) & ~ZEND_CALL_ALLOCATED),
45 call->func,
46 ZEND_CALL_NUM_ARGS(call),
47 Z_PTR(call->This));
48 memcpy(((zval*)new_call) + ZEND_CALL_FRAME_SLOT, ((zval*)call) + ZEND_CALL_FRAME_SLOT, ZEND_CALL_NUM_ARGS(call) * sizeof(zval));
49 new_call->extra_named_params = call->extra_named_params;
50 new_call->prev_execute_data = prev_call;
51 prev_call = new_call;
52
53 call = call->prev_execute_data;
54 } while (call);
55 generator->execute_data->call = prev_call;
56 efree(generator->frozen_call_stack);
57 generator->frozen_call_stack = NULL;
58 }
59 /* }}} */
60
zend_generator_freeze_call_stack(zend_execute_data * execute_data)61 ZEND_API zend_execute_data* zend_generator_freeze_call_stack(zend_execute_data *execute_data) /* {{{ */
62 {
63 size_t used_stack;
64 zend_execute_data *call, *new_call, *prev_call = NULL;
65 zval *stack;
66
67 /* calculate required stack size */
68 used_stack = 0;
69 call = EX(call);
70 do {
71 used_stack += ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call);
72 call = call->prev_execute_data;
73 } while (call);
74
75 stack = emalloc(used_stack * sizeof(zval));
76
77 /* save stack, linking frames in reverse order */
78 call = EX(call);
79 do {
80 size_t frame_size = ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call);
81
82 new_call = (zend_execute_data*)(stack + used_stack - frame_size);
83 memcpy(new_call, call, frame_size * sizeof(zval));
84 used_stack -= frame_size;
85 new_call->prev_execute_data = prev_call;
86 prev_call = new_call;
87
88 new_call = call->prev_execute_data;
89 zend_vm_stack_free_call_frame(call);
90 call = new_call;
91 } while (call);
92
93 execute_data->call = NULL;
94 ZEND_ASSERT(prev_call == (zend_execute_data*)stack);
95
96 return prev_call;
97 }
98 /* }}} */
99
zend_generator_revert_call_stack(zend_execute_data * call)100 static zend_execute_data* zend_generator_revert_call_stack(zend_execute_data *call)
101 {
102 zend_execute_data *prev = NULL;
103
104 do {
105 zend_execute_data *next = call->prev_execute_data;
106 call->prev_execute_data = prev;
107 prev = call;
108 call = next;
109 } while (call);
110
111 return prev;
112 }
113
zend_generator_cleanup_unfinished_execution(zend_generator * generator,zend_execute_data * execute_data,uint32_t catch_op_num)114 static void zend_generator_cleanup_unfinished_execution(
115 zend_generator *generator, zend_execute_data *execute_data, uint32_t catch_op_num) /* {{{ */
116 {
117 zend_op_array *op_array = &execute_data->func->op_array;
118 if (execute_data->opline != op_array->opcodes) {
119 uint32_t op_num = execute_data->opline - op_array->opcodes;
120
121 if (UNEXPECTED(generator->frozen_call_stack)) {
122 /* Temporarily restore generator->execute_data if it has been NULLed out already. */
123 zend_execute_data *save_ex = generator->execute_data;
124 generator->execute_data = execute_data;
125 zend_generator_restore_call_stack(generator);
126 generator->execute_data = save_ex;
127 }
128
129 zend_cleanup_unfinished_execution(execute_data, op_num, catch_op_num);
130 }
131 }
132 /* }}} */
133
zend_generator_close(zend_generator * generator,bool finished_execution)134 ZEND_API void zend_generator_close(zend_generator *generator, bool finished_execution) /* {{{ */
135 {
136 if (EXPECTED(generator->execute_data)) {
137 zend_execute_data *execute_data = generator->execute_data;
138 /* Null out execute_data early, to prevent double frees if GC runs while we're
139 * already cleaning up execute_data. */
140 generator->execute_data = NULL;
141
142 if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) {
143 zend_clean_and_cache_symbol_table(execute_data->symbol_table);
144 }
145 /* always free the CV's, in the symtable are only not-free'd IS_INDIRECT's */
146 zend_free_compiled_variables(execute_data);
147 if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
148 zend_free_extra_named_params(execute_data->extra_named_params);
149 }
150
151 if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) {
152 OBJ_RELEASE(Z_OBJ(execute_data->This));
153 }
154
155 /* A fatal error / die occurred during the generator execution.
156 * Trying to clean up the stack may not be safe in this case. */
157 if (UNEXPECTED(CG(unclean_shutdown))) {
158 generator->execute_data = NULL;
159 return;
160 }
161
162 zend_vm_stack_free_extra_args(execute_data);
163
164 /* Some cleanups are only necessary if the generator was closed
165 * before it could finish execution (reach a return statement). */
166 if (UNEXPECTED(!finished_execution)) {
167 zend_generator_cleanup_unfinished_execution(generator, execute_data, 0);
168 }
169
170 efree(execute_data);
171 }
172 }
173 /* }}} */
174
zend_generator_remove_child(zend_generator_node * node,zend_generator * child)175 static void zend_generator_remove_child(zend_generator_node *node, zend_generator *child)
176 {
177 ZEND_ASSERT(node->children >= 1);
178 if (node->children == 1) {
179 node->child.single = NULL;
180 } else {
181 HashTable *ht = node->child.ht;
182 zend_hash_index_del(ht, (zend_ulong) child);
183 if (node->children == 2) {
184 zend_generator *other_child;
185 ZEND_HASH_FOREACH_PTR(ht, other_child) {
186 node->child.single = other_child;
187 break;
188 } ZEND_HASH_FOREACH_END();
189 zend_hash_destroy(ht);
190 efree(ht);
191 }
192 }
193 node->children--;
194 }
195
clear_link_to_leaf(zend_generator * generator)196 static zend_always_inline zend_generator *clear_link_to_leaf(zend_generator *generator) {
197 ZEND_ASSERT(!generator->node.parent);
198 zend_generator *leaf = generator->node.ptr.leaf;
199 if (leaf) {
200 leaf->node.ptr.root = NULL;
201 generator->node.ptr.leaf = NULL;
202 return leaf;
203 }
204 return NULL;
205 }
206
clear_link_to_root(zend_generator * generator)207 static zend_always_inline void clear_link_to_root(zend_generator *generator) {
208 ZEND_ASSERT(generator->node.parent);
209 if (generator->node.ptr.root) {
210 generator->node.ptr.root->node.ptr.leaf = NULL;
211 generator->node.ptr.root = NULL;
212 }
213 }
214
215 /* In the context of zend_generator_dtor_storage during shutdown, check if
216 * the intermediate node 'generator' is running in a fiber */
check_node_running_in_fiber(zend_generator * generator)217 static inline bool check_node_running_in_fiber(zend_generator *generator) {
218 ZEND_ASSERT(EG(flags) & EG_FLAGS_IN_SHUTDOWN);
219 ZEND_ASSERT(generator->execute_data);
220
221 if (generator->flags & ZEND_GENERATOR_IN_FIBER) {
222 return true;
223 }
224
225 if (generator->node.children == 0) {
226 return false;
227 }
228
229 if (generator->flags & ZEND_GENERATOR_DTOR_VISITED) {
230 return false;
231 }
232 generator->flags |= ZEND_GENERATOR_DTOR_VISITED;
233
234 if (generator->node.children == 1) {
235 if (check_node_running_in_fiber(generator->node.child.single)) {
236 goto in_fiber;
237 }
238 return false;
239 }
240
241 zend_generator *child;
242 ZEND_HASH_FOREACH_PTR(generator->node.child.ht, child) {
243 if (check_node_running_in_fiber(child)) {
244 goto in_fiber;
245 }
246 } ZEND_HASH_FOREACH_END();
247 return false;
248
249 in_fiber:
250 generator->flags |= ZEND_GENERATOR_IN_FIBER;
251 return true;
252 }
253
zend_generator_dtor_storage(zend_object * object)254 static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
255 {
256 zend_generator *generator = (zend_generator*) object;
257 zend_generator *current_generator = zend_generator_get_current(generator);
258 zend_execute_data *ex = generator->execute_data;
259 uint32_t op_num, try_catch_offset;
260 int i;
261
262 /* If current_generator is running in a fiber, there are 2 cases to consider:
263 * - If generator is also marked with ZEND_GENERATOR_IN_FIBER, then the
264 * entire path from current_generator to generator is executing in a
265 * fiber. Do not dtor now: These will be dtor when terminating the fiber.
266 * - If generator is not marked with ZEND_GENERATOR_IN_FIBER, and has a
267 * child marked with ZEND_GENERATOR_IN_FIBER, then this an intermediate
268 * node of case 1. Otherwise generator is not executing in a fiber and we
269 * can dtor.
270 */
271 if (current_generator->flags & ZEND_GENERATOR_IN_FIBER) {
272 if (check_node_running_in_fiber(generator)) {
273 /* Prevent finally blocks from yielding */
274 generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
275 return;
276 }
277 }
278
279 /* leave yield from mode to properly allow finally execution */
280 if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
281 zval_ptr_dtor(&generator->values);
282 ZVAL_UNDEF(&generator->values);
283 }
284
285 zend_generator *parent = generator->node.parent;
286 if (parent) {
287 zend_generator_remove_child(&parent->node, generator);
288 clear_link_to_root(generator);
289 generator->node.parent = NULL;
290 OBJ_RELEASE(&parent->std);
291 } else {
292 clear_link_to_leaf(generator);
293 }
294
295 if (EXPECTED(!ex) || EXPECTED(!(ex->func->op_array.fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK))
296 || CG(unclean_shutdown)) {
297 zend_generator_close(generator, 0);
298 return;
299 }
300
301 op_num = ex->opline - ex->func->op_array.opcodes;
302 try_catch_offset = -1;
303
304 /* Find the innermost try/catch that we are inside of. */
305 for (i = 0; i < ex->func->op_array.last_try_catch; i++) {
306 zend_try_catch_element *try_catch = &ex->func->op_array.try_catch_array[i];
307 if (op_num < try_catch->try_op) {
308 break;
309 }
310 if (op_num < try_catch->catch_op || op_num < try_catch->finally_end) {
311 try_catch_offset = i;
312 }
313 }
314
315 /* Walk try/catch/finally structures upwards, performing the necessary actions. */
316 while (try_catch_offset != (uint32_t) -1) {
317 zend_try_catch_element *try_catch = &ex->func->op_array.try_catch_array[try_catch_offset];
318
319 if (op_num < try_catch->finally_op) {
320 /* Go to finally block */
321 zval *fast_call =
322 ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[try_catch->finally_end].op1.var);
323
324 zend_generator_cleanup_unfinished_execution(generator, ex, try_catch->finally_op);
325 zend_object *old_exception = EG(exception);
326 const zend_op *old_opline_before_exception = EG(opline_before_exception);
327 EG(exception) = NULL;
328 Z_OBJ_P(fast_call) = NULL;
329 Z_OPLINE_NUM_P(fast_call) = (uint32_t)-1;
330
331 /* -1 because zend_generator_resume() will increment it */
332 ex->opline = &ex->func->op_array.opcodes[try_catch->finally_op] - 1;
333 generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
334 zend_generator_resume(generator);
335
336 if (old_exception) {
337 EG(opline_before_exception) = old_opline_before_exception;
338 if (EG(exception)) {
339 zend_exception_set_previous(EG(exception), old_exception);
340 } else {
341 EG(exception) = old_exception;
342 }
343 }
344
345 /* TODO: If we hit another yield inside try/finally,
346 * should we also jump to the next finally block? */
347 break;
348 } else if (op_num < try_catch->finally_end) {
349 zval *fast_call =
350 ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[try_catch->finally_end].op1.var);
351 /* Clean up incomplete return statement */
352 if (Z_OPLINE_NUM_P(fast_call) != (uint32_t) -1) {
353 zend_op *retval_op = &ex->func->op_array.opcodes[Z_OPLINE_NUM_P(fast_call)];
354 if (retval_op->op2_type & (IS_TMP_VAR | IS_VAR)) {
355 zval_ptr_dtor(ZEND_CALL_VAR(ex, retval_op->op2.var));
356 }
357 }
358 /* Clean up backed-up exception */
359 if (Z_OBJ_P(fast_call)) {
360 OBJ_RELEASE(Z_OBJ_P(fast_call));
361 }
362 }
363
364 try_catch_offset--;
365 }
366
367 zend_generator_close(generator, 0);
368 }
369 /* }}} */
370
zend_generator_free_storage(zend_object * object)371 static void zend_generator_free_storage(zend_object *object) /* {{{ */
372 {
373 zend_generator *generator = (zend_generator*) object;
374
375 zend_generator_close(generator, 0);
376
377 if (generator->func && (generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
378 OBJ_RELEASE(ZEND_CLOSURE_OBJECT(generator->func));
379 }
380
381 /* we can't immediately free them in zend_generator_close() else yield from won't be able to fetch it */
382 zval_ptr_dtor(&generator->value);
383 zval_ptr_dtor(&generator->key);
384
385 if (EXPECTED(!Z_ISUNDEF(generator->retval))) {
386 zval_ptr_dtor(&generator->retval);
387 }
388
389 if (UNEXPECTED(generator->node.children > 1)) {
390 zend_hash_destroy(generator->node.child.ht);
391 efree(generator->node.child.ht);
392 }
393
394 zend_object_std_dtor(&generator->std);
395 }
396 /* }}} */
397
zend_generator_frame_gc(zend_get_gc_buffer * gc_buffer,zend_generator * generator)398 HashTable *zend_generator_frame_gc(zend_get_gc_buffer *gc_buffer, zend_generator *generator)
399 {
400 zend_execute_data *execute_data = generator->execute_data;
401 zend_execute_data *call = NULL;
402
403 zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
404 zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
405 zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
406 zend_get_gc_buffer_add_zval(gc_buffer, &generator->values);
407
408 if (UNEXPECTED(generator->frozen_call_stack)) {
409 /* The frozen stack is linked in reverse order */
410 call = zend_generator_revert_call_stack(generator->frozen_call_stack);
411 }
412
413 HashTable *ht = zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, true);
414
415 if (UNEXPECTED(generator->frozen_call_stack)) {
416 zend_generator_revert_call_stack(call);
417 }
418
419 if (generator->node.parent) {
420 zend_get_gc_buffer_add_obj(gc_buffer, &generator->node.parent->std);
421 }
422
423 return ht;
424 }
425
zend_generator_get_gc(zend_object * object,zval ** table,int * n)426 static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *n) /* {{{ */
427 {
428 zend_generator *generator = (zend_generator*)object;
429 zend_execute_data *execute_data = generator->execute_data;
430
431 if (!execute_data) {
432 if (UNEXPECTED(generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
433 zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
434 zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
435 zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
436 zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
437 zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(generator->func));
438 zend_get_gc_buffer_use(gc_buffer, table, n);
439 } else {
440 /* If the non-closure generator has been closed, it can only hold on to three values: The value, key
441 * and retval. These three zvals are stored sequentially starting at &generator->value. */
442 *table = &generator->value;
443 *n = 3;
444 }
445 return NULL;
446 }
447
448 if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
449 /* If the generator is currently running, we certainly won't be able to GC any values it
450 * holds on to. The execute_data state might be inconsistent during execution (e.g. because
451 * GC has been triggered in the middle of a variable reassignment), so we should not try
452 * to inspect it here. */
453 *table = NULL;
454 *n = 0;
455 return NULL;
456 }
457
458 zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
459 HashTable *ht = zend_generator_frame_gc(gc_buffer, generator);
460 zend_get_gc_buffer_use(gc_buffer, table, n);
461
462 return ht;
463 }
464 /* }}} */
465
zend_generator_create(zend_class_entry * class_type)466 static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ */
467 {
468 zend_generator *generator = emalloc(sizeof(zend_generator));
469 memset(generator, 0, sizeof(zend_generator));
470
471 /* The key will be incremented on first use, so it'll start at 0 */
472 generator->largest_used_integer_key = -1;
473
474 ZVAL_UNDEF(&generator->retval);
475 ZVAL_UNDEF(&generator->values);
476
477 /* By default we have a tree of only one node */
478 generator->node.parent = NULL;
479 generator->node.children = 0;
480 generator->node.ptr.root = NULL;
481
482 zend_object_std_init(&generator->std, class_type);
483 return (zend_object*)generator;
484 }
485 /* }}} */
486
zend_generator_get_constructor(zend_object * object)487 static ZEND_COLD zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ */
488 {
489 zend_throw_error(NULL, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated");
490
491 return NULL;
492 }
493 /* }}} */
494
zend_generator_check_placeholder_frame(zend_execute_data * ptr)495 ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr)
496 {
497 if (!ptr->func && Z_TYPE(ptr->This) == IS_OBJECT) {
498 if (Z_OBJCE(ptr->This) == zend_ce_generator) {
499 zend_generator *generator = (zend_generator *) Z_OBJ(ptr->This);
500 zend_execute_data *prev = ptr->prev_execute_data;
501 ZEND_ASSERT(generator->node.parent && "Placeholder only used with delegation");
502 while (generator->node.parent->node.parent) {
503 generator->execute_data->prev_execute_data = prev;
504 prev = generator->execute_data;
505 generator = generator->node.parent;
506 }
507 generator->execute_data->prev_execute_data = prev;
508 ptr = generator->execute_data;
509 }
510 }
511 return ptr;
512 }
513
zend_generator_throw_exception(zend_generator * generator,zval * exception)514 static void zend_generator_throw_exception(zend_generator *generator, zval *exception)
515 {
516 zend_execute_data *original_execute_data = EG(current_execute_data);
517
518 /* Throw the exception in the context of the generator. Decrementing the opline
519 * to pretend the exception happened during the YIELD opcode. */
520 EG(current_execute_data) = generator->execute_data;
521 generator->execute_data->prev_execute_data = original_execute_data;
522
523 if (exception) {
524 zend_throw_exception_object(exception);
525 } else {
526 zend_rethrow_exception(EG(current_execute_data));
527 }
528
529 /* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */
530 if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) {
531 zval_ptr_dtor(&generator->values);
532 ZVAL_UNDEF(&generator->values);
533 }
534
535 EG(current_execute_data) = original_execute_data;
536 }
537
zend_generator_add_child(zend_generator * generator,zend_generator * child)538 static void zend_generator_add_child(zend_generator *generator, zend_generator *child)
539 {
540 zend_generator_node *node = &generator->node;
541
542 if (node->children == 0) {
543 node->child.single = child;
544 } else {
545 if (node->children == 1) {
546 HashTable *ht = emalloc(sizeof(HashTable));
547 zend_hash_init(ht, 0, NULL, NULL, 0);
548 zend_hash_index_add_new_ptr(ht,
549 (zend_ulong) node->child.single, node->child.single);
550 node->child.ht = ht;
551 }
552
553 zend_hash_index_add_new_ptr(node->child.ht, (zend_ulong) child, child);
554 }
555
556 ++node->children;
557 }
558
zend_generator_yield_from(zend_generator * generator,zend_generator * from)559 void zend_generator_yield_from(zend_generator *generator, zend_generator *from)
560 {
561 ZEND_ASSERT(!generator->node.parent && "Already has parent?");
562 zend_generator *leaf = clear_link_to_leaf(generator);
563 if (leaf && !from->node.parent && !from->node.ptr.leaf) {
564 from->node.ptr.leaf = leaf;
565 leaf->node.ptr.root = from;
566 }
567 generator->node.parent = from;
568 zend_generator_add_child(from, generator);
569 generator->flags |= ZEND_GENERATOR_DO_INIT;
570 }
571
zend_generator_update_root(zend_generator * generator)572 ZEND_API zend_generator *zend_generator_update_root(zend_generator *generator)
573 {
574 zend_generator *root = generator->node.parent;
575 while (root->node.parent) {
576 root = root->node.parent;
577 }
578
579 clear_link_to_leaf(root);
580 root->node.ptr.leaf = generator;
581 generator->node.ptr.root = root;
582 return root;
583 }
584
get_new_root(zend_generator * generator,zend_generator * root)585 static zend_generator *get_new_root(zend_generator *generator, zend_generator *root)
586 {
587 while (!root->execute_data && root->node.children == 1) {
588 root = root->node.child.single;
589 }
590
591 if (root->execute_data) {
592 return root;
593 }
594
595 /* We have reached a multi-child node haven't found the root yet. We don't know which
596 * child to follow, so perform the search from the other direction instead. */
597 while (generator->node.parent->execute_data) {
598 generator = generator->node.parent;
599 }
600
601 return generator;
602 }
603
zend_generator_update_current(zend_generator * generator)604 ZEND_API zend_generator *zend_generator_update_current(zend_generator *generator)
605 {
606 zend_generator *old_root = generator->node.ptr.root;
607 ZEND_ASSERT(!old_root->execute_data && "Nothing to update?");
608
609 zend_generator *new_root = get_new_root(generator, old_root);
610
611 ZEND_ASSERT(old_root->node.ptr.leaf == generator);
612 generator->node.ptr.root = new_root;
613 new_root->node.ptr.leaf = generator;
614 old_root->node.ptr.leaf = NULL;
615
616 zend_generator *new_root_parent = new_root->node.parent;
617 ZEND_ASSERT(new_root_parent);
618 zend_generator_remove_child(&new_root_parent->node, new_root);
619
620 if (EXPECTED(EG(exception) == NULL) && EXPECTED((OBJ_FLAGS(&generator->std) & IS_OBJ_DESTRUCTOR_CALLED) == 0)) {
621 zend_op *yield_from = (zend_op *) new_root->execute_data->opline;
622
623 if (yield_from->opcode == ZEND_YIELD_FROM) {
624 if (Z_ISUNDEF(new_root_parent->retval)) {
625 /* Throw the exception in the context of the generator */
626 zend_execute_data *original_execute_data = EG(current_execute_data);
627 EG(current_execute_data) = new_root->execute_data;
628
629 if (new_root == generator) {
630 new_root->execute_data->prev_execute_data = original_execute_data;
631 } else {
632 new_root->execute_data->prev_execute_data = &generator->execute_fake;
633 generator->execute_fake.prev_execute_data = original_execute_data;
634 }
635
636 zend_throw_exception(zend_ce_ClosedGeneratorException, "Generator yielded from aborted, no return value available", 0);
637
638 EG(current_execute_data) = original_execute_data;
639
640 if (!(old_root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) {
641 new_root->node.parent = NULL;
642 OBJ_RELEASE(&new_root_parent->std);
643 zend_generator_resume(generator);
644 return zend_generator_get_current(generator);
645 }
646 } else {
647 zval_ptr_dtor(&new_root->value);
648 ZVAL_COPY(&new_root->value, &new_root_parent->value);
649 ZVAL_COPY(ZEND_CALL_VAR(new_root->execute_data, yield_from->result.var), &new_root_parent->retval);
650 }
651 }
652 }
653
654 new_root->node.parent = NULL;
655 OBJ_RELEASE(&new_root_parent->std);
656
657 return new_root;
658 }
659
zend_generator_get_next_delegated_value(zend_generator * generator)660 static zend_result zend_generator_get_next_delegated_value(zend_generator *generator) /* {{{ */
661 {
662 zval *value;
663 if (Z_TYPE(generator->values) == IS_ARRAY) {
664 HashTable *ht = Z_ARR(generator->values);
665 HashPosition pos = Z_FE_POS(generator->values);
666
667 if (HT_IS_PACKED(ht)) {
668 do {
669 if (UNEXPECTED(pos >= ht->nNumUsed)) {
670 /* Reached end of array */
671 goto failure;
672 }
673
674 value = &ht->arPacked[pos];
675 pos++;
676 } while (Z_ISUNDEF_P(value));
677
678 zval_ptr_dtor(&generator->value);
679 ZVAL_COPY(&generator->value, value);
680
681 zval_ptr_dtor(&generator->key);
682 ZVAL_LONG(&generator->key, pos - 1);
683 } else {
684 Bucket *p;
685
686 do {
687 if (UNEXPECTED(pos >= ht->nNumUsed)) {
688 /* Reached end of array */
689 goto failure;
690 }
691
692 p = &ht->arData[pos];
693 value = &p->val;
694 pos++;
695 } while (Z_ISUNDEF_P(value));
696
697 zval_ptr_dtor(&generator->value);
698 ZVAL_COPY(&generator->value, value);
699
700 zval_ptr_dtor(&generator->key);
701 if (p->key) {
702 ZVAL_STR_COPY(&generator->key, p->key);
703 } else {
704 ZVAL_LONG(&generator->key, p->h);
705 }
706 }
707 Z_FE_POS(generator->values) = pos;
708 } else {
709 zend_object_iterator *iter = (zend_object_iterator *) Z_OBJ(generator->values);
710
711 if (iter->index++ > 0) {
712 iter->funcs->move_forward(iter);
713 if (UNEXPECTED(EG(exception) != NULL)) {
714 goto failure;
715 }
716 }
717
718 if (iter->funcs->valid(iter) == FAILURE) {
719 /* reached end of iteration */
720 goto failure;
721 }
722
723 value = iter->funcs->get_current_data(iter);
724 if (UNEXPECTED(EG(exception) != NULL) || UNEXPECTED(!value)) {
725 goto failure;
726 }
727
728 zval_ptr_dtor(&generator->value);
729 ZVAL_COPY(&generator->value, value);
730
731 zval_ptr_dtor(&generator->key);
732 if (iter->funcs->get_current_key) {
733 iter->funcs->get_current_key(iter, &generator->key);
734 if (UNEXPECTED(EG(exception) != NULL)) {
735 ZVAL_UNDEF(&generator->key);
736 goto failure;
737 }
738 } else {
739 ZVAL_LONG(&generator->key, iter->index);
740 }
741 }
742
743 return SUCCESS;
744
745 failure:
746 zval_ptr_dtor(&generator->values);
747 ZVAL_UNDEF(&generator->values);
748
749 return FAILURE;
750 }
751 /* }}} */
752
zend_generator_resume(zend_generator * orig_generator)753 ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
754 {
755 zend_generator *generator = zend_generator_get_current(orig_generator);
756
757 /* The generator is already closed, thus can't resume */
758 if (UNEXPECTED(!generator->execute_data)) {
759 return;
760 }
761
762 try_again:
763 if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
764 zend_throw_error(NULL, "Cannot resume an already running generator");
765 return;
766 }
767
768 if (UNEXPECTED((orig_generator->flags & ZEND_GENERATOR_DO_INIT) != 0 && !Z_ISUNDEF(generator->value))) {
769 /* We must not advance Generator if we yield from a Generator being currently run */
770 orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
771 return;
772 }
773
774 if (EG(active_fiber)) {
775 orig_generator->flags |= ZEND_GENERATOR_IN_FIBER;
776 generator->flags |= ZEND_GENERATOR_IN_FIBER;
777 }
778
779 /* Drop the AT_FIRST_YIELD flag */
780 orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
781
782 /* Backup executor globals */
783 zend_execute_data *original_execute_data = EG(current_execute_data);
784 uint32_t original_jit_trace_num = EG(jit_trace_num);
785
786 /* Set executor globals */
787 EG(current_execute_data) = generator->execute_data;
788 EG(jit_trace_num) = 0;
789
790 /* We want the backtrace to look as if the generator function was
791 * called from whatever method we are current running (e.g. next()).
792 * So we have to link generator call frame with caller call frame. */
793 if (generator == orig_generator) {
794 generator->execute_data->prev_execute_data = original_execute_data;
795 } else {
796 /* We need some execute_data placeholder in stacktrace to be replaced
797 * by the real stack trace when needed */
798 generator->execute_data->prev_execute_data = &orig_generator->execute_fake;
799 orig_generator->execute_fake.prev_execute_data = original_execute_data;
800 }
801
802 /* Ensure this is run after executor_data swap to have a proper stack trace */
803 if (UNEXPECTED(!Z_ISUNDEF(generator->values))) {
804 if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) {
805 /* Restore executor globals */
806 EG(current_execute_data) = original_execute_data;
807 EG(jit_trace_num) = original_jit_trace_num;
808
809 orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
810 generator->flags &= ~ZEND_GENERATOR_IN_FIBER;
811 return;
812 }
813 /* If there are no more delegated values, resume the generator
814 * after the "yield from" expression. */
815 }
816
817 if (UNEXPECTED(generator->frozen_call_stack)) {
818 /* Restore frozen call-stack */
819 zend_generator_restore_call_stack(generator);
820 }
821
822 /* Resume execution */
823 ZEND_ASSERT(generator->execute_data->opline->opcode == ZEND_GENERATOR_CREATE
824 || generator->execute_data->opline->opcode == ZEND_YIELD
825 || generator->execute_data->opline->opcode == ZEND_YIELD_FROM
826 /* opline points to EG(exception_op), which is a sequence of
827 * ZEND_HANDLE_EXCEPTION ops, so the following increment is safe */
828 || generator->execute_data->opline->opcode == ZEND_HANDLE_EXCEPTION
829 /* opline points to the start of a finally block minus one op to
830 * account for the following increment */
831 || (generator->flags & ZEND_GENERATOR_FORCED_CLOSE));
832 generator->execute_data->opline++;
833 generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
834 if (!ZEND_OBSERVER_ENABLED) {
835 zend_execute_ex(generator->execute_data);
836 } else {
837 zend_observer_generator_resume(generator->execute_data);
838 zend_execute_ex(generator->execute_data);
839 if (generator->execute_data) {
840 /* On the final return, this will be called from ZEND_GENERATOR_RETURN */
841 zend_observer_fcall_end(generator->execute_data, &generator->value);
842 }
843 }
844 generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER);
845
846 generator->frozen_call_stack = NULL;
847 if (EXPECTED(generator->execute_data) &&
848 UNEXPECTED(generator->execute_data->call)) {
849 /* Frize call-stack */
850 generator->frozen_call_stack = zend_generator_freeze_call_stack(generator->execute_data);
851 }
852
853 /* Restore executor globals */
854 EG(current_execute_data) = original_execute_data;
855 EG(jit_trace_num) = original_jit_trace_num;
856
857 /* If an exception was thrown in the generator we have to internally
858 * rethrow it in the parent scope.
859 * In case we did yield from, the Exception must be rethrown into
860 * its calling frame (see above in if (check_yield_from). */
861 if (UNEXPECTED(EG(exception) != NULL)) {
862 if (generator == orig_generator) {
863 zend_generator_close(generator, 0);
864 if (!EG(current_execute_data)) {
865 zend_throw_exception_internal(NULL);
866 } else if (EG(current_execute_data)->func &&
867 ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
868 zend_rethrow_exception(EG(current_execute_data));
869 }
870 } else {
871 generator = zend_generator_get_current(orig_generator);
872 zend_generator_throw_exception(generator, NULL);
873 orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
874 goto try_again;
875 }
876 }
877
878 /* yield from was used, try another resume. */
879 if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && generator->execute_data->opline->opcode == ZEND_YIELD_FROM))) {
880 generator = zend_generator_get_current(orig_generator);
881 goto try_again;
882 }
883
884 orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER);
885 }
886 /* }}} */
887
zend_generator_ensure_initialized(zend_generator * generator)888 static inline void zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */
889 {
890 if (UNEXPECTED(Z_TYPE(generator->value) == IS_UNDEF) && EXPECTED(generator->execute_data) && EXPECTED(generator->node.parent == NULL)) {
891 zend_generator_resume(generator);
892 generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
893 }
894 }
895 /* }}} */
896
zend_generator_rewind(zend_generator * generator)897 static inline void zend_generator_rewind(zend_generator *generator) /* {{{ */
898 {
899 zend_generator_ensure_initialized(generator);
900
901 if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
902 zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0);
903 }
904 }
905 /* }}} */
906
907 /* {{{ Rewind the generator */
ZEND_METHOD(Generator,rewind)908 ZEND_METHOD(Generator, rewind)
909 {
910 zend_generator *generator;
911
912 ZEND_PARSE_PARAMETERS_NONE();
913
914 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
915
916 zend_generator_rewind(generator);
917 }
918 /* }}} */
919
920 /* {{{ Check whether the generator is valid */
ZEND_METHOD(Generator,valid)921 ZEND_METHOD(Generator, valid)
922 {
923 zend_generator *generator;
924
925 ZEND_PARSE_PARAMETERS_NONE();
926
927 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
928
929 zend_generator_ensure_initialized(generator);
930
931 zend_generator_get_current(generator);
932
933 RETURN_BOOL(EXPECTED(generator->execute_data != NULL));
934 }
935 /* }}} */
936
937 /* {{{ Get the current value */
ZEND_METHOD(Generator,current)938 ZEND_METHOD(Generator, current)
939 {
940 zend_generator *generator, *root;
941
942 ZEND_PARSE_PARAMETERS_NONE();
943
944 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
945
946 zend_generator_ensure_initialized(generator);
947
948 root = zend_generator_get_current(generator);
949 if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->value) != IS_UNDEF)) {
950 RETURN_COPY_DEREF(&root->value);
951 }
952 }
953 /* }}} */
954
955 /* {{{ Get the current key */
ZEND_METHOD(Generator,key)956 ZEND_METHOD(Generator, key)
957 {
958 zend_generator *generator, *root;
959
960 ZEND_PARSE_PARAMETERS_NONE();
961
962 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
963
964 zend_generator_ensure_initialized(generator);
965
966 root = zend_generator_get_current(generator);
967 if (EXPECTED(generator->execute_data != NULL && Z_TYPE(root->key) != IS_UNDEF)) {
968 RETURN_COPY_DEREF(&root->key);
969 }
970 }
971 /* }}} */
972
973 /* {{{ Advances the generator */
ZEND_METHOD(Generator,next)974 ZEND_METHOD(Generator, next)
975 {
976 zend_generator *generator;
977
978 ZEND_PARSE_PARAMETERS_NONE();
979
980 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
981
982 zend_generator_ensure_initialized(generator);
983
984 zend_generator_resume(generator);
985 }
986 /* }}} */
987
988 /* {{{ Sends a value to the generator */
ZEND_METHOD(Generator,send)989 ZEND_METHOD(Generator, send)
990 {
991 zval *value;
992 zend_generator *generator, *root;
993
994 ZEND_PARSE_PARAMETERS_START(1, 1)
995 Z_PARAM_ZVAL(value)
996 ZEND_PARSE_PARAMETERS_END();
997
998 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
999
1000 zend_generator_ensure_initialized(generator);
1001
1002 /* The generator is already closed, thus can't send anything */
1003 if (UNEXPECTED(!generator->execute_data)) {
1004 return;
1005 }
1006
1007 root = zend_generator_get_current(generator);
1008 /* Put sent value in the target VAR slot, if it is used */
1009 if (root->send_target && !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) {
1010 ZVAL_COPY(root->send_target, value);
1011 }
1012
1013 zend_generator_resume(generator);
1014
1015 root = zend_generator_get_current(generator);
1016 if (EXPECTED(generator->execute_data)) {
1017 RETURN_COPY_DEREF(&root->value);
1018 }
1019 }
1020 /* }}} */
1021
1022 /* {{{ Throws an exception into the generator */
ZEND_METHOD(Generator,throw)1023 ZEND_METHOD(Generator, throw)
1024 {
1025 zval *exception;
1026 zend_generator *generator;
1027
1028 ZEND_PARSE_PARAMETERS_START(1, 1)
1029 Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable);
1030 ZEND_PARSE_PARAMETERS_END();
1031
1032 Z_TRY_ADDREF_P(exception);
1033
1034 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
1035
1036 zend_generator_ensure_initialized(generator);
1037
1038 if (generator->execute_data) {
1039 zend_generator *root = zend_generator_get_current(generator);
1040
1041 zend_generator_throw_exception(root, exception);
1042
1043 zend_generator_resume(generator);
1044
1045 root = zend_generator_get_current(generator);
1046 if (generator->execute_data) {
1047 RETURN_COPY_DEREF(&root->value);
1048 }
1049 } else {
1050 /* If the generator is already closed throw the exception in the
1051 * current context */
1052 zend_throw_exception_object(exception);
1053 }
1054 }
1055 /* }}} */
1056
1057 /* {{{ Retrieves the return value of the generator */
ZEND_METHOD(Generator,getReturn)1058 ZEND_METHOD(Generator, getReturn)
1059 {
1060 zend_generator *generator;
1061
1062 ZEND_PARSE_PARAMETERS_NONE();
1063
1064 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
1065
1066 zend_generator_ensure_initialized(generator);
1067 if (UNEXPECTED(EG(exception))) {
1068 return;
1069 }
1070
1071 if (Z_ISUNDEF(generator->retval)) {
1072 /* Generator hasn't returned yet -> error! */
1073 zend_throw_exception(NULL,
1074 "Cannot get return value of a generator that hasn't returned", 0);
1075 return;
1076 }
1077
1078 ZVAL_COPY(return_value, &generator->retval);
1079 }
1080 /* }}} */
1081
ZEND_METHOD(Generator,__debugInfo)1082 ZEND_METHOD(Generator, __debugInfo)
1083 {
1084 zend_generator *generator;
1085
1086 ZEND_PARSE_PARAMETERS_NONE();
1087
1088 generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
1089
1090 array_init(return_value);
1091
1092 zend_function *func = generator->func;
1093
1094 zval val;
1095 if (func->common.scope) {
1096 zend_string *class_name = func->common.scope->name;
1097 zend_string *func_name = func->common.function_name;
1098 zend_string *combined = zend_string_concat3(
1099 ZSTR_VAL(class_name), ZSTR_LEN(class_name),
1100 "::", strlen("::"),
1101 ZSTR_VAL(func_name), ZSTR_LEN(func_name)
1102 );
1103 ZVAL_NEW_STR(&val, combined);
1104 } else {
1105 ZVAL_STR_COPY(&val, func->common.function_name);
1106 }
1107
1108 zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_FUNCTION), &val);
1109 }
1110
1111 /* get_iterator implementation */
1112
zend_generator_iterator_dtor(zend_object_iterator * iterator)1113 static void zend_generator_iterator_dtor(zend_object_iterator *iterator) /* {{{ */
1114 {
1115 zval_ptr_dtor(&iterator->data);
1116 }
1117 /* }}} */
1118
zend_generator_iterator_valid(zend_object_iterator * iterator)1119 static zend_result zend_generator_iterator_valid(zend_object_iterator *iterator) /* {{{ */
1120 {
1121 zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
1122
1123 zend_generator_ensure_initialized(generator);
1124
1125 zend_generator_get_current(generator);
1126
1127 return generator->execute_data ? SUCCESS : FAILURE;
1128 }
1129 /* }}} */
1130
zend_generator_iterator_get_data(zend_object_iterator * iterator)1131 static zval *zend_generator_iterator_get_data(zend_object_iterator *iterator) /* {{{ */
1132 {
1133 zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root;
1134
1135 zend_generator_ensure_initialized(generator);
1136
1137 root = zend_generator_get_current(generator);
1138
1139 return &root->value;
1140 }
1141 /* }}} */
1142
zend_generator_iterator_get_key(zend_object_iterator * iterator,zval * key)1143 static void zend_generator_iterator_get_key(zend_object_iterator *iterator, zval *key) /* {{{ */
1144 {
1145 zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data), *root;
1146
1147 zend_generator_ensure_initialized(generator);
1148
1149 root = zend_generator_get_current(generator);
1150
1151 if (EXPECTED(Z_TYPE(root->key) != IS_UNDEF)) {
1152 zval *zv = &root->key;
1153
1154 ZVAL_COPY_DEREF(key, zv);
1155 } else {
1156 ZVAL_NULL(key);
1157 }
1158 }
1159 /* }}} */
1160
zend_generator_iterator_move_forward(zend_object_iterator * iterator)1161 static void zend_generator_iterator_move_forward(zend_object_iterator *iterator) /* {{{ */
1162 {
1163 zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
1164
1165 zend_generator_ensure_initialized(generator);
1166
1167 zend_generator_resume(generator);
1168 }
1169 /* }}} */
1170
zend_generator_iterator_rewind(zend_object_iterator * iterator)1171 static void zend_generator_iterator_rewind(zend_object_iterator *iterator) /* {{{ */
1172 {
1173 zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
1174
1175 zend_generator_rewind(generator);
1176 }
1177 /* }}} */
1178
zend_generator_iterator_get_gc(zend_object_iterator * iterator,zval ** table,int * n)1179 static HashTable *zend_generator_iterator_get_gc(
1180 zend_object_iterator *iterator, zval **table, int *n)
1181 {
1182 *table = &iterator->data;
1183 *n = 1;
1184 return NULL;
1185 }
1186
1187 static const zend_object_iterator_funcs zend_generator_iterator_functions = {
1188 zend_generator_iterator_dtor,
1189 zend_generator_iterator_valid,
1190 zend_generator_iterator_get_data,
1191 zend_generator_iterator_get_key,
1192 zend_generator_iterator_move_forward,
1193 zend_generator_iterator_rewind,
1194 NULL,
1195 zend_generator_iterator_get_gc,
1196 };
1197
1198 /* by_ref is int due to Iterator API */
zend_generator_get_iterator(zend_class_entry * ce,zval * object,int by_ref)1199 static zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
1200 {
1201 zend_object_iterator *iterator;
1202 zend_generator *generator = (zend_generator*)Z_OBJ_P(object);
1203
1204 if (!generator->execute_data) {
1205 zend_throw_exception(NULL, "Cannot traverse an already closed generator", 0);
1206 return NULL;
1207 }
1208
1209 if (UNEXPECTED(by_ref) && !(generator->execute_data->func->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
1210 zend_throw_exception(NULL, "You can only iterate a generator by-reference if it declared that it yields by-reference", 0);
1211 return NULL;
1212 }
1213
1214 iterator = emalloc(sizeof(zend_object_iterator));
1215 zend_iterator_init(iterator);
1216
1217 iterator->funcs = &zend_generator_iterator_functions;
1218 ZVAL_OBJ_COPY(&iterator->data, Z_OBJ_P(object));
1219
1220 return iterator;
1221 }
1222 /* }}} */
1223
zend_register_generator_ce(void)1224 void zend_register_generator_ce(void) /* {{{ */
1225 {
1226 zend_ce_generator = register_class_Generator(zend_ce_iterator);
1227 zend_ce_generator->create_object = zend_generator_create;
1228 /* get_iterator has to be assigned *after* implementing the interface */
1229 zend_ce_generator->get_iterator = zend_generator_get_iterator;
1230 zend_ce_generator->default_object_handlers = &zend_generator_handlers;
1231
1232 memcpy(&zend_generator_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1233 zend_generator_handlers.free_obj = zend_generator_free_storage;
1234 zend_generator_handlers.dtor_obj = zend_generator_dtor_storage;
1235 zend_generator_handlers.get_gc = zend_generator_get_gc;
1236 zend_generator_handlers.clone_obj = NULL;
1237 zend_generator_handlers.get_constructor = zend_generator_get_constructor;
1238
1239 zend_ce_ClosedGeneratorException = register_class_ClosedGeneratorException(zend_ce_exception);
1240 }
1241 /* }}} */
1242