Coverage Report

Created: 2025-09-27 06:26

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