xref: /ext-fiber/src/fiber.c (revision c265d9fb)
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