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