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