1 /*
2 +--------------------------------------------------------------------+
3 | ext-fiber |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Authors: Aaron Piotrowski <aaron@trowski.com> |
10 | Martin Schröder <m.schroeder2007@gmail.com> |
11 +--------------------------------------------------------------------+
12 */
13
14 #include "zend.h"
15 #include "zend_API.h"
16 #include "zend_ini.h"
17 #include "zend_vm.h"
18 #include "zend_portability.h"
19 #include "zend_interfaces.h"
20 #include "zend_exceptions.h"
21 #include "zend_builtin_functions.h"
22
23 #include "php_fiber.h"
24 #include "fiber.h"
25 #include "fiber_arginfo.h"
26
27 PHP_FIBER_API zend_class_entry *zend_ce_fiber;
28
29 static zend_class_entry *zend_ce_reflection_fiber;
30
31 static zend_class_entry *zend_ce_fiber_error;
32 static zend_class_entry *zend_ce_fiber_exit;
33
34 static zend_object_handlers zend_fiber_handlers;
35 static zend_object_handlers zend_reflection_fiber_handlers;
36
37 static zend_function zend_fiber_function = { ZEND_INTERNAL_FUNCTION };
38
39 static zend_llist zend_fiber_observers_list;
40
41 #define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
42 stack = EG(vm_stack); \
43 stack->top = EG(vm_stack_top); \
44 stack->end = EG(vm_stack_end); \
45 stack_page_size = EG(vm_stack_page_size); \
46 execute_data = EG(current_execute_data); \
47 error_reporting = EG(error_reporting); \
48 trace_num = EG(jit_trace_num); \
49 bailout = EG(bailout); \
50 } while (0)
51
52 #define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \
53 EG(vm_stack) = stack; \
54 EG(vm_stack_top) = stack->top; \
55 EG(vm_stack_end) = stack->end; \
56 EG(vm_stack_page_size) = stack_page_size; \
57 EG(current_execute_data) = execute_data; \
58 EG(error_reporting) = error_reporting; \
59 EG(jit_trace_num) = trace_num; \
60 EG(bailout) = bailout; \
61 } while (0)
62
63 #if __has_attribute(force_align_arg_pointer)
64 # define ZEND_STACK_ALIGNED __attribute__((force_align_arg_pointer))
65 #else
66 # define ZEND_STACK_ALIGNED
67 #endif
68
69
70
zend_is_fiber_exit(const zend_object * exception)71 zend_always_inline PHP_FIBER_API zend_bool zend_is_fiber_exit(const zend_object *exception)
72 {
73 ZEND_ASSERT(exception && "No exception object provided");
74
75 return exception->ce == zend_ce_fiber_exit;
76 }
77
zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler)78 PHP_FIBER_API void zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler)
79 {
80 zend_llist_add_element(&zend_fiber_observers_list, &handler);
81 }
82
zend_observer_fiber_switch_notify(zend_fiber * from,zend_fiber * to)83 zend_always_inline static void zend_observer_fiber_switch_notify(zend_fiber *from, zend_fiber *to)
84 {
85 zend_llist_element *element;
86 zend_observer_fiber_switch_handler callback;
87
88 for (element = zend_fiber_observers_list.head; element; element = element->next) {
89 callback = *(zend_observer_fiber_switch_handler *) element->data;
90 callback(from, to);
91 }
92 }
93
zend_fiber_suspend(zend_fiber * fiber)94 static void zend_fiber_suspend(zend_fiber *fiber)
95 {
96 zend_vm_stack stack;
97 size_t stack_page_size;
98 zend_execute_data *execute_data;
99 int error_reporting;
100 uint32_t jit_trace_num;
101 JMP_BUF *bailout;
102
103 ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
104
105 zend_fiber_suspend_context(&fiber->context);
106
107 ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
108 }
109
zend_fiber_switch_to(zend_fiber * fiber)110 static void zend_fiber_switch_to(zend_fiber *fiber)
111 {
112 zend_fiber *previous;
113 zend_vm_stack stack;
114 size_t stack_page_size;
115 zend_execute_data *execute_data;
116 int error_reporting;
117 uint32_t jit_trace_num;
118 JMP_BUF *bailout;
119
120 previous = FIBER_G(current_fiber);
121
122 zend_observer_fiber_switch_notify(previous, fiber);
123
124 ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
125
126 FIBER_G(current_fiber) = fiber;
127
128 zend_fiber_switch_context(&fiber->context);
129
130 FIBER_G(current_fiber) = previous;
131
132 ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout);
133
134 zend_observer_fiber_switch_notify(fiber, previous);
135
136 if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_BAILOUT)) {
137 // zend_bailout() was called in the fiber, so call it again in the previous fiber or {main}.
138 zend_bailout();
139 }
140 }
141
zend_fiber_vm_stack_alloc(size_t size)142 static zend_always_inline zend_vm_stack zend_fiber_vm_stack_alloc(size_t size)
143 {
144 zend_vm_stack page = emalloc(size);
145
146 page->top = ZEND_VM_STACK_ELEMENTS(page);
147 page->end = (zval *) ((uintptr_t) page + size);
148 page->prev = NULL;
149
150 return page;
151 }
152
zend_fiber_execute(zend_fiber_context * context)153 static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context)
154 {
155 zend_fiber *fiber = FIBER_G(current_fiber);
156 ZEND_ASSERT(fiber);
157
158 zend_long error_reporting = INI_INT("error_reporting");
159 if (!error_reporting && !INI_STR("error_reporting")) {
160 error_reporting = E_ALL;
161 }
162
163 EG(vm_stack) = NULL;
164
165 zend_first_try {
166 zend_vm_stack stack = zend_fiber_vm_stack_alloc(ZEND_FIBER_VM_STACK_SIZE);
167 EG(vm_stack) = stack;
168 EG(vm_stack_top) = stack->top + ZEND_CALL_FRAME_SLOT;
169 EG(vm_stack_end) = stack->end;
170 EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE;
171
172 fiber->execute_data = (zend_execute_data *) stack->top;
173 fiber->stack_bottom = fiber->execute_data;
174
175 memset(fiber->execute_data, 0, sizeof(zend_execute_data));
176
177 fiber->execute_data->func = &zend_fiber_function;
178 fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
179
180 EG(current_execute_data) = fiber->execute_data;
181 EG(jit_trace_num) = 0;
182 EG(error_reporting) = error_reporting;
183
184 fiber->fci.retval = &fiber->value;
185
186 fiber->status = ZEND_FIBER_STATUS_RUNNING;
187
188 zend_call_function(&fiber->fci, &fiber->fci_cache);
189
190 // Cleanup callback and unset field to prevent GC / duplicate dtor issues.
191 zval_ptr_dtor(&fiber->fci.function_name);
192 ZVAL_UNDEF(&fiber->fci.function_name);
193
194 if (EG(exception)) {
195 if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
196 if (EXPECTED(zend_is_fiber_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) {
197 zend_clear_exception();
198 }
199 } else {
200 fiber->status = ZEND_FIBER_STATUS_THREW;
201 }
202 } else {
203 fiber->status = ZEND_FIBER_STATUS_RETURNED;
204 }
205 } zend_catch {
206 fiber->status = ZEND_FIBER_STATUS_BAILOUT;
207 } zend_end_try();
208
209 zend_vm_stack_destroy();
210 fiber->execute_data = NULL;
211 fiber->stack_bottom = NULL;
212 }
213
zend_fiber_object_create(zend_class_entry * ce)214 static zend_object *zend_fiber_object_create(zend_class_entry *ce)
215 {
216 zend_fiber *fiber = emalloc(sizeof(zend_fiber));
217 memset(fiber, 0, sizeof(zend_fiber));
218
219 zend_object_std_init(&fiber->std, ce);
220 fiber->std.handlers = &zend_fiber_handlers;
221
222 return &fiber->std;
223 }
224
zend_fiber_object_destroy(zend_object * object)225 static void zend_fiber_object_destroy(zend_object *object)
226 {
227 zend_fiber *fiber = (zend_fiber *) object;
228
229 if (fiber->status != ZEND_FIBER_STATUS_SUSPENDED) {
230 return;
231 }
232
233 zend_object *exception = EG(exception);
234 EG(exception) = NULL;
235
236 fiber->status = ZEND_FIBER_STATUS_SHUTDOWN;
237
238 zend_fiber_switch_to(fiber);
239
240 if (EG(exception)) {
241 if (!exception && EG(current_execute_data) && EG(current_execute_data)->func
242 && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
243 zend_rethrow_exception(EG(current_execute_data));
244 }
245
246 zend_exception_set_previous(EG(exception), exception);
247
248 if (!EG(current_execute_data)) {
249 zend_exception_error(EG(exception), E_ERROR);
250 }
251 } else {
252 EG(exception) = exception;
253 }
254 }
255
zend_fiber_object_free(zend_object * object)256 static void zend_fiber_object_free(zend_object *object)
257 {
258 zend_fiber *fiber = (zend_fiber *) object;
259
260 zval_ptr_dtor(&fiber->fci.function_name);
261 zval_ptr_dtor(&fiber->value);
262
263 zend_fiber_destroy_context(&fiber->context);
264
265 zend_object_std_dtor(&fiber->std);
266 }
267
zend_fiber_object_gc(zend_object * object,zval ** table,int * num)268 static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *num)
269 {
270 zend_fiber *fiber = (zend_fiber *) object;
271 zend_get_gc_buffer *buf = zend_get_gc_buffer_create();
272
273 zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name);
274 zend_get_gc_buffer_add_zval(buf, &fiber->value);
275
276 zend_get_gc_buffer_use(buf, table, num);
277
278 return NULL;
279 }
280
zend_fiber_catch_handler(zend_execute_data * execute_data)281 static int zend_fiber_catch_handler(zend_execute_data *execute_data)
282 {
283 if (UNEXPECTED(EG(exception) && zend_is_fiber_exit(EG(exception)))) {
284 zend_rethrow_exception(execute_data);
285 return ZEND_USER_OPCODE_CONTINUE;
286 }
287
288 if (FIBER_G(catch_handler)) {
289 return FIBER_G(catch_handler)(execute_data);
290 }
291
292 return ZEND_USER_OPCODE_DISPATCH;
293 }
294
zend_reflection_fiber_object_create(zend_class_entry * ce)295 static zend_object *zend_reflection_fiber_object_create(zend_class_entry *ce)
296 {
297 zend_fiber_reflection *reflection;
298
299 reflection = emalloc(sizeof(zend_fiber_reflection));
300 memset(reflection, 0, sizeof(zend_fiber_reflection));
301
302 zend_object_std_init(&reflection->std, ce);
303 reflection->std.handlers = &zend_reflection_fiber_handlers;
304
305 return &reflection->std;
306 }
307
zend_reflection_fiber_object_free(zend_object * object)308 static void zend_reflection_fiber_object_free(zend_object *object)
309 {
310 zend_fiber_reflection *reflection = (zend_fiber_reflection *) object;
311
312 if (reflection->fiber) {
313 GC_DELREF(&reflection->fiber->std);
314 }
315
316 zend_object_std_dtor(&reflection->std);
317 }
318
ZEND_METHOD(Fiber,__construct)319 ZEND_METHOD(Fiber, __construct)
320 {
321 zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis());
322
323 ZEND_PARSE_PARAMETERS_START(1, 1)
324 Z_PARAM_FUNC(fiber->fci, fiber->fci_cache)
325 ZEND_PARSE_PARAMETERS_END();
326
327 // Keep a reference to closures or callable objects until the fiber is started.
328 Z_TRY_ADDREF(fiber->fci.function_name);
329 }
330
ZEND_METHOD(Fiber,start)331 ZEND_METHOD(Fiber, start)
332 {
333 zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis());
334
335 ZEND_PARSE_PARAMETERS_START(0, -1)
336 Z_PARAM_VARIADIC_WITH_NAMED(fiber->fci.params, fiber->fci.param_count, fiber->fci.named_params);
337 ZEND_PARSE_PARAMETERS_END();
338
339 if (fiber->status != ZEND_FIBER_STATUS_INIT) {
340 zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started");
341 RETURN_THROWS();
342 }
343
344 if (!zend_fiber_init_context(&fiber->context, zend_fiber_execute, FIBER_G(stack_size))) {
345 zend_throw_exception(NULL, "Failed to create native fiber context", 0);
346 RETURN_THROWS();
347 }
348
349 zend_fiber_switch_to(fiber);
350
351 if (fiber->status & ZEND_FIBER_STATUS_FINISHED) {
352 RETURN_NULL();
353 }
354
355 RETVAL_COPY_VALUE(&fiber->value);
356 ZVAL_UNDEF(&fiber->value);
357 }
358
ZEND_METHOD(Fiber,suspend)359 ZEND_METHOD(Fiber, suspend)
360 {
361 zend_fiber *fiber = FIBER_G(current_fiber);
362 zval *exception, *value = NULL;
363
364 ZEND_PARSE_PARAMETERS_START(0, 1)
365 Z_PARAM_OPTIONAL
366 Z_PARAM_ZVAL(value);
367 ZEND_PARSE_PARAMETERS_END();
368
369 if (UNEXPECTED(!fiber)) {
370 zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber");
371 RETURN_THROWS();
372 }
373
374 if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_SHUTDOWN)) {
375 zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber");
376 RETURN_THROWS();
377 }
378
379 ZEND_ASSERT(fiber->status == ZEND_FIBER_STATUS_RUNNING);
380
381 if (value) {
382 ZVAL_COPY(&fiber->value, value);
383 } else {
384 ZVAL_NULL(&fiber->value);
385 }
386
387 fiber->execute_data = execute_data;
388 fiber->status = ZEND_FIBER_STATUS_SUSPENDED;
389 fiber->stack_bottom->prev_execute_data = NULL;
390
391 zend_fiber_suspend(fiber);
392
393 if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) {
394 // This occurs on exit if the fiber never resumed, it has been GC'ed, so do not add a ref.
395 if (FIBER_G(error)) {
396 // Throw UnwindExit so finally blocks are not executed on fatal error.
397 zend_throw_unwind_exit();
398 } else {
399 // Otherwise throw FiberExit to execute finally blocks.
400 zend_throw_error(zend_ce_fiber_exit, "Fiber destroyed");
401 }
402 RETURN_THROWS();
403 }
404
405 fiber->status = ZEND_FIBER_STATUS_RUNNING;
406
407 if (fiber->exception) {
408 exception = fiber->exception;
409 fiber->exception = NULL;
410
411 zend_throw_exception_object(exception);
412 RETURN_THROWS();
413 }
414
415 RETVAL_COPY_VALUE(&fiber->value);
416 ZVAL_UNDEF(&fiber->value);
417 }
418
ZEND_METHOD(Fiber,resume)419 ZEND_METHOD(Fiber, resume)
420 {
421 zend_fiber *fiber;
422 zval *value = NULL;
423
424 ZEND_PARSE_PARAMETERS_START(0, 1)
425 Z_PARAM_OPTIONAL
426 Z_PARAM_ZVAL(value);
427 ZEND_PARSE_PARAMETERS_END();
428
429 fiber = (zend_fiber *) Z_OBJ_P(getThis());
430
431 if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) {
432 zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
433 RETURN_THROWS();
434 }
435
436 if (value) {
437 ZVAL_COPY(&fiber->value, value);
438 } else {
439 ZVAL_NULL(&fiber->value);
440 }
441
442 fiber->status = ZEND_FIBER_STATUS_RUNNING;
443 fiber->stack_bottom->prev_execute_data = execute_data;
444
445 zend_fiber_switch_to(fiber);
446
447 if (fiber->status & ZEND_FIBER_STATUS_FINISHED) {
448 RETURN_NULL();
449 }
450
451 RETVAL_COPY_VALUE(&fiber->value);
452 ZVAL_UNDEF(&fiber->value);
453 }
454
ZEND_METHOD(Fiber,throw)455 ZEND_METHOD(Fiber, throw)
456 {
457 zend_fiber *fiber;
458 zval *exception;
459
460 ZEND_PARSE_PARAMETERS_START(1, 1)
461 Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable)
462 ZEND_PARSE_PARAMETERS_END();
463
464 fiber = (zend_fiber *) Z_OBJ_P(getThis());
465
466 if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) {
467 zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
468 RETURN_THROWS();
469 }
470
471 Z_ADDREF_P(exception);
472 fiber->exception = exception;
473
474 fiber->status = ZEND_FIBER_STATUS_RUNNING;
475 fiber->stack_bottom->prev_execute_data = execute_data;
476
477 zend_fiber_switch_to(fiber);
478
479 if (fiber->status & ZEND_FIBER_STATUS_FINISHED) {
480 RETURN_NULL();
481 }
482
483 RETVAL_COPY_VALUE(&fiber->value);
484 ZVAL_UNDEF(&fiber->value);
485 }
486
ZEND_METHOD(Fiber,isStarted)487 ZEND_METHOD(Fiber, isStarted)
488 {
489 zend_fiber *fiber;
490
491 ZEND_PARSE_PARAMETERS_NONE();
492
493 fiber = (zend_fiber *) Z_OBJ_P(getThis());
494
495 RETURN_BOOL(fiber->status != ZEND_FIBER_STATUS_INIT);
496 }
497
ZEND_METHOD(Fiber,isSuspended)498 ZEND_METHOD(Fiber, isSuspended)
499 {
500 zend_fiber *fiber;
501
502 ZEND_PARSE_PARAMETERS_NONE();
503
504 fiber = (zend_fiber *) Z_OBJ_P(getThis());
505
506 RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_SUSPENDED);
507 }
508
ZEND_METHOD(Fiber,isRunning)509 ZEND_METHOD(Fiber, isRunning)
510 {
511 zend_fiber *fiber;
512
513 ZEND_PARSE_PARAMETERS_NONE();
514
515 fiber = (zend_fiber *) Z_OBJ_P(getThis());
516
517 RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_RUNNING);
518 }
519
ZEND_METHOD(Fiber,isTerminated)520 ZEND_METHOD(Fiber, isTerminated)
521 {
522 zend_fiber *fiber;
523
524 ZEND_PARSE_PARAMETERS_NONE();
525
526 fiber = (zend_fiber *) Z_OBJ_P(getThis());
527
528 RETURN_BOOL(fiber->status & ZEND_FIBER_STATUS_FINISHED);
529 }
530
ZEND_METHOD(Fiber,getReturn)531 ZEND_METHOD(Fiber, getReturn)
532 {
533 zend_fiber *fiber;
534
535 ZEND_PARSE_PARAMETERS_NONE();
536
537 fiber = (zend_fiber *) Z_OBJ_P(getThis());
538
539 if (fiber->status != ZEND_FIBER_STATUS_RETURNED) {
540 const char *message;
541
542 if (fiber->status == ZEND_FIBER_STATUS_INIT) {
543 message = "The fiber has not been started";
544 } else if (fiber->status == ZEND_FIBER_STATUS_THREW) {
545 message = "The fiber threw an exception";
546 } else {
547 message = "The fiber has not returned";
548 }
549
550 zend_throw_error(zend_ce_fiber_error, "Cannot get fiber return value: %s", message);
551 RETURN_THROWS();
552 }
553
554 RETURN_COPY(&fiber->value);
555 }
556
ZEND_METHOD(Fiber,getCurrent)557 ZEND_METHOD(Fiber, getCurrent)
558 {
559 zend_fiber *fiber;
560
561 ZEND_PARSE_PARAMETERS_NONE();
562
563 fiber = FIBER_G(current_fiber);
564
565 if (!fiber) {
566 RETURN_NULL();
567 }
568
569 RETURN_OBJ_COPY(&fiber->std);
570 }
571
ZEND_METHOD(FiberError,__construct)572 ZEND_METHOD(FiberError, __construct)
573 {
574 zend_object *object = Z_OBJ_P(getThis());
575
576 zend_throw_error(
577 NULL,
578 "The \"%s\" class is reserved for internal use and cannot be manually instantiated",
579 object->ce->name->val
580 );
581 }
582
ZEND_METHOD(FiberExit,__construct)583 ZEND_METHOD(FiberExit, __construct)
584 {
585 zend_object *object = Z_OBJ_P(getThis());
586
587 zend_throw_error(
588 NULL,
589 "The \"%s\" class is reserved for internal use and cannot be manually instantiated",
590 object->ce->name->val
591 );
592 }
593
ZEND_METHOD(ReflectionFiber,__construct)594 ZEND_METHOD(ReflectionFiber, __construct)
595 {
596 zend_fiber_reflection *reflection;
597 zval *object;
598
599 ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
600 Z_PARAM_OBJECT_OF_CLASS_EX(object, zend_ce_fiber, 0, 0)
601 ZEND_PARSE_PARAMETERS_END();
602
603 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
604 reflection->fiber = (zend_fiber *) Z_OBJ_P(object);
605
606 GC_ADDREF(&reflection->fiber->std);
607 }
608
ZEND_METHOD(ReflectionFiber,getFiber)609 ZEND_METHOD(ReflectionFiber, getFiber)
610 {
611 zend_fiber_reflection *reflection;
612
613 ZEND_PARSE_PARAMETERS_NONE();
614
615 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
616
617 RETURN_OBJ_COPY(&reflection->fiber->std);
618 }
619
620 #define REFLECTION_CHECK_VALID_FIBER(fiber) do { \
621 if (fiber == NULL || fiber->status == ZEND_FIBER_STATUS_INIT || fiber->status & ZEND_FIBER_STATUS_FINISHED) { \
622 zend_throw_error(NULL, "Cannot fetch information from a fiber that has not been started or is terminated"); \
623 return; \
624 } \
625 } while (0)
626
ZEND_METHOD(ReflectionFiber,getTrace)627 ZEND_METHOD(ReflectionFiber, getTrace)
628 {
629 zend_fiber_reflection *reflection;
630 zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT;
631 zend_execute_data *prev_execute_data;
632
633 ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1)
634 Z_PARAM_OPTIONAL
635 Z_PARAM_LONG(options);
636 ZEND_PARSE_PARAMETERS_END();
637
638 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
639
640 REFLECTION_CHECK_VALID_FIBER(reflection->fiber);
641
642 prev_execute_data = reflection->fiber->stack_bottom->prev_execute_data;
643 reflection->fiber->stack_bottom->prev_execute_data = NULL;
644
645 if (FIBER_G(current_fiber) != reflection->fiber) {
646 // No need to replace current execute data if within the current fiber.
647 EG(current_execute_data) = reflection->fiber->execute_data;
648 }
649
650 zend_fetch_debug_backtrace(return_value, 0, options, 0);
651
652 EG(current_execute_data) = execute_data; // Restore original execute data.
653 reflection->fiber->stack_bottom->prev_execute_data = prev_execute_data;
654 }
655
ZEND_METHOD(ReflectionFiber,getExecutingLine)656 ZEND_METHOD(ReflectionFiber, getExecutingLine)
657 {
658 zend_fiber_reflection *reflection;
659 zend_execute_data *prev_execute_data;
660
661 ZEND_PARSE_PARAMETERS_NONE();
662
663 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
664
665 REFLECTION_CHECK_VALID_FIBER(reflection->fiber);
666
667 if (FIBER_G(current_fiber) == reflection->fiber) {
668 prev_execute_data = execute_data->prev_execute_data;
669 } else {
670 prev_execute_data = reflection->fiber->execute_data->prev_execute_data;
671 }
672
673 RETURN_LONG(prev_execute_data->opline->lineno);
674 }
675
ZEND_METHOD(ReflectionFiber,getExecutingFile)676 ZEND_METHOD(ReflectionFiber, getExecutingFile)
677 {
678 zend_fiber_reflection *reflection;
679 zend_execute_data *prev_execute_data;
680
681 ZEND_PARSE_PARAMETERS_NONE();
682
683 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
684
685 REFLECTION_CHECK_VALID_FIBER(reflection->fiber);
686
687 if (FIBER_G(current_fiber) == reflection->fiber) {
688 prev_execute_data = execute_data->prev_execute_data;
689 } else {
690 prev_execute_data = reflection->fiber->execute_data->prev_execute_data;
691 }
692
693 RETURN_STR_COPY(prev_execute_data->func->op_array.filename);
694 }
695
ZEND_METHOD(ReflectionFiber,getCallable)696 ZEND_METHOD(ReflectionFiber, getCallable)
697 {
698 zend_fiber_reflection *reflection;
699 zend_fiber *fiber;
700
701 ZEND_PARSE_PARAMETERS_NONE();
702
703 reflection = (zend_fiber_reflection *) Z_OBJ_P(getThis());
704 fiber = reflection->fiber;
705
706 if (fiber == NULL || fiber->status & ZEND_FIBER_STATUS_FINISHED) {
707 zend_throw_error(NULL, "Cannot fetch the callable from a fiber that has terminated"); \
708 RETURN_THROWS();
709 }
710
711 RETURN_COPY(&fiber->fci.function_name);
712 }
713
zend_register_fiber_ce(void)714 void zend_register_fiber_ce(void)
715 {
716 FIBER_G(catch_handler) = zend_get_user_opcode_handler(ZEND_CATCH);
717 zend_set_user_opcode_handler(ZEND_CATCH, zend_fiber_catch_handler);
718
719 zend_ce_fiber = register_class_Fiber();
720 zend_ce_fiber->create_object = zend_fiber_object_create;
721 zend_ce_fiber->serialize = zend_class_serialize_deny;
722 zend_ce_fiber->unserialize = zend_class_unserialize_deny;
723
724 zend_fiber_handlers = std_object_handlers;
725 zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy;
726 zend_fiber_handlers.free_obj = zend_fiber_object_free;
727 zend_fiber_handlers.get_gc = zend_fiber_object_gc;
728 zend_fiber_handlers.clone_obj = NULL;
729
730 zend_ce_fiber_error = register_class_FiberError(zend_ce_error);
731 zend_ce_fiber_error->create_object = zend_ce_error->create_object;
732
733 zend_ce_fiber_exit = register_class_FiberExit(zend_ce_exception);
734 zend_ce_fiber_exit->create_object = zend_ce_exception->create_object;
735
736 zend_ce_reflection_fiber = register_class_ReflectionFiber();
737 zend_ce_reflection_fiber->create_object = zend_reflection_fiber_object_create;
738 zend_ce_reflection_fiber->serialize = zend_class_serialize_deny;
739 zend_ce_reflection_fiber->unserialize = zend_class_unserialize_deny;
740
741 zend_reflection_fiber_handlers = std_object_handlers;
742 zend_reflection_fiber_handlers.free_obj = zend_reflection_fiber_object_free;
743 zend_reflection_fiber_handlers.clone_obj = NULL;
744
745 zend_llist_init(&zend_fiber_observers_list, sizeof(zend_observer_fiber_switch_handler), NULL, 1);
746 }
747
zend_fiber_shutdown(void)748 void zend_fiber_shutdown(void)
749 {
750 zend_set_user_opcode_handler(ZEND_CATCH, FIBER_G(catch_handler));
751
752 zend_llist_destroy(&zend_fiber_observers_list);
753 }
754
zend_fiber_init(void)755 void zend_fiber_init(void)
756 {
757 FIBER_G(current_fiber) = NULL;
758 }
759