Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 839 lines (675 sloc) 27.213 kB
ca59e54 @nikic Add empty Generator class
nikic authored
1 /*
2 +----------------------------------------------------------------------+
3 | Zend Engine |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2012 Zend Technologies Ltd. (http://www.zend.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 2.00 of the Zend license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.zend.com/license/2_00.txt. |
11 | If you did not receive a copy of the Zend license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@zend.com so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Nikita Popov <nikic@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* $Id$ */
20
21 #include "zend.h"
22 #include "zend_API.h"
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
23 #include "zend_interfaces.h"
94b2cca @nikic Fix throwing of exceptions within a generator
nikic authored
24 #include "zend_exceptions.h"
ca59e54 @nikic Add empty Generator class
nikic authored
25 #include "zend_generators.h"
26
27 ZEND_API zend_class_entry *zend_ce_generator;
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
28 static zend_object_handlers zend_generator_handlers;
29
64a643a @nikic Free loop variables
nikic authored
30 void zend_generator_close(zend_generator *generator, zend_bool finished_execution TSRMLS_DC) /* {{{ */
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
31 {
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
32 if (generator->execute_data) {
33 zend_execute_data *execute_data = generator->execute_data;
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
34 zend_op_array *op_array = execute_data->op_array;
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
35
4d8edda @nikic Run finally if generator is closed before finishing
nikic authored
36 if (!finished_execution) {
37 if (op_array->has_finally_block) {
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
38 /* -1 required because we want the last run opcode, not the
39 * next to-be-run one. */
40 zend_uint op_num = execute_data->opline - op_array->opcodes - 1;
4d8edda @nikic Run finally if generator is closed before finishing
nikic authored
41 zend_uint finally_op_num = 0;
42
43 /* Find next finally block */
44 int i;
45 for (i = 0; i < op_array->last_try_catch; i++) {
46 zend_try_catch_element *try_catch = &op_array->try_catch_array[i];
47
48 if (op_num < try_catch->try_op) {
49 break;
50 }
51
52 if (op_num < try_catch->finally_op) {
53 finally_op_num = try_catch->finally_op;
54 }
55 }
56
57 /* If a finally block was found we jump directly to it and
58 * resume the generator. Furthermore we abort this close call
59 * because the generator will already be closed somewhere in
60 * the resume. */
61 if (finally_op_num) {
62 execute_data->opline = &op_array->opcodes[finally_op_num];
63 execute_data->leaving = ZEND_RETURN;
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
64 generator->flags |= ZEND_GENERATOR_FORCED_CLOSE;
4d8edda @nikic Run finally if generator is closed before finishing
nikic authored
65 zend_generator_resume(generator TSRMLS_CC);
66 return;
67 }
68 }
69 }
70
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
71 if (!execute_data->symbol_table) {
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
72 zend_free_compiled_variables(execute_data->CVs, op_array->last_var);
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
73 } else {
6233408 @nikic Fix thread safe build
nikic authored
74 zend_clean_and_cache_symbol_table(execute_data->symbol_table TSRMLS_CC);
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
75 }
76
247bb73 @nikic Add support for generator methods
nikic authored
77 if (execute_data->current_this) {
78 zval_ptr_dtor(&execute_data->current_this);
79 }
80
bf82f46 @nikic Properly handle yield during method calls
nikic authored
81 if (execute_data->object) {
82 zval_ptr_dtor(&execute_data->object);
83 }
84
64a643a @nikic Free loop variables
nikic authored
85 /* If the generator is closed before it can finish execution (reach
86 * a return statement) we have to free loop variables manually, as
87 * we don't know whether the SWITCH_FREE / FREE opcodes have run */
88 if (!finished_execution) {
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
89 /* -1 required because we want the last run opcode, not the
90 * next to-be-run one. */
91 zend_uint op_num = execute_data->opline - op_array->opcodes - 1;
64a643a @nikic Free loop variables
nikic authored
92
93 int i;
94 for (i = 0; i < op_array->last_brk_cont; ++i) {
95 zend_brk_cont_element *brk_cont = op_array->brk_cont_array + i;
96
97 if (brk_cont->start < 0) {
98 continue;
99 } else if (brk_cont->start > op_num) {
100 break;
101 } else if (brk_cont->brk > op_num) {
102 zend_op *brk_opline = op_array->opcodes + brk_cont->brk;
103
104 switch (brk_opline->opcode) {
105 case ZEND_SWITCH_FREE:
106 {
107 temp_variable *var = (temp_variable *) ((char *) execute_data->Ts + brk_opline->op1.var);
108 zval_ptr_dtor(&var->var.ptr);
109 }
110 break;
111 case ZEND_FREE:
112 {
113 temp_variable *var = (temp_variable *) ((char *) execute_data->Ts + brk_opline->op1.var);
114 zval_dtor(&var->tmp_var);
115 }
116 break;
117 }
118 }
119 }
120 }
121
ee89e22 @nikic Allow yielding during function calls
nikic authored
122 /* Clear any backed up stack arguments */
123 if (generator->backed_up_stack) {
124 zval **zvals = (zval **) generator->backed_up_stack;
125 size_t zval_num = generator->backed_up_stack_size / sizeof(zval *);
126 int i;
127
128 for (i = 0; i < zval_num; i++) {
129 zval_ptr_dtor(&zvals[i]);
130 }
131
132 efree(generator->backed_up_stack);
133 }
134
f169b26 @nikic Fix backtraces and func_get_args()
nikic authored
135 /* We have added an additional stack frame in prev_execute_data, so we
136 * have to free it. It also contains the arguments passed to the
137 * generator (for func_get_args) so those have to be freed too. */
138 {
139 zend_execute_data *prev_execute_data = execute_data->prev_execute_data;
140 void **arguments = prev_execute_data->function_state.arguments;
141
142 if (arguments) {
143 int arguments_count = (int) (zend_uintptr_t) *arguments;
144 zval **arguments_start = (zval **) (arguments - arguments_count);
145 int i;
146
147 for (i = 0; i < arguments_count; ++i) {
148 zval_ptr_dtor(arguments_start + i);
149 }
150
151 efree(arguments_start);
152 }
153
154 efree(prev_execute_data);
155 }
156
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
157 efree(execute_data);
d49d397 @nikic Close generator on return
nikic authored
158 generator->execute_data = NULL;
9ce9a7e @nikic Add initial code for suspending execution
nikic authored
159 }
160
fafce58 @nikic Add YIELD opcode implementation
nikic authored
161 if (generator->value) {
162 zval_ptr_dtor(&generator->value);
d49d397 @nikic Close generator on return
nikic authored
163 generator->value = NULL;
fafce58 @nikic Add YIELD opcode implementation
nikic authored
164 }
165
bc08c2c @nikic Add support for yielding keys
nikic authored
166 if (generator->key) {
167 zval_ptr_dtor(&generator->key);
168 generator->key = NULL;
169 }
d49d397 @nikic Close generator on return
nikic authored
170 }
171 /* }}} */
172
173 static void zend_generator_free_storage(zend_generator *generator TSRMLS_DC) /* {{{ */
174 {
64a643a @nikic Free loop variables
nikic authored
175 zend_generator_close(generator, 0 TSRMLS_CC);
d49d397 @nikic Close generator on return
nikic authored
176
177 zend_object_std_dtor(&generator->std TSRMLS_CC);
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
178 efree(generator);
179 }
180 /* }}} */
181
6117f4c @nikic Add cloning support for generators
nikic authored
182 static void zend_generator_clone_storage(zend_generator *orig, zend_generator **clone_ptr) /* {{{ */
183 {
184 zend_generator *clone = emalloc(sizeof(zend_generator));
185 memcpy(clone, orig, sizeof(zend_generator));
186
187 if (orig->execute_data) {
188 /* Create a few shorter aliases to the old execution data */
189 zend_execute_data *execute_data = orig->execute_data;
190 zend_op_array *op_array = execute_data->op_array;
191 HashTable *symbol_table = execute_data->symbol_table;
192
193 /* Alloc separate execution context, as well as separate sections for
194 * compiled variables and temporary variables */
195 size_t execute_data_size = ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data));
196 size_t CVs_size = ZEND_MM_ALIGNED_SIZE(sizeof(zval **) * op_array->last_var * (symbol_table ? 1 : 2));
197 size_t Ts_size = ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable)) * op_array->T;
198 size_t total_size = execute_data_size + CVs_size + Ts_size;
199
200 clone->execute_data = emalloc(total_size);
201
202 /* Copy the zend_execute_data struct */
203 memcpy(clone->execute_data, execute_data, execute_data_size);
204
205 /* Set the pointers to the memory segments for the compiled and
206 * temporary variables (which are located after the execute_data) */
207 clone->execute_data->CVs = (zval ***) ((char *) clone->execute_data + execute_data_size);
208 clone->execute_data->Ts = (temp_variable *) ((char *) clone->execute_data->CVs + CVs_size);
209
210 /* Zero out the compiled variables section */
211 memset(clone->execute_data->CVs, 0, sizeof(zval **) * op_array->last_var);
212
213 if (!symbol_table) {
214 int i;
215
216 /* Copy compiled variables */
217 for (i = 0; i < op_array->last_var; i++) {
218 if (execute_data->CVs[i]) {
219 clone->execute_data->CVs[i] = (zval **) clone->execute_data->CVs + op_array->last_var + i;
220 *clone->execute_data->CVs[i] = (zval *) orig->execute_data->CVs[op_array->last_var + i];
221 Z_ADDREF_PP(clone->execute_data->CVs[i]);
222 }
223 }
224 } else {
225 /* Copy symbol table */
226 ALLOC_HASHTABLE(clone->execute_data->symbol_table);
227 zend_hash_init(clone->execute_data->symbol_table, zend_hash_num_elements(symbol_table), NULL, ZVAL_PTR_DTOR, 0);
228 zend_hash_copy(clone->execute_data->symbol_table, symbol_table, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
229
230 /* Update zval** pointers for compiled variables */
231 {
232 int i;
233 for (i = 0; i < op_array->last_var; i++) {
234 if (zend_hash_quick_find(clone->execute_data->symbol_table, op_array->vars[i].name, op_array->vars[i].name_len + 1, op_array->vars[i].hash_value, (void **) &clone->execute_data->CVs[i]) == FAILURE) {
235 clone->execute_data->CVs[i] = NULL;
236 }
237 }
238 }
239 }
240
241 /* Copy the temporary variables */
242 memcpy(clone->execute_data->Ts, orig->execute_data->Ts, Ts_size);
243
244 /* Add references to loop variables */
245 {
246 zend_uint op_num = execute_data->opline - op_array->opcodes;
247
248 int i;
249 for (i = 0; i < op_array->last_brk_cont; ++i) {
250 zend_brk_cont_element *brk_cont = op_array->brk_cont_array + i;
251
252 if (brk_cont->start < 0) {
253 continue;
254 } else if (brk_cont->start > op_num) {
255 break;
256 } else if (brk_cont->brk > op_num) {
257 zend_op *brk_opline = op_array->opcodes + brk_cont->brk;
258
259 if (brk_opline->opcode == ZEND_SWITCH_FREE) {
260 temp_variable *var = (temp_variable *) ((char *) execute_data->Ts + brk_opline->op1.var);
261
262 Z_ADDREF_P(var->var.ptr);
263 }
264 }
265 }
266 }
267
268 if (orig->backed_up_stack) {
269 /* Copy backed up stack */
270 clone->backed_up_stack = emalloc(orig->backed_up_stack_size);
271 memcpy(clone->backed_up_stack, orig->backed_up_stack, orig->backed_up_stack_size);
272
273 /* Add refs to stack variables */
274 {
275 zval **zvals = (zval **) orig->backed_up_stack;
276 size_t zval_num = orig->backed_up_stack_size / sizeof(zval *);
277 int i;
278
279 for (i = 0; i < zval_num; i++) {
280 Z_ADDREF_P(zvals[i]);
281 }
282 }
283 }
284
285 /* Update the send_target to use the temporary variable with the same
d939d2d @nikic Add sceleton for yield* expression
nikic authored
286 * offset as the original generator, but in our temporary variable
6117f4c @nikic Add cloning support for generators
nikic authored
287 * memory segment. */
288 if (orig->send_target) {
289 size_t offset = (char *) orig->send_target - (char *) execute_data->Ts;
290 clone->send_target = (temp_variable *) (
291 (char *) clone->execute_data->Ts + offset
292 );
293 Z_ADDREF_P(clone->send_target->var.ptr);
294 }
bf82f46 @nikic Properly handle yield during method calls
nikic authored
295
40760ec @nikic Fix cloning of generator methods
nikic authored
296 if (execute_data->current_this) {
297 Z_ADDREF_P(execute_data->current_this);
298 }
299
bf82f46 @nikic Properly handle yield during method calls
nikic authored
300 if (execute_data->object) {
301 Z_ADDREF_P(execute_data->object);
302 }
f169b26 @nikic Fix backtraces and func_get_args()
nikic authored
303
dbc7809 @nikic Fix typos
nikic authored
304 /* Prev execute data contains an additional stack frame (for proper
f169b26 @nikic Fix backtraces and func_get_args()
nikic authored
305 * backtraces) which has to be copied. */
306 clone->execute_data->prev_execute_data = emalloc(sizeof(zend_execute_data));
307 memcpy(clone->execute_data->prev_execute_data, execute_data->prev_execute_data, sizeof(zend_execute_data));
308
309 /* It also contains the arguments passed to the generator, which also
310 * have to be copied */
311 if (execute_data->prev_execute_data->function_state.arguments) {
312 clone->execute_data->prev_execute_data->function_state.arguments
313 = zend_copy_arguments(execute_data->prev_execute_data->function_state.arguments);
314 }
6117f4c @nikic Add cloning support for generators
nikic authored
315 }
316
317 /* The value and key are known not to be references, so simply add refs */
318 if (orig->value) {
319 Z_ADDREF_P(orig->value);
320 }
321
322 if (orig->key) {
323 Z_ADDREF_P(orig->key);
324 }
325
326 *clone_ptr = clone;
327 }
328 /* }}} */
329
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
330 static zend_object_value zend_generator_create(zend_class_entry *class_type TSRMLS_DC) /* {{{ */
331 {
332 zend_generator *generator;
333 zend_object_value object;
334
335 generator = emalloc(sizeof(zend_generator));
336 memset(generator, 0, sizeof(zend_generator));
337
8790160 @nikic Add auto-increment keys
nikic authored
338 /* The key will be incremented on first use, so it'll start at 0 */
339 generator->largest_used_integer_key = -1;
340
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
341 zend_object_std_init(&generator->std, class_type TSRMLS_CC);
342
343 object.handle = zend_objects_store_put(generator, NULL,
344 (zend_objects_free_object_storage_t) zend_generator_free_storage,
6117f4c @nikic Add cloning support for generators
nikic authored
345 (zend_objects_store_clone_t) zend_generator_clone_storage
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
346 TSRMLS_CC
347 );
348 object.handlers = &zend_generator_handlers;
349
350 return object;
351 }
352 /* }}} */
353
c9709bf @nikic Remove asterix modifier (*) for generators
nikic authored
354 /* Requires globals EG(scope), EG(current_scope), EG(This),
355 * EG(active_symbol_table) and EG(current_execute_data). */
356 zval *zend_generator_create_zval(zend_op_array *op_array TSRMLS_DC) /* {{{ */
357 {
358 zval *return_value;
359 zend_generator *generator;
360
361 /* Create new execution context. We have to back up and restore
362 * EG(current_execute_data) and EG(opline_ptr) here because the function
363 * modifies it. */
364 zend_execute_data *current_execute_data = EG(current_execute_data);
365 zend_op **opline_ptr = EG(opline_ptr);
366 zend_execute_data *execute_data = zend_create_execute_data_from_op_array(op_array, 0 TSRMLS_CC);
367 EG(current_execute_data) = current_execute_data;
368 EG(opline_ptr) = opline_ptr;
369
370 ALLOC_INIT_ZVAL(return_value);
371 object_init_ex(return_value, zend_ce_generator);
372
373 if (EG(This)) {
374 Z_ADDREF_P(EG(This));
375 }
376
377 /* Back up executor globals. */
378 execute_data->current_scope = EG(scope);
379 execute_data->current_called_scope = EG(called_scope);
380 execute_data->symbol_table = EG(active_symbol_table);
381 execute_data->current_this = EG(This);
382
383 /* Save execution context in generator object. */
384 generator = (zend_generator *) zend_object_store_get_object(return_value TSRMLS_CC);
385 generator->execute_data = execute_data;
386
387 /* We have to add another stack frame so the generator function shows
388 * up in backtraces and func_get_all() can access the function
389 * arguments. */
390 execute_data->prev_execute_data = emalloc(sizeof(zend_execute_data));
94b2cca @nikic Fix throwing of exceptions within a generator
nikic authored
391 memset(execute_data->prev_execute_data, 0, sizeof(zend_execute_data));
392 execute_data->prev_execute_data->function_state.function = (zend_function *) op_array;
c9709bf @nikic Remove asterix modifier (*) for generators
nikic authored
393 if (EG(current_execute_data)) {
394 execute_data->prev_execute_data->function_state.arguments = zend_copy_arguments(EG(current_execute_data)->function_state.arguments);
395 } else {
396 execute_data->prev_execute_data->function_state.arguments = NULL;
397 }
398
399 return return_value;
400 }
401 /* }}} */
402
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
403 static zend_function *zend_generator_get_constructor(zval *object TSRMLS_DC) /* {{{ */
404 {
405 zend_error(E_RECOVERABLE_ERROR, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated");
406
407 return NULL;
408 }
409 /* }}} */
ca59e54 @nikic Add empty Generator class
nikic authored
410
4d8edda @nikic Run finally if generator is closed before finishing
nikic authored
411 void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
412 {
d49d397 @nikic Close generator on return
nikic authored
413 /* The generator is already closed, thus can't resume */
414 if (!generator->execute_data) {
415 return;
416 }
417
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
418 if (generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING) {
419 zend_error(E_ERROR, "Cannot resume an already running generator");
420 }
421
422 /* Drop the AT_FIRST_YIELD flag */
423 generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
424
247bb73 @nikic Add support for generator methods
nikic authored
425 {
426 /* Backup executor globals */
427 zval **original_return_value_ptr_ptr = EG(return_value_ptr_ptr);
bcc7d97 @nikic Set EG(current_execute_data)
nikic authored
428 zend_execute_data *original_execute_data = EG(current_execute_data);
247bb73 @nikic Add support for generator methods
nikic authored
429 zend_op **original_opline_ptr = EG(opline_ptr);
430 zend_op_array *original_active_op_array = EG(active_op_array);
431 HashTable *original_active_symbol_table = EG(active_symbol_table);
432 zval *original_This = EG(This);
433 zend_class_entry *original_scope = EG(scope);
434 zend_class_entry *original_called_scope = EG(called_scope);
435
ee89e22 @nikic Allow yielding during function calls
nikic authored
436 /* Remember the current stack position so we can back up pushed args */
437 generator->original_stack_top = zend_vm_stack_top(TSRMLS_C);
438
439 /* If there is a backed up stack copy it to the VM stack */
440 if (generator->backed_up_stack) {
441 void *stack = zend_vm_stack_alloc(generator->backed_up_stack_size TSRMLS_CC);
442 memcpy(stack, generator->backed_up_stack, generator->backed_up_stack_size);
443 efree(generator->backed_up_stack);
444 generator->backed_up_stack = NULL;
445 }
446
247bb73 @nikic Add support for generator methods
nikic authored
447 /* We (mis)use the return_value_ptr_ptr to provide the generator object
448 * to the executor, so YIELD will be able to set the yielded value */
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
449 EG(return_value_ptr_ptr) = (zval **) generator;
247bb73 @nikic Add support for generator methods
nikic authored
450
451 /* Set executor globals */
bcc7d97 @nikic Set EG(current_execute_data)
nikic authored
452 EG(current_execute_data) = generator->execute_data;
247bb73 @nikic Add support for generator methods
nikic authored
453 EG(opline_ptr) = &generator->execute_data->opline;
454 EG(active_op_array) = generator->execute_data->op_array;
455 EG(active_symbol_table) = generator->execute_data->symbol_table;
456 EG(This) = generator->execute_data->current_this;
457 EG(scope) = generator->execute_data->current_scope;
458 EG(called_scope) = generator->execute_data->current_called_scope;
459
f169b26 @nikic Fix backtraces and func_get_args()
nikic authored
460 /* We want the backtrace to look as if the generator function was
461 * called from whatever method we are current running (e.g. next()).
462 * The first prev_execute_data contains an additional stack frame,
463 * which makes the generator function show up in the backtrace and
464 * makes the arguments available to func_get_args(). So we have to
465 * set the prev_execute_data of that prev_execute_data :) */
466 generator->execute_data->prev_execute_data->prev_execute_data = original_execute_data;
7b3bfa5 @nikic Improve backtraces from generators
nikic authored
467
247bb73 @nikic Add support for generator methods
nikic authored
468 /* Resume execution */
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
469 generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
247bb73 @nikic Add support for generator methods
nikic authored
470 execute_ex(generator->execute_data TSRMLS_CC);
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
471 generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
247bb73 @nikic Add support for generator methods
nikic authored
472
473 /* Restore executor globals */
474 EG(return_value_ptr_ptr) = original_return_value_ptr_ptr;
bcc7d97 @nikic Set EG(current_execute_data)
nikic authored
475 EG(current_execute_data) = original_execute_data;
247bb73 @nikic Add support for generator methods
nikic authored
476 EG(opline_ptr) = original_opline_ptr;
477 EG(active_op_array) = original_active_op_array;
478 EG(active_symbol_table) = original_active_symbol_table;
479 EG(This) = original_This;
480 EG(scope) = original_scope;
481 EG(called_scope) = original_called_scope;
ee89e22 @nikic Allow yielding during function calls
nikic authored
482
483 /* The stack top before and after the execution differ, i.e. there are
484 * arguments pushed to the stack. */
485 if (generator->original_stack_top != zend_vm_stack_top(TSRMLS_C)) {
486 generator->backed_up_stack_size = (zend_vm_stack_top(TSRMLS_C) - generator->original_stack_top) * sizeof(void *);
487 generator->backed_up_stack = emalloc(generator->backed_up_stack_size);
488 memcpy(generator->backed_up_stack, generator->original_stack_top, generator->backed_up_stack_size);
489 zend_vm_stack_free(generator->original_stack_top TSRMLS_CC);
490 }
94b2cca @nikic Fix throwing of exceptions within a generator
nikic authored
491
492 /* If an exception was thrown in the generator we have to internally
493 * rethrow it in the parent scope. */
494 if (UNEXPECTED(EG(exception) != NULL)) {
495 zend_throw_exception_internal(NULL TSRMLS_CC);
496 }
247bb73 @nikic Add support for generator methods
nikic authored
497 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
498 }
499 /* }}} */
500
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
501 static void zend_generator_ensure_initialized(zend_generator *generator TSRMLS_DC) /* {{{ */
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
502 {
cc07038 @nikic Make sure that exception is thrown on rewind() after closing too
nikic authored
503 if (generator->execute_data && !generator->value) {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
504 zend_generator_resume(generator TSRMLS_CC);
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
505 generator->flags |= ZEND_GENERATOR_AT_FIRST_YIELD;
506 }
507 }
508 /* }}} */
509
510 static void zend_generator_rewind(zend_generator *generator TSRMLS_DC) /* {{{ */
511 {
512 zend_generator_ensure_initialized(generator TSRMLS_CC);
513
514 if (!(generator->flags & ZEND_GENERATOR_AT_FIRST_YIELD)) {
515 zend_throw_exception(NULL, "Cannot rewind a generator that was already run", 0 TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
516 }
517 }
518 /* }}} */
519
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
520 /* {{{ proto void Generator::rewind()
521 * Rewind the generator */
522 ZEND_METHOD(Generator, rewind)
523 {
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
524 zend_generator *generator;
525
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
526 if (zend_parse_parameters_none() == FAILURE) {
527 return;
528 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
529
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
530 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
531
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
532 zend_generator_rewind(generator TSRMLS_CC);
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
533 }
534 /* }}} */
535
536 /* {{{ proto bool Generator::valid()
537 * Check whether the generator is valid */
538 ZEND_METHOD(Generator, valid)
539 {
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
540 zend_generator *generator;
541
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
542 if (zend_parse_parameters_none() == FAILURE) {
543 return;
544 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
545
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
546 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
547
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
548 zend_generator_ensure_initialized(generator TSRMLS_CC);
5bb3a99 @nikic Implement return for generators
nikic authored
549
550 RETURN_BOOL(generator->value != NULL);
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
551 }
552 /* }}} */
553
554 /* {{{ proto mixed Generator::current()
555 * Get the current value */
556 ZEND_METHOD(Generator, current)
557 {
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
558 zend_generator *generator;
559
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
560 if (zend_parse_parameters_none() == FAILURE) {
561 return;
562 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
563
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
564 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
565
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
566 zend_generator_ensure_initialized(generator TSRMLS_CC);
5bb3a99 @nikic Implement return for generators
nikic authored
567
568 if (generator->value) {
cbfa96c @nikic Remove wrong dtor call
nikic authored
569 RETURN_ZVAL(generator->value, 1, 0);
5bb3a99 @nikic Implement return for generators
nikic authored
570 }
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
571 }
572 /* }}} */
573
574 /* {{{ proto mixed Generator::key()
575 * Get the current key */
576 ZEND_METHOD(Generator, key)
577 {
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
578 zend_generator *generator;
579
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
580 if (zend_parse_parameters_none() == FAILURE) {
581 return;
582 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
583
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
584 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
585
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
586 zend_generator_ensure_initialized(generator TSRMLS_CC);
bc08c2c @nikic Add support for yielding keys
nikic authored
587
588 if (generator->key) {
589 RETURN_ZVAL(generator->key, 1, 0);
590 }
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
591 }
592 /* }}} */
593
594 /* {{{ proto void Generator::next()
595 * Advances the generator */
596 ZEND_METHOD(Generator, next)
597 {
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
598 zend_generator *generator;
599
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
600 if (zend_parse_parameters_none() == FAILURE) {
601 return;
602 }
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
603
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
604 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
f627be5 @nikic Add support for executing a zend_execute_data
nikic authored
605
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
606 zend_generator_ensure_initialized(generator TSRMLS_CC);
5bb3a99 @nikic Implement return for generators
nikic authored
607
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
608 zend_generator_resume(generator TSRMLS_CC);
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
609 }
610 /* }}} */
611
1477be9 @nikic Make $generator->send() return the current value
nikic authored
612 /* {{{ proto mixed Generator::send()
3600914 @nikic Add support for $generator->send()
nikic authored
613 * Sends a value to the generator */
614 ZEND_METHOD(Generator, send)
615 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
616 zval *value;
3600914 @nikic Add support for $generator->send()
nikic authored
617 zend_generator *generator;
618
619 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) == FAILURE) {
620 return;
621 }
622
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
623 generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
3600914 @nikic Add support for $generator->send()
nikic authored
624
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
625 zend_generator_ensure_initialized(generator TSRMLS_CC);
3600914 @nikic Add support for $generator->send()
nikic authored
626
12e9283 @nikic Fix segfault when send()ing to a closed generator
nikic authored
627 /* The generator is already closed, thus can't send anything */
628 if (!generator->execute_data) {
629 return;
630 }
631
3600914 @nikic Add support for $generator->send()
nikic authored
632 /* The sent value was initialized to NULL, so dtor that */
6117f4c @nikic Add cloning support for generators
nikic authored
633 zval_ptr_dtor(&generator->send_target->var.ptr);
634
3600914 @nikic Add support for $generator->send()
nikic authored
635 /* Set new sent value */
636 Z_ADDREF_P(value);
637 generator->send_target->var.ptr = value;
638 generator->send_target->var.ptr_ptr = &value;
639
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
640 zend_generator_resume(generator TSRMLS_CC);
1477be9 @nikic Make $generator->send() return the current value
nikic authored
641
642 if (generator->value) {
643 RETURN_ZVAL(generator->value, 1, 0);
644 }
3600914 @nikic Add support for $generator->send()
nikic authored
645 }
dbc7809 @nikic Fix typos
nikic authored
646 /* }}} */
3600914 @nikic Add support for $generator->send()
nikic authored
647
dbc7809 @nikic Fix typos
nikic authored
648 /* {{{ proto void Generator::__wakeup()
f45a0f3 @nikic Disallow serialization and unserialization
nikic authored
649 * Throws an Exception as generators can't be serialized */
650 ZEND_METHOD(Generator, __wakeup)
651 {
652 /* Just specifying the zend_class_unserialize_deny handler is not enough,
653 * because it is only invoked for C unserialization. For O the error has
654 * to be thrown in __wakeup. */
655
656 if (zend_parse_parameters_none() == FAILURE) {
657 return;
658 }
659
660 zend_throw_exception(NULL, "Unserialization of 'Generator' is not allowed", 0 TSRMLS_CC);
661 }
662 /* }}} */
663
04e781f @nikic Implement get_iterator
nikic authored
664 /* get_iterator implementation */
665
666 typedef struct _zend_generator_iterator {
667 zend_object_iterator intern;
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
668
669 /* The generator object zval has to be stored, because the iterator is
670 * holding a ref to it, which has to be dtored. */
671 zval *object;
04e781f @nikic Implement get_iterator
nikic authored
672 } zend_generator_iterator;
673
674 static void zend_generator_iterator_dtor(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
675 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
676 zval *object = ((zend_generator_iterator *) iterator)->object;
677
678 zval_ptr_dtor(&object);
679
04e781f @nikic Implement get_iterator
nikic authored
680 efree(iterator);
681 }
682 /* }}} */
683
684 static int zend_generator_iterator_valid(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
685 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
686 zend_generator *generator = (zend_generator *) iterator->data;
04e781f @nikic Implement get_iterator
nikic authored
687
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
688 zend_generator_ensure_initialized(generator TSRMLS_CC);
04e781f @nikic Implement get_iterator
nikic authored
689
690 return generator->value != NULL ? SUCCESS : FAILURE;
691 }
692 /* }}} */
693
694 static void zend_generator_iterator_get_data(zend_object_iterator *iterator, zval ***data TSRMLS_DC) /* {{{ */
695 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
696 zend_generator *generator = (zend_generator *) iterator->data;
04e781f @nikic Implement get_iterator
nikic authored
697
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
698 zend_generator_ensure_initialized(generator TSRMLS_CC);
04e781f @nikic Implement get_iterator
nikic authored
699
700 if (generator->value) {
701 *data = &generator->value;
702 } else {
703 *data = NULL;
704 }
705 }
706 /* }}} */
707
708 static int zend_generator_iterator_get_key(zend_object_iterator *iterator, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) /* {{{ */
709 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
710 zend_generator *generator = (zend_generator *) iterator->data;
04e781f @nikic Implement get_iterator
nikic authored
711
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
712 zend_generator_ensure_initialized(generator TSRMLS_CC);
04e781f @nikic Implement get_iterator
nikic authored
713
714 if (!generator->key) {
715 return HASH_KEY_NON_EXISTANT;
716 }
717
718 if (Z_TYPE_P(generator->key) == IS_LONG) {
719 *int_key = Z_LVAL_P(generator->key);
720 return HASH_KEY_IS_LONG;
721 }
722
723 if (Z_TYPE_P(generator->key) == IS_STRING) {
724 *str_key = estrndup(Z_STRVAL_P(generator->key), Z_STRLEN_P(generator->key));
725 *str_key_len = Z_STRLEN_P(generator->key) + 1;
726 return HASH_KEY_IS_STRING;
727 }
728
729 /* Waiting for Etienne's patch to allow arbitrary zval keys. Until then
730 * error out on non-int and non-string keys. */
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
731 zend_error_noreturn(E_ERROR, "Currently only int and string keys can be yielded");
04e781f @nikic Implement get_iterator
nikic authored
732 }
733 /* }}} */
734
735 static void zend_generator_iterator_move_forward(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
736 {
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
737 zend_generator *generator = (zend_generator *) iterator->data;
04e781f @nikic Implement get_iterator
nikic authored
738
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
739 zend_generator_ensure_initialized(generator TSRMLS_CC);
04e781f @nikic Implement get_iterator
nikic authored
740
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
741 zend_generator_resume(generator TSRMLS_CC);
04e781f @nikic Implement get_iterator
nikic authored
742 }
743 /* }}} */
744
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
745 static void zend_generator_iterator_rewind(zend_object_iterator *iterator TSRMLS_DC) /* {{{ */
746 {
747 zend_generator *generator = (zend_generator *) iterator->data;
748
749 zend_generator_rewind(generator TSRMLS_CC);
750 }
751 /* }}} */
752
04e781f @nikic Implement get_iterator
nikic authored
753 static zend_object_iterator_funcs zend_generator_iterator_functions = {
754 zend_generator_iterator_dtor,
755 zend_generator_iterator_valid,
756 zend_generator_iterator_get_data,
757 zend_generator_iterator_get_key,
758 zend_generator_iterator_move_forward,
f53225a @nikic Fix several issues and allow rewind only at/before first yield
nikic authored
759 zend_generator_iterator_rewind
04e781f @nikic Implement get_iterator
nikic authored
760 };
761
762 zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC) /* {{{ */
763 {
764 zend_generator_iterator *iterator;
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
765 zend_generator *generator;
04e781f @nikic Implement get_iterator
nikic authored
766
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
767 generator = (zend_generator *) zend_object_store_get_object(object TSRMLS_CC);
768
bef7958 @nikic Fix segfault when traversing a by-ref generator twice
nikic authored
769 if (!generator->execute_data) {
770 zend_throw_exception(NULL, "Cannot traverse an already closed generator", 0 TSRMLS_CC);
771 return NULL;
772 }
773
85f077c @nikic Add support by yielding by-reference
nikic authored
774 if (by_ref && !(generator->execute_data->op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
bef7958 @nikic Fix segfault when traversing a by-ref generator twice
nikic authored
775 zend_throw_exception(NULL, "You can only iterate a generator by-reference if it declared that it yields by-reference", 0 TSRMLS_CC);
776 return NULL;
85f077c @nikic Add support by yielding by-reference
nikic authored
777 }
778
04e781f @nikic Implement get_iterator
nikic authored
779 iterator = emalloc(sizeof(zend_generator_iterator));
780 iterator->intern.funcs = &zend_generator_iterator_functions;
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
781 iterator->intern.data = (void *) generator;
04e781f @nikic Implement get_iterator
nikic authored
782
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
783 /* We have to keep a reference to the generator object zval around,
784 * otherwise the generator may be destroyed during iteration. */
04e781f @nikic Implement get_iterator
nikic authored
785 Z_ADDREF_P(object);
14766e1 @nikic Pass zend_generator directly to Zend VM
nikic authored
786 iterator->object = object;
04e781f @nikic Implement get_iterator
nikic authored
787
788 return (zend_object_iterator *) iterator;
789 }
790 /* }}} */
791
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
792 ZEND_BEGIN_ARG_INFO(arginfo_generator_void, 0)
793 ZEND_END_ARG_INFO()
794
3600914 @nikic Add support for $generator->send()
nikic authored
795 ZEND_BEGIN_ARG_INFO_EX(arginfo_generator_send, 0, 0, 1)
796 ZEND_ARG_INFO(0, value)
797 ZEND_END_ARG_INFO()
798
ca59e54 @nikic Add empty Generator class
nikic authored
799 static const zend_function_entry generator_functions[] = {
f45a0f3 @nikic Disallow serialization and unserialization
nikic authored
800 ZEND_ME(Generator, rewind, arginfo_generator_void, ZEND_ACC_PUBLIC)
801 ZEND_ME(Generator, valid, arginfo_generator_void, ZEND_ACC_PUBLIC)
802 ZEND_ME(Generator, current, arginfo_generator_void, ZEND_ACC_PUBLIC)
803 ZEND_ME(Generator, key, arginfo_generator_void, ZEND_ACC_PUBLIC)
804 ZEND_ME(Generator, next, arginfo_generator_void, ZEND_ACC_PUBLIC)
805 ZEND_ME(Generator, send, arginfo_generator_send, ZEND_ACC_PUBLIC)
806 ZEND_ME(Generator, __wakeup, arginfo_generator_void, ZEND_ACC_PUBLIC)
ca59e54 @nikic Add empty Generator class
nikic authored
807 ZEND_FE_END
808 };
809
810 void zend_register_generator_ce(TSRMLS_D) /* {{{ */
811 {
812 zend_class_entry ce;
813
814 INIT_CLASS_ENTRY(ce, "Generator", generator_functions);
815 zend_ce_generator = zend_register_internal_class(&ce TSRMLS_CC);
816 zend_ce_generator->ce_flags |= ZEND_ACC_FINAL_CLASS;
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
817 zend_ce_generator->create_object = zend_generator_create;
f45a0f3 @nikic Disallow serialization and unserialization
nikic authored
818 zend_ce_generator->serialize = zend_class_serialize_deny;
819 zend_ce_generator->unserialize = zend_class_unserialize_deny;
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
820
268740d @nikic Fix implementation of Iterator interface
nikic authored
821 /* get_iterator has to be assigned *after* implementing the inferface */
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
822 zend_class_implements(zend_ce_generator TSRMLS_CC, 1, zend_ce_iterator);
268740d @nikic Fix implementation of Iterator interface
nikic authored
823 zend_ce_generator->get_iterator = zend_generator_get_iterator;
824 zend_ce_generator->iterator_funcs.funcs = &zend_generator_iterator_functions;
2c5ecb4 @nikic Add dummy Iterator implementation
nikic authored
825
40b7533 @nikic Add some boilerplate code for Generator class
nikic authored
826 memcpy(&zend_generator_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
827 zend_generator_handlers.get_constructor = zend_generator_get_constructor;
6117f4c @nikic Add cloning support for generators
nikic authored
828 zend_generator_handlers.clone_obj = zend_objects_store_clone_obj;
ca59e54 @nikic Add empty Generator class
nikic authored
829 }
830 /* }}} */
831
832 /*
833 * Local variables:
834 * tab-width: 4
835 * c-basic-offset: 4
836 * indent-tabs-mode: t
837 * End:
838 */
Something went wrong with that request. Please try again.