1/*
2   +----------------------------------------------------------------------+
3   | Zend Engine                                                          |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2015 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   +----------------------------------------------------------------------+
17*/
18
19/* $Id$ */
20
21#include "zend.h"
22#include "zend_API.h"
23#include "zend_interfaces.h"
24#include "zend_exceptions.h"
25#include "zend_generators.h"
26
27ZEND_API zend_class_entry *zend_ce_generator;
28static zend_object_handlers zend_generator_handlers;
29
30static zend_object *zend_generator_create(zend_class_entry *class_type);
31
32static void zend_generator_cleanup_unfinished_execution(zend_generator *generator) /* {{{ */
33{
34    zend_execute_data *execute_data = generator->execute_data;
35    zend_op_array *op_array = &execute_data->func->op_array;
36
37    if (generator->send_target) {
38        if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target);
39        generator->send_target = NULL;
40    }
41
42    /* Manually free loop variables, as execution couldn't reach their
43     * SWITCH_FREE / FREE opcodes. */
44    {
45        /* -1 required because we want the last run opcode, not the
46         * next to-be-run one. */
47        uint32_t op_num = execute_data->opline - op_array->opcodes - 1;
48
49        int i;
50        for (i = 0; i < op_array->last_brk_cont; ++i) {
51            zend_brk_cont_element *brk_cont = op_array->brk_cont_array + i;
52
53            if (brk_cont->start < 0) {
54                continue;
55            } else if ((uint32_t)brk_cont->start > op_num) {
56                break;
57            } else if (brk_cont->brk >= 0 && (uint32_t)brk_cont->brk > op_num) {
58                zend_op *brk_opline = op_array->opcodes + brk_cont->brk;
59
60                if (brk_opline->opcode == ZEND_FREE) {
61                    zval *var = EX_VAR(brk_opline->op1.var);
62                    zval_ptr_dtor_nogc(var);
63                } else if (brk_opline->opcode == ZEND_FE_FREE) {
64                    zval *var = EX_VAR(brk_opline->op1.var);
65                    if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
66                        zend_hash_iterator_del(Z_FE_ITER_P(var));
67                    }
68                    zval_ptr_dtor_nogc(var);
69                }
70            }
71        }
72    }
73
74    /* If yield was used as a function argument there may be active
75     * method calls those objects need to be freed */
76    while (execute_data->call) {
77        if (Z_OBJ(execute_data->call->This)) {
78            OBJ_RELEASE(Z_OBJ(execute_data->call->This));
79        }
80        execute_data->call = execute_data->call->prev_execute_data;
81    }
82}
83/* }}} */
84
85ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished_execution) /* {{{ */
86{
87    if (Z_TYPE(generator->value) != IS_UNDEF) {
88        zval_ptr_dtor(&generator->value);
89        ZVAL_UNDEF(&generator->value);
90    }
91
92    if (Z_TYPE(generator->key) != IS_UNDEF) {
93        zval_ptr_dtor(&generator->key);
94        ZVAL_UNDEF(&generator->key);
95    }
96
97    if (generator->execute_data) {
98        zend_execute_data *execute_data = generator->execute_data;
99        zend_op_array *op_array = &execute_data->func->op_array;
100
101        if (!execute_data->symbol_table) {
102            zend_free_compiled_variables(execute_data);
103        } else {
104            zend_clean_and_cache_symbol_table(execute_data->symbol_table);
105        }
106
107        if (Z_OBJ(execute_data->This)) {
108            OBJ_RELEASE(Z_OBJ(execute_data->This));
109        }
110
111        /* A fatal error / die occurred during the generator execution. Trying to clean
112         * up the stack may not be safe in this case. */
113        if (CG(unclean_shutdown)) {
114            generator->execute_data = NULL;
115            return;
116        }
117
118        zend_vm_stack_free_extra_args(generator->execute_data);
119
120        /* Some cleanups are only necessary if the generator was closued
121         * before it could finish execution (reach a return statement). */
122        if (!finished_execution) {
123            zend_generator_cleanup_unfinished_execution(generator);
124        }
125
126        /* Free a clone of closure */
127        if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
128            destroy_op_array(op_array);
129            efree_size(op_array, sizeof(zend_op_array));
130        }
131
132        efree(generator->stack);
133        generator->execute_data = NULL;
134    }
135}
136/* }}} */
137
138static void zend_generator_dtor_storage(zend_object *object) /* {{{ */
139{
140    zend_generator *generator = (zend_generator*) object;
141    zend_execute_data *ex = generator->execute_data;
142    uint32_t op_num, finally_op_num, finally_op_end;
143    int i;
144
145    if (!ex || !(ex->func->op_array.fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
146        return;
147    }
148
149    /* -1 required because we want the last run opcode, not the
150     * next to-be-run one. */
151    op_num = ex->opline - ex->func->op_array.opcodes - 1;
152
153    /* Find next finally block */
154    finally_op_num = 0;
155    finally_op_end = 0;
156    for (i = 0; i < ex->func->op_array.last_try_catch; i++) {
157        zend_try_catch_element *try_catch = &ex->func->op_array.try_catch_array[i];
158
159        if (op_num < try_catch->try_op) {
160            break;
161        }
162
163        if (op_num < try_catch->finally_op) {
164            finally_op_num = try_catch->finally_op;
165            finally_op_end = try_catch->finally_end;
166        }
167    }
168
169    /* If a finally block was found we jump directly to it and
170     * resume the generator. */
171    if (finally_op_num) {
172        zval *fast_call = ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[finally_op_end].op1.var);
173
174        Z_OBJ_P(fast_call) = NULL;
175        fast_call->u2.lineno = (uint32_t)-1;
176        ex->opline = &ex->func->op_array.opcodes[finally_op_num];
177        generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
178        zend_generator_resume(generator);
179    }
180}
181/* }}} */
182
183static void zend_generator_free_storage(zend_object *object) /* {{{ */
184{
185    zend_generator *generator = (zend_generator*) object;
186
187    zend_generator_close(generator, 0);
188
189    zend_object_std_dtor(&generator->std);
190
191    if (generator->iterator) {
192        zend_iterator_dtor(generator->iterator);
193    }
194}
195/* }}} */
196
197static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ */
198{
199    zend_generator *generator;
200
201    generator = emalloc(sizeof(zend_generator));
202    memset(generator, 0, sizeof(zend_generator));
203
204    /* The key will be incremented on first use, so it'll start at 0 */
205    generator->largest_used_integer_key = -1;
206
207    zend_object_std_init(&generator->std, class_type);
208    generator->std.handlers = &zend_generator_handlers;
209
210    return (zend_object*)generator;
211}
212/* }}} */
213
214static int copy_closure_static_var(zval *var, int num_args, va_list args, zend_hash_key *key) /* {{{ */
215{
216    HashTable *target = va_arg(args, HashTable *);
217
218    ZVAL_MAKE_REF(var);
219    Z_ADDREF_P(var);
220    zend_hash_update(target, key->key, var);
221    return 0;
222}
223/* }}} */
224
225/* Requires globals EG(scope), EG(This) and EG(current_execute_data). */
226ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array *op_array, zval *return_value) /* {{{ */
227{
228    zend_generator *generator;
229    zend_execute_data *current_execute_data;
230    zend_execute_data *execute_data;
231    zend_vm_stack current_stack = EG(vm_stack);
232
233    current_stack->top = EG(vm_stack_top);
234    /* Create a clone of closure, because it may be destroyed */
235    if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
236        zend_op_array *op_array_copy = (zend_op_array*)emalloc(sizeof(zend_op_array));
237        *op_array_copy = *op_array;
238
239        if (op_array->refcount) {
240            (*op_array->refcount)++;
241        }
242        op_array->run_time_cache = NULL;
243        if (op_array->static_variables) {
244            ALLOC_HASHTABLE(op_array_copy->static_variables);
245            zend_hash_init(
246                op_array_copy->static_variables,
247                zend_hash_num_elements(op_array->static_variables),
248                NULL, ZVAL_PTR_DTOR, 0
249            );
250            zend_hash_apply_with_arguments(
251                op_array->static_variables,
252                copy_closure_static_var, 1,
253                op_array_copy->static_variables
254            );
255        }
256
257        op_array = op_array_copy;
258    }
259
260    /* Create new execution context. We have to back up and restore
261     * EG(current_execute_data) here. */
262    current_execute_data = EG(current_execute_data);
263    execute_data = zend_create_generator_execute_data(call, op_array, return_value);
264    EG(current_execute_data) = current_execute_data;
265
266    object_init_ex(return_value, zend_ce_generator);
267
268    if (Z_OBJ(call->This)) {
269        Z_ADDREF(call->This);
270    }
271
272    /* Save execution context in generator object. */
273    generator = (zend_generator *) Z_OBJ_P(return_value);
274    execute_data->prev_execute_data = NULL;
275    generator->execute_data = execute_data;
276    generator->stack = EG(vm_stack);
277    generator->stack->top = EG(vm_stack_top);
278    EG(vm_stack_top) = current_stack->top;
279    EG(vm_stack_end) = current_stack->end;
280    EG(vm_stack) = current_stack;
281
282    /* EX(return_value) keeps pointer to zend_object (not a real zval) */
283    execute_data->return_value = (zval*)generator;
284}
285/* }}} */
286
287static zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ */
288{
289    zend_error(E_RECOVERABLE_ERROR, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated");
290
291    return NULL;
292}
293/* }}} */
294
295ZEND_API void zend_generator_resume(zend_generator *generator) /* {{{ */
296{
297    /* The generator is already closed, thus can't resume */
298    if (!generator->execute_data) {
299        return;
300    }
301
302    if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
303        zend_error(E_ERROR, "Cannot resume an already running generator");
304    }
305
306    /* Drop the AT_FIRST_YIELD flag */
307    generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
308
309    {
310        /* Backup executor globals */
311        zend_execute_data *original_execute_data = EG(current_execute_data);
312        zend_class_entry *original_scope = EG(scope);
313        zend_vm_stack original_stack = EG(vm_stack);
314        original_stack->top = EG(vm_stack_top);
315
316        /* Set executor globals */
317        EG(current_execute_data) = generator->execute_data;
318        EG(scope) = generator->execute_data->func->common.scope;
319        EG(vm_stack_top) = generator->stack->top;
320        EG(vm_stack_end) = generator->stack->end;
321        EG(vm_stack) = generator->stack;
322
323        /* We want the backtrace to look as if the generator function was
324         * called from whatever method we are current running (e.g. next()).
325         * So we have to link generator call frame with caller call frame. */
326        generator->execute_data->prev_execute_data = original_execute_data;
327
328        /* Resume execution */
329        generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
330        zend_execute_ex(generator->execute_data);
331        generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
332
333        /* Unlink generator call_frame from the caller and backup vm_stack_top */
334        if (generator->execute_data) {
335            generator->stack = EG(vm_stack);
336            generator->stack->top = EG(vm_stack_top);
337            generator->execute_data->prev_execute_data = NULL;
338        }
339
340        /* Restore executor globals */
341        EG(current_execute_data) = original_execute_data;
342        EG(scope) = original_scope;
343        EG(vm_stack_top) = original_stack->top;
344        EG(vm_stack_end) = original_stack->end;
345        EG(vm_stack) = original_stack;
346
347        /* If an exception was thrown in the generator we have to internally
348         * rethrow it in the parent scope. */
349        if (UNEXPECTED(EG(exception) != NULL)) {
350            zend_throw_exception_internal(NULL);
351        }
352    }
353}
354/* }}} */
355
356static void zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */
357{
358    if (generator->execute_data && Z_TYPE(generator->value) == IS_UNDEF) {
359        zend_generator_resume(generator);
360        generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
361    }
362}
363/* }}} */
364
365static void zend_generator_rewind(zend_generator *generator) /* {{{ */
366{
367    zend_generator_ensure_initialized(generator);
368
369    if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
370        zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0);
371    }
372}
373/* }}} */
374
375/* {{{ proto void Generator::rewind()
376 * Rewind the generator */
377ZEND_METHOD(Generator, rewind)
378{
379    zend_generator *generator;
380
381    if (zend_parse_parameters_none() == FAILURE) {
382        return;
383    }
384
385    generator = (zend_generator *) Z_OBJ_P(getThis());
386
387    zend_generator_rewind(generator);
388}
389/* }}} */
390
391/* {{{ proto bool Generator::valid()
392 * Check whether the generator is valid */
393ZEND_METHOD(Generator, valid)
394{
395    zend_generator *generator;
396
397    if (zend_parse_parameters_none() == FAILURE) {
398        return;
399    }
400
401    generator = (zend_generator *) Z_OBJ_P(getThis());
402
403    zend_generator_ensure_initialized(generator);
404
405    RETURN_BOOL(Z_TYPE(generator->value) != IS_UNDEF);
406}
407/* }}} */
408
409/* {{{ proto mixed Generator::current()
410 * Get the current value */
411ZEND_METHOD(Generator, current)
412{
413    zend_generator *generator;
414
415    if (zend_parse_parameters_none() == FAILURE) {
416        return;
417    }
418
419    generator = (zend_generator *) Z_OBJ_P(getThis());
420
421    zend_generator_ensure_initialized(generator);
422
423    if (Z_TYPE(generator->value) != IS_UNDEF) {
424        RETURN_ZVAL_FAST(&generator->value);
425    }
426}
427/* }}} */
428
429/* {{{ proto mixed Generator::key()
430 * Get the current key */
431ZEND_METHOD(Generator, key)
432{
433    zend_generator *generator;
434
435    if (zend_parse_parameters_none() == FAILURE) {
436        return;
437    }
438
439    generator = (zend_generator *) Z_OBJ_P(getThis());
440
441    zend_generator_ensure_initialized(generator);
442
443    if (Z_TYPE(generator->key) != IS_UNDEF) {
444        RETURN_ZVAL_FAST(&generator->key);
445    }
446}
447/* }}} */
448
449/* {{{ proto void Generator::next()
450 * Advances the generator */
451ZEND_METHOD(Generator, next)
452{
453    zend_generator *generator;
454
455    if (zend_parse_parameters_none() == FAILURE) {
456        return;
457    }
458
459    generator = (zend_generator *) Z_OBJ_P(getThis());
460
461    zend_generator_ensure_initialized(generator);
462
463    zend_generator_resume(generator);
464}
465/* }}} */
466
467/* {{{ proto mixed Generator::send(mixed $value)
468 * Sends a value to the generator */
469ZEND_METHOD(Generator, send)
470{
471    zval *value;
472    zend_generator *generator;
473
474    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &value) == FAILURE) {
475        return;
476    }
477
478    generator = (zend_generator *) Z_OBJ_P(getThis());
479
480    zend_generator_ensure_initialized(generator);
481
482    /* The generator is already closed, thus can't send anything */
483    if (!generator->execute_data) {
484        return;
485    }
486
487    /* Put sent value in the target VAR slot, if it is used */
488    if (generator->send_target) {
489        if (Z_REFCOUNTED_P(generator->send_target)) Z_DELREF_P(generator->send_target);
490        ZVAL_COPY(generator->send_target, value);
491    }
492
493    zend_generator_resume(generator);
494
495    if (Z_TYPE(generator->value) != IS_UNDEF) {
496        RETURN_ZVAL_FAST(&generator->value);
497    }
498}
499/* }}} */
500
501/* {{{ proto mixed Generator::throw(Exception $exception)
502 * Throws an exception into the generator */
503ZEND_METHOD(Generator, throw)
504{
505    zval *exception, exception_copy;
506    zend_generator *generator;
507
508    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &exception) == FAILURE) {
509        return;
510    }
511
512    ZVAL_DUP(&exception_copy, exception);
513
514    generator = (zend_generator *) Z_OBJ_P(getThis());
515
516    zend_generator_ensure_initialized(generator);
517
518    if (generator->execute_data) {
519        /* Throw the exception in the context of the generator */
520        zend_execute_data *current_execute_data = EG(current_execute_data);
521        EG(current_execute_data) = generator->execute_data;
522
523        zend_throw_exception_object(&exception_copy);
524
525        EG(current_execute_data) = current_execute_data;
526
527        zend_generator_resume(generator);
528
529        if (Z_TYPE(generator->value) != IS_UNDEF) {
530            RETURN_ZVAL_FAST(&generator->value);
531        }
532    } else {
533        /* If the generator is already closed throw the exception in the
534         * current context */
535        zend_throw_exception_object(&exception_copy);
536    }
537}
538/* }}} */
539
540/* {{{ proto void Generator::__wakeup()
541 * Throws an Exception as generators can't be serialized */
542ZEND_METHOD(Generator, __wakeup)
543{
544    /* Just specifying the zend_class_unserialize_deny handler is not enough,
545     * because it is only invoked for C unserialization. For O the error has
546     * to be thrown in __wakeup. */
547
548    if (zend_parse_parameters_none() == FAILURE) {
549        return;
550    }
551
552    zend_throw_exception(NULL, "Unserialization of 'Generator' is not allowed", 0);
553}
554/* }}} */
555
556/* get_iterator implementation */
557
558static void zend_generator_iterator_dtor(zend_object_iterator *iterator) /* {{{ */
559{
560    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
561    generator->iterator = NULL;
562    zval_ptr_dtor(&iterator->data);
563    zend_iterator_dtor(iterator);
564}
565/* }}} */
566
567static int zend_generator_iterator_valid(zend_object_iterator *iterator) /* {{{ */
568{
569    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
570
571    zend_generator_ensure_initialized(generator);
572
573    return Z_TYPE(generator->value) != IS_UNDEF ? SUCCESS : FAILURE;
574}
575/* }}} */
576
577static zval *zend_generator_iterator_get_data(zend_object_iterator *iterator) /* {{{ */
578{
579    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
580
581    zend_generator_ensure_initialized(generator);
582
583    return &generator->value;
584}
585/* }}} */
586
587static void zend_generator_iterator_get_key(zend_object_iterator *iterator, zval *key) /* {{{ */
588{
589    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
590
591    zend_generator_ensure_initialized(generator);
592
593    if (Z_TYPE(generator->key) != IS_UNDEF) {
594        ZVAL_ZVAL(key, &generator->key, 1, 0);
595    } else {
596        ZVAL_NULL(key);
597    }
598}
599/* }}} */
600
601static void zend_generator_iterator_move_forward(zend_object_iterator *iterator) /* {{{ */
602{
603    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
604
605    zend_generator_ensure_initialized(generator);
606
607    zend_generator_resume(generator);
608}
609/* }}} */
610
611static void zend_generator_iterator_rewind(zend_object_iterator *iterator) /* {{{ */
612{
613    zend_generator *generator = (zend_generator*)Z_OBJ(iterator->data);
614
615    zend_generator_rewind(generator);
616}
617/* }}} */
618
619static zend_object_iterator_funcs zend_generator_iterator_functions = {
620    zend_generator_iterator_dtor,
621    zend_generator_iterator_valid,
622    zend_generator_iterator_get_data,
623    zend_generator_iterator_get_key,
624    zend_generator_iterator_move_forward,
625    zend_generator_iterator_rewind
626};
627
628zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
629{
630    zend_object_iterator *iterator;
631    zend_generator *generator = (zend_generator*)Z_OBJ_P(object);
632
633    if (!generator->execute_data) {
634        zend_throw_exception(NULL, "Cannot traverse an already closed generator", 0);
635        return NULL;
636    }
637
638    if (by_ref && !(generator->execute_data->func->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
639        zend_throw_exception(NULL, "You can only iterate a generator by-reference if it declared that it yields by-reference", 0);
640        return NULL;
641    }
642
643    iterator = generator->iterator = emalloc(sizeof(zend_object_iterator));
644
645    zend_iterator_init(iterator);
646
647    iterator->funcs = &zend_generator_iterator_functions;
648    ZVAL_COPY(&iterator->data, object);
649
650    return iterator;
651}
652/* }}} */
653
654ZEND_BEGIN_ARG_INFO(arginfo_generator_void, 0)
655ZEND_END_ARG_INFO()
656
657ZEND_BEGIN_ARG_INFO_EX(arginfo_generator_send, 0, 0, 1)
658    ZEND_ARG_INFO(0, value)
659ZEND_END_ARG_INFO()
660
661ZEND_BEGIN_ARG_INFO_EX(arginfo_generator_throw, 0, 0, 1)
662    ZEND_ARG_INFO(0, exception)
663ZEND_END_ARG_INFO()
664
665static const zend_function_entry generator_functions[] = {
666    ZEND_ME(Generator, rewind,   arginfo_generator_void, ZEND_ACC_PUBLIC)
667    ZEND_ME(Generator, valid,    arginfo_generator_void, ZEND_ACC_PUBLIC)
668    ZEND_ME(Generator, current,  arginfo_generator_void, ZEND_ACC_PUBLIC)
669    ZEND_ME(Generator, key,      arginfo_generator_void, ZEND_ACC_PUBLIC)
670    ZEND_ME(Generator, next,     arginfo_generator_void, ZEND_ACC_PUBLIC)
671    ZEND_ME(Generator, send,     arginfo_generator_send, ZEND_ACC_PUBLIC)
672    ZEND_ME(Generator, throw,    arginfo_generator_throw, ZEND_ACC_PUBLIC)
673    ZEND_ME(Generator, __wakeup, arginfo_generator_void, ZEND_ACC_PUBLIC)
674    ZEND_FE_END
675};
676
677void zend_register_generator_ce(void) /* {{{ */
678{
679    zend_class_entry ce;
680
681    INIT_CLASS_ENTRY(ce, "Generator", generator_functions);
682    zend_ce_generator = zend_register_internal_class(&ce);
683    zend_ce_generator->ce_flags |= ZEND_ACC_FINAL;
684    zend_ce_generator->create_object = zend_generator_create;
685    zend_ce_generator->serialize = zend_class_serialize_deny;
686    zend_ce_generator->unserialize = zend_class_unserialize_deny;
687
688    /* get_iterator has to be assigned *after* implementing the inferface */
689    zend_class_implements(zend_ce_generator, 1, zend_ce_iterator);
690    zend_ce_generator->get_iterator = zend_generator_get_iterator;
691    zend_ce_generator->iterator_funcs.funcs = &zend_generator_iterator_functions;
692
693    memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
694    zend_generator_handlers.free_obj = zend_generator_free_storage;
695    zend_generator_handlers.dtor_obj = zend_generator_dtor_storage;
696    zend_generator_handlers.clone_obj = NULL;
697    zend_generator_handlers.get_constructor = zend_generator_get_constructor;
698}
699/* }}} */
700
701/*
702 * Local variables:
703 * tab-width: 4
704 * c-basic-offset: 4
705 * indent-tabs-mode: t
706 * End:
707 */
708