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