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: Aaron Piotrowski <aaron@trowski.com> |
16 | Martin Schröder <m.schroeder2007@gmail.com> |
17 +----------------------------------------------------------------------+
18 */
19
20 #include "zend.h"
21 #include "zend_API.h"
22 #include "zend_gc.h"
23 #include "zend_ini.h"
24 #include "zend_vm.h"
25 #include "zend_exceptions.h"
26 #include "zend_builtin_functions.h"
27 #include "zend_observer.h"
28 #include "zend_mmap.h"
29 #include "zend_compile.h"
30 #include "zend_closures.h"
31 #include "zend_generators.h"
32
33 #include "zend_fibers.h"
34 #include "zend_fibers_arginfo.h"
35
36 #ifdef HAVE_VALGRIND
37 # include <valgrind/valgrind.h>
38 #endif
39
40 #ifdef ZEND_FIBER_UCONTEXT
41 # include <ucontext.h>
42 #endif
43
44 #ifndef ZEND_WIN32
45 # include <unistd.h>
46 # include <sys/mman.h>
47 # include <limits.h>
48
49 # if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
50 # define MAP_ANONYMOUS MAP_ANON
51 # endif
52
53 /* FreeBSD require a first (i.e. addr) argument of mmap(2) is not NULL
54 * if MAP_STACK is passed.
55 * http://www.FreeBSD.org/cgi/query-pr.cgi?pr=158755 */
56 # if !defined(MAP_STACK) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
57 # undef MAP_STACK
58 # define MAP_STACK 0
59 # endif
60
61 # ifndef MAP_FAILED
62 # define MAP_FAILED ((void * ) -1)
63 # endif
64 #endif
65
66 #ifdef __SANITIZE_ADDRESS__
67 # include <sanitizer/asan_interface.h>
68 # include <sanitizer/common_interface_defs.h>
69 #endif
70
71 /* Encapsulates the fiber C stack with extension for debugging tools. */
72 struct _zend_fiber_stack {
73 void *pointer;
74 size_t size;
75
76 #ifdef HAVE_VALGRIND
77 unsigned int valgrind_stack_id;
78 #endif
79
80 #ifdef __SANITIZE_ADDRESS__
81 const void *asan_pointer;
82 size_t asan_size;
83 #endif
84
85 #ifdef ZEND_FIBER_UCONTEXT
86 /* Embedded ucontext to avoid unnecessary memory allocations. */
87 ucontext_t ucontext;
88 #endif
89 };
90
91 /* Zend VM state that needs to be captured / restored during fiber context switch. */
92 typedef struct _zend_fiber_vm_state {
93 zend_vm_stack vm_stack;
94 zval *vm_stack_top;
95 zval *vm_stack_end;
96 size_t vm_stack_page_size;
97 zend_execute_data *current_execute_data;
98 int error_reporting;
99 uint32_t jit_trace_num;
100 JMP_BUF *bailout;
101 zend_fiber *active_fiber;
102 } zend_fiber_vm_state;
103
zend_fiber_capture_vm_state(zend_fiber_vm_state * state)104 static zend_always_inline void zend_fiber_capture_vm_state(zend_fiber_vm_state *state)
105 {
106 state->vm_stack = EG(vm_stack);
107 state->vm_stack_top = EG(vm_stack_top);
108 state->vm_stack_end = EG(vm_stack_end);
109 state->vm_stack_page_size = EG(vm_stack_page_size);
110 state->current_execute_data = EG(current_execute_data);
111 state->error_reporting = EG(error_reporting);
112 state->jit_trace_num = EG(jit_trace_num);
113 state->bailout = EG(bailout);
114 state->active_fiber = EG(active_fiber);
115 }
116
zend_fiber_restore_vm_state(zend_fiber_vm_state * state)117 static zend_always_inline void zend_fiber_restore_vm_state(zend_fiber_vm_state *state)
118 {
119 EG(vm_stack) = state->vm_stack;
120 EG(vm_stack_top) = state->vm_stack_top;
121 EG(vm_stack_end) = state->vm_stack_end;
122 EG(vm_stack_page_size) = state->vm_stack_page_size;
123 EG(current_execute_data) = state->current_execute_data;
124 EG(error_reporting) = state->error_reporting;
125 EG(jit_trace_num) = state->jit_trace_num;
126 EG(bailout) = state->bailout;
127 EG(active_fiber) = state->active_fiber;
128 }
129
130 #ifdef ZEND_FIBER_UCONTEXT
131 ZEND_TLS zend_fiber_transfer *transfer_data;
132 #else
133 /* boost_context_data is our customized definition of struct transfer_t as
134 * provided by boost.context in fcontext.hpp:
135 *
136 * typedef void* fcontext_t;
137 *
138 * struct transfer_t {
139 * fcontext_t fctx;
140 * void *data;
141 * }; */
142
143 typedef struct {
144 void *handle;
145 zend_fiber_transfer *transfer;
146 } boost_context_data;
147
148 /* These functions are defined in assembler files provided by boost.context (located in "Zend/asm"). */
149 extern void *make_fcontext(void *sp, size_t size, void (*fn)(boost_context_data));
150 extern ZEND_INDIRECT_RETURN boost_context_data jump_fcontext(void *to, zend_fiber_transfer *transfer);
151 #endif
152
153 ZEND_API zend_class_entry *zend_ce_fiber;
154 static zend_class_entry *zend_ce_fiber_error;
155
156 static zend_object_handlers zend_fiber_handlers;
157
158 static zend_function zend_fiber_function = { ZEND_INTERNAL_FUNCTION };
159
160 ZEND_TLS uint32_t zend_fiber_switch_blocking = 0;
161
162 #define ZEND_FIBER_DEFAULT_PAGE_SIZE 4096
163
zend_fiber_get_page_size(void)164 static size_t zend_fiber_get_page_size(void)
165 {
166 static size_t page_size = 0;
167
168 if (!page_size) {
169 page_size = zend_get_page_size();
170 if (!page_size || (page_size & (page_size - 1))) {
171 /* anyway, we have to return a valid result */
172 page_size = ZEND_FIBER_DEFAULT_PAGE_SIZE;
173 }
174 }
175
176 return page_size;
177 }
178
zend_fiber_stack_allocate(size_t size)179 static zend_fiber_stack *zend_fiber_stack_allocate(size_t size)
180 {
181 void *pointer;
182 const size_t page_size = zend_fiber_get_page_size();
183 const size_t minimum_stack_size = page_size + ZEND_FIBER_GUARD_PAGES * page_size;
184
185 if (size < minimum_stack_size) {
186 zend_throw_exception_ex(NULL, 0, "Fiber stack size is too small, it needs to be at least %zu bytes", minimum_stack_size);
187 return NULL;
188 }
189
190 const size_t stack_size = (size + page_size - 1) / page_size * page_size;
191 const size_t alloc_size = stack_size + ZEND_FIBER_GUARD_PAGES * page_size;
192
193 #ifdef ZEND_WIN32
194 pointer = VirtualAlloc(0, alloc_size, MEM_COMMIT, PAGE_READWRITE);
195
196 if (!pointer) {
197 DWORD err = GetLastError();
198 char *errmsg = php_win32_error_to_msg(err);
199 zend_throw_exception_ex(NULL, 0, "Fiber stack allocate failed: VirtualAlloc failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown");
200 php_win32_error_msg_free(errmsg);
201 return NULL;
202 }
203
204 # if ZEND_FIBER_GUARD_PAGES
205 DWORD protect;
206
207 if (!VirtualProtect(pointer, ZEND_FIBER_GUARD_PAGES * page_size, PAGE_READWRITE | PAGE_GUARD, &protect)) {
208 DWORD err = GetLastError();
209 char *errmsg = php_win32_error_to_msg(err);
210 zend_throw_exception_ex(NULL, 0, "Fiber stack protect failed: VirtualProtect failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown");
211 php_win32_error_msg_free(errmsg);
212 VirtualFree(pointer, 0, MEM_RELEASE);
213 return NULL;
214 }
215 # endif
216 #else
217 pointer = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
218
219 if (pointer == MAP_FAILED) {
220 zend_throw_exception_ex(NULL, 0, "Fiber stack allocate failed: mmap failed: %s (%d)", strerror(errno), errno);
221 return NULL;
222 }
223
224 zend_mmap_set_name(pointer, alloc_size, "zend_fiber_stack");
225
226 # if ZEND_FIBER_GUARD_PAGES
227 if (mprotect(pointer, ZEND_FIBER_GUARD_PAGES * page_size, PROT_NONE) < 0) {
228 zend_throw_exception_ex(NULL, 0, "Fiber stack protect failed: mprotect failed: %s (%d)", strerror(errno), errno);
229 munmap(pointer, alloc_size);
230 return NULL;
231 }
232 # endif
233 #endif
234
235 zend_fiber_stack *stack = emalloc(sizeof(zend_fiber_stack));
236
237 stack->pointer = (void *) ((uintptr_t) pointer + ZEND_FIBER_GUARD_PAGES * page_size);
238 stack->size = stack_size;
239
240 #ifdef VALGRIND_STACK_REGISTER
241 uintptr_t base = (uintptr_t) stack->pointer;
242 stack->valgrind_stack_id = VALGRIND_STACK_REGISTER(base, base + stack->size);
243 #endif
244
245 #ifdef __SANITIZE_ADDRESS__
246 stack->asan_pointer = stack->pointer;
247 stack->asan_size = stack->size;
248 #endif
249
250 return stack;
251 }
252
zend_fiber_stack_free(zend_fiber_stack * stack)253 static void zend_fiber_stack_free(zend_fiber_stack *stack)
254 {
255 #ifdef VALGRIND_STACK_DEREGISTER
256 VALGRIND_STACK_DEREGISTER(stack->valgrind_stack_id);
257 #endif
258
259 const size_t page_size = zend_fiber_get_page_size();
260
261 void *pointer = (void *) ((uintptr_t) stack->pointer - ZEND_FIBER_GUARD_PAGES * page_size);
262
263 #ifdef __SANITIZE_ADDRESS__
264 /* If another mmap happens after unmapping, it may trigger the stale stack red zones
265 * so we have to unpoison it before unmapping. */
266 ASAN_UNPOISON_MEMORY_REGION(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size);
267 #endif
268
269 #ifdef ZEND_WIN32
270 VirtualFree(pointer, 0, MEM_RELEASE);
271 #else
272 munmap(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size);
273 #endif
274
275 efree(stack);
276 }
277 #ifdef ZEND_FIBER_UCONTEXT
zend_fiber_trampoline(void)278 static ZEND_NORETURN void zend_fiber_trampoline(void)
279 #else
280 static ZEND_NORETURN void zend_fiber_trampoline(boost_context_data data)
281 #endif
282 {
283 /* Initialize transfer struct with a copy of passed data. */
284 #ifdef ZEND_FIBER_UCONTEXT
285 zend_fiber_transfer transfer = *transfer_data;
286 #else
287 zend_fiber_transfer transfer = *data.transfer;
288 #endif
289
290 zend_fiber_context *from = transfer.context;
291
292 #ifdef __SANITIZE_ADDRESS__
293 __sanitizer_finish_switch_fiber(NULL, &from->stack->asan_pointer, &from->stack->asan_size);
294 #endif
295
296 #ifndef ZEND_FIBER_UCONTEXT
297 /* Get the context that resumed us and update its handle to allow for symmetric coroutines. */
298 from->handle = data.handle;
299 #endif
300
301 /* Ensure that previous fiber will be cleaned up (needed by symmetric coroutines). */
302 if (from->status == ZEND_FIBER_STATUS_DEAD) {
303 zend_fiber_destroy_context(from);
304 }
305
306 zend_fiber_context *context = EG(current_fiber_context);
307
308 context->function(&transfer);
309 context->status = ZEND_FIBER_STATUS_DEAD;
310
311 /* Final context switch, the fiber must not be resumed afterwards! */
312 zend_fiber_switch_context(&transfer);
313
314 /* Abort here because we are in an inconsistent program state. */
315 abort();
316 }
317
zend_fiber_switch_block(void)318 ZEND_API void zend_fiber_switch_block(void)
319 {
320 ++zend_fiber_switch_blocking;
321 }
322
zend_fiber_switch_unblock(void)323 ZEND_API void zend_fiber_switch_unblock(void)
324 {
325 ZEND_ASSERT(zend_fiber_switch_blocking && "Fiber switching was not blocked");
326 --zend_fiber_switch_blocking;
327 }
328
zend_fiber_switch_blocked(void)329 ZEND_API bool zend_fiber_switch_blocked(void)
330 {
331 return zend_fiber_switch_blocking;
332 }
333
zend_fiber_init_context(zend_fiber_context * context,void * kind,zend_fiber_coroutine coroutine,size_t stack_size)334 ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, void *kind, zend_fiber_coroutine coroutine, size_t stack_size)
335 {
336 context->stack = zend_fiber_stack_allocate(stack_size);
337
338 if (UNEXPECTED(!context->stack)) {
339 return false;
340 }
341
342 #ifdef ZEND_FIBER_UCONTEXT
343 ucontext_t *handle = &context->stack->ucontext;
344
345 getcontext(handle);
346
347 handle->uc_stack.ss_size = context->stack->size;
348 handle->uc_stack.ss_sp = context->stack->pointer;
349 handle->uc_stack.ss_flags = 0;
350 handle->uc_link = NULL;
351
352 makecontext(handle, (void (*)(void)) zend_fiber_trampoline, 0);
353
354 context->handle = handle;
355 #else
356 // Stack grows down, calculate the top of the stack. make_fcontext then shifts pointer to lower 16-byte boundary.
357 void *stack = (void *) ((uintptr_t) context->stack->pointer + context->stack->size);
358
359 context->handle = make_fcontext(stack, context->stack->size, zend_fiber_trampoline);
360 ZEND_ASSERT(context->handle != NULL && "make_fcontext() never returns NULL");
361 #endif
362
363 context->kind = kind;
364 context->function = coroutine;
365
366 // Set status in case memory has not been zeroed.
367 context->status = ZEND_FIBER_STATUS_INIT;
368
369 zend_observer_fiber_init_notify(context);
370
371 return true;
372 }
373
zend_fiber_destroy_context(zend_fiber_context * context)374 ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context)
375 {
376 zend_observer_fiber_destroy_notify(context);
377
378 if (context->cleanup) {
379 context->cleanup(context);
380 }
381
382 zend_fiber_stack_free(context->stack);
383 }
384
zend_fiber_switch_context(zend_fiber_transfer * transfer)385 ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)
386 {
387 zend_fiber_context *from = EG(current_fiber_context);
388 zend_fiber_context *to = transfer->context;
389 zend_fiber_vm_state state;
390
391 ZEND_ASSERT(to && to->handle && to->status != ZEND_FIBER_STATUS_DEAD && "Invalid fiber context");
392 ZEND_ASSERT(from && "From fiber context must be present");
393 ZEND_ASSERT(to != from && "Cannot switch into the running fiber context");
394
395 /* Assert that all error transfers hold a Throwable value. */
396 ZEND_ASSERT((
397 !(transfer->flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) ||
398 (Z_TYPE(transfer->value) == IS_OBJECT && (
399 zend_is_unwind_exit(Z_OBJ(transfer->value)) ||
400 zend_is_graceful_exit(Z_OBJ(transfer->value)) ||
401 instanceof_function(Z_OBJCE(transfer->value), zend_ce_throwable)
402 ))
403 ) && "Error transfer requires a throwable value");
404
405 zend_observer_fiber_switch_notify(from, to);
406
407 zend_fiber_capture_vm_state(&state);
408
409 to->status = ZEND_FIBER_STATUS_RUNNING;
410
411 if (EXPECTED(from->status == ZEND_FIBER_STATUS_RUNNING)) {
412 from->status = ZEND_FIBER_STATUS_SUSPENDED;
413 }
414
415 /* Update transfer context with the current fiber before switching. */
416 transfer->context = from;
417
418 EG(current_fiber_context) = to;
419
420 #ifdef __SANITIZE_ADDRESS__
421 void *fake_stack = NULL;
422 __sanitizer_start_switch_fiber(
423 from->status != ZEND_FIBER_STATUS_DEAD ? &fake_stack : NULL,
424 to->stack->asan_pointer,
425 to->stack->asan_size);
426 #endif
427
428 #ifdef ZEND_FIBER_UCONTEXT
429 transfer_data = transfer;
430
431 swapcontext(from->handle, to->handle);
432
433 /* Copy transfer struct because it might live on the other fiber's stack that will eventually be destroyed. */
434 *transfer = *transfer_data;
435 #else
436 boost_context_data data = jump_fcontext(to->handle, transfer);
437
438 /* Copy transfer struct because it might live on the other fiber's stack that will eventually be destroyed. */
439 *transfer = *data.transfer;
440 #endif
441
442 to = transfer->context;
443
444 #ifndef ZEND_FIBER_UCONTEXT
445 /* Get the context that resumed us and update its handle to allow for symmetric coroutines. */
446 to->handle = data.handle;
447 #endif
448
449 #ifdef __SANITIZE_ADDRESS__
450 __sanitizer_finish_switch_fiber(fake_stack, &to->stack->asan_pointer, &to->stack->asan_size);
451 #endif
452
453 EG(current_fiber_context) = from;
454
455 zend_fiber_restore_vm_state(&state);
456
457 /* Destroy prior context if it has been marked as dead. */
458 if (to->status == ZEND_FIBER_STATUS_DEAD) {
459 zend_fiber_destroy_context(to);
460 }
461 }
462
zend_fiber_cleanup(zend_fiber_context * context)463 static void zend_fiber_cleanup(zend_fiber_context *context)
464 {
465 zend_fiber *fiber = zend_fiber_from_context(context);
466
467 zend_vm_stack current_stack = EG(vm_stack);
468 EG(vm_stack) = fiber->vm_stack;
469 zend_vm_stack_destroy();
470 EG(vm_stack) = current_stack;
471 fiber->execute_data = NULL;
472 fiber->stack_bottom = NULL;
473 fiber->caller = NULL;
474 }
475
zend_fiber_execute(zend_fiber_transfer * transfer)476 static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer)
477 {
478 ZEND_ASSERT(Z_TYPE(transfer->value) == IS_NULL && "Initial transfer value to fiber context must be NULL");
479 ZEND_ASSERT(!transfer->flags && "No flags should be set on initial transfer");
480
481 zend_fiber *fiber = EG(active_fiber);
482
483 /* Determine the current error_reporting ini setting. */
484 zend_long error_reporting = INI_INT("error_reporting");
485 /* If error_reporting is 0 and not explicitly set to 0, INI_STR returns a null pointer. */
486 if (!error_reporting && !INI_STR("error_reporting")) {
487 error_reporting = E_ALL;
488 }
489
490 EG(vm_stack) = NULL;
491
492 zend_first_try {
493 zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL);
494 EG(vm_stack) = stack;
495 EG(vm_stack_top) = stack->top + ZEND_CALL_FRAME_SLOT;
496 EG(vm_stack_end) = stack->end;
497 EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE;
498
499 fiber->execute_data = (zend_execute_data *) stack->top;
500 fiber->stack_bottom = fiber->execute_data;
501
502 memset(fiber->execute_data, 0, sizeof(zend_execute_data));
503
504 fiber->execute_data->func = &zend_fiber_function;
505 fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
506
507 EG(current_execute_data) = fiber->execute_data;
508 EG(jit_trace_num) = 0;
509 EG(error_reporting) = error_reporting;
510
511 fiber->fci.retval = &fiber->result;
512
513 zend_call_function(&fiber->fci, &fiber->fci_cache);
514
515 /* Cleanup callback and unset field to prevent GC / duplicate dtor issues. */
516 zval_ptr_dtor(&fiber->fci.function_name);
517 ZVAL_UNDEF(&fiber->fci.function_name);
518
519 if (EG(exception)) {
520 if (!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)
521 || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))
522 ) {
523 fiber->flags |= ZEND_FIBER_FLAG_THREW;
524 transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR;
525
526 ZVAL_OBJ_COPY(&transfer->value, EG(exception));
527 }
528
529 zend_clear_exception();
530 }
531 } zend_catch {
532 fiber->flags |= ZEND_FIBER_FLAG_BAILOUT;
533 transfer->flags = ZEND_FIBER_TRANSFER_FLAG_BAILOUT;
534 } zend_end_try();
535
536 fiber->context.cleanup = &zend_fiber_cleanup;
537 fiber->vm_stack = EG(vm_stack);
538
539 transfer->context = fiber->caller;
540 }
541
542 /* Handles forwarding of result / error from a transfer into the running fiber. */
zend_fiber_delegate_transfer_result(zend_fiber_transfer * transfer,INTERNAL_FUNCTION_PARAMETERS)543 static zend_always_inline void zend_fiber_delegate_transfer_result(
544 zend_fiber_transfer *transfer, INTERNAL_FUNCTION_PARAMETERS
545 ) {
546 if (transfer->flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) {
547 /* Use internal throw to skip the Throwable-check that would fail for (graceful) exit. */
548 zend_throw_exception_internal(Z_OBJ(transfer->value));
549 RETURN_THROWS();
550 }
551
552 RETURN_COPY_VALUE(&transfer->value);
553 }
554
zend_fiber_switch_to(zend_fiber_context * context,zval * value,bool exception)555 static zend_always_inline zend_fiber_transfer zend_fiber_switch_to(
556 zend_fiber_context *context, zval *value, bool exception
557 ) {
558 zend_fiber_transfer transfer = {
559 .context = context,
560 .flags = exception ? ZEND_FIBER_TRANSFER_FLAG_ERROR : 0,
561 };
562
563 if (value) {
564 ZVAL_COPY(&transfer.value, value);
565 } else {
566 ZVAL_NULL(&transfer.value);
567 }
568
569 zend_fiber_switch_context(&transfer);
570
571 /* Forward bailout into current fiber. */
572 if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) {
573 EG(active_fiber) = NULL;
574 zend_bailout();
575 }
576
577 return transfer;
578 }
579
zend_fiber_resume(zend_fiber * fiber,zval * value,bool exception)580 static zend_always_inline zend_fiber_transfer zend_fiber_resume(zend_fiber *fiber, zval *value, bool exception)
581 {
582 zend_fiber *previous = EG(active_fiber);
583
584 if (previous) {
585 previous->execute_data = EG(current_execute_data);
586 }
587
588 fiber->caller = EG(current_fiber_context);
589 EG(active_fiber) = fiber;
590
591 zend_fiber_transfer transfer = zend_fiber_switch_to(fiber->previous, value, exception);
592
593 EG(active_fiber) = previous;
594
595 return transfer;
596 }
597
zend_fiber_suspend(zend_fiber * fiber,zval * value)598 static zend_always_inline zend_fiber_transfer zend_fiber_suspend(zend_fiber *fiber, zval *value)
599 {
600 ZEND_ASSERT(fiber->caller != NULL);
601
602 zend_fiber_context *caller = fiber->caller;
603 fiber->previous = EG(current_fiber_context);
604 fiber->caller = NULL;
605 fiber->execute_data = EG(current_execute_data);
606
607 return zend_fiber_switch_to(caller, value, false);
608 }
609
zend_fiber_object_create(zend_class_entry * ce)610 static zend_object *zend_fiber_object_create(zend_class_entry *ce)
611 {
612 zend_fiber *fiber = emalloc(sizeof(zend_fiber));
613
614 memset(fiber, 0, sizeof(zend_fiber));
615
616 zend_object_std_init(&fiber->std, ce);
617 fiber->std.handlers = &zend_fiber_handlers;
618
619 return &fiber->std;
620 }
621
zend_fiber_object_destroy(zend_object * object)622 static void zend_fiber_object_destroy(zend_object *object)
623 {
624 zend_fiber *fiber = (zend_fiber *) object;
625
626 if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED) {
627 return;
628 }
629
630 zend_object *exception = EG(exception);
631 EG(exception) = NULL;
632
633 zval graceful_exit;
634 ZVAL_OBJ(&graceful_exit, zend_create_graceful_exit());
635
636 fiber->flags |= ZEND_FIBER_FLAG_DESTROYED;
637
638 zend_fiber_transfer transfer = zend_fiber_resume(fiber, &graceful_exit, true);
639
640 zval_ptr_dtor(&graceful_exit);
641
642 if (transfer.flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) {
643 EG(exception) = Z_OBJ(transfer.value);
644
645 if (!exception && EG(current_execute_data) && EG(current_execute_data)->func
646 && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
647 zend_rethrow_exception(EG(current_execute_data));
648 }
649
650 zend_exception_set_previous(EG(exception), exception);
651
652 if (!EG(current_execute_data)) {
653 zend_exception_error(EG(exception), E_ERROR);
654 }
655 } else {
656 zval_ptr_dtor(&transfer.value);
657 EG(exception) = exception;
658 }
659 }
660
zend_fiber_object_free(zend_object * object)661 static void zend_fiber_object_free(zend_object *object)
662 {
663 zend_fiber *fiber = (zend_fiber *) object;
664
665 zval_ptr_dtor(&fiber->fci.function_name);
666 zval_ptr_dtor(&fiber->result);
667
668 zend_object_std_dtor(&fiber->std);
669 }
670
zend_fiber_object_gc(zend_object * object,zval ** table,int * num)671 static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *num)
672 {
673 zend_fiber *fiber = (zend_fiber *) object;
674 zend_get_gc_buffer *buf = zend_get_gc_buffer_create();
675
676 zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name);
677 zend_get_gc_buffer_add_zval(buf, &fiber->result);
678
679 if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL) {
680 zend_get_gc_buffer_use(buf, table, num);
681 return NULL;
682 }
683
684 HashTable *lastSymTable = NULL;
685 zend_execute_data *ex = fiber->execute_data;
686 for (; ex; ex = ex->prev_execute_data) {
687 HashTable *symTable;
688 if (ZEND_CALL_INFO(ex) & ZEND_CALL_GENERATOR) {
689 /* The generator object is stored in ex->return_value */
690 zend_generator *generator = (zend_generator*)ex->return_value;
691 /* There are two cases to consider:
692 * - If the generator is currently running, the Generator's GC
693 * handler will ignore it because it is not collectable. However,
694 * in this context the generator is suspended in Fiber::suspend()
695 * and may be collectable, so we can inspect it.
696 * - If the generator is not running, the Generator's GC handler
697 * will inspect it. In this case we have to skip the frame.
698 */
699 if (!(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) {
700 continue;
701 }
702 symTable = zend_generator_frame_gc(buf, generator);
703 } else {
704 symTable = zend_unfinished_execution_gc_ex(ex, ex->func && ZEND_USER_CODE(ex->func->type) ? ex->call : NULL, buf, false);
705 }
706 if (symTable) {
707 if (lastSymTable) {
708 zval *val;
709 ZEND_HASH_FOREACH_VAL(lastSymTable, val) {
710 if (EXPECTED(Z_TYPE_P(val) == IS_INDIRECT)) {
711 val = Z_INDIRECT_P(val);
712 }
713 zend_get_gc_buffer_add_zval(buf, val);
714 } ZEND_HASH_FOREACH_END();
715 }
716 lastSymTable = symTable;
717 }
718 }
719
720 zend_get_gc_buffer_use(buf, table, num);
721
722 return lastSymTable;
723 }
724
ZEND_METHOD(Fiber,__construct)725 ZEND_METHOD(Fiber, __construct)
726 {
727 zend_fcall_info fci;
728 zend_fcall_info_cache fcc;
729
730 ZEND_PARSE_PARAMETERS_START(1, 1)
731 Z_PARAM_FUNC(fci, fcc)
732 ZEND_PARSE_PARAMETERS_END();
733
734 zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
735
736 if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_INIT || Z_TYPE(fiber->fci.function_name) != IS_UNDEF)) {
737 zend_throw_error(zend_ce_fiber_error, "Cannot call constructor twice");
738 RETURN_THROWS();
739 }
740
741 fiber->fci = fci;
742 fiber->fci_cache = fcc;
743
744 // Keep a reference to closures or callable objects while the fiber is running.
745 Z_TRY_ADDREF(fiber->fci.function_name);
746 }
747
ZEND_METHOD(Fiber,start)748 ZEND_METHOD(Fiber, start)
749 {
750 zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
751
752 ZEND_PARSE_PARAMETERS_START(0, -1)
753 Z_PARAM_VARIADIC_WITH_NAMED(fiber->fci.params, fiber->fci.param_count, fiber->fci.named_params);
754 ZEND_PARSE_PARAMETERS_END();
755
756 if (UNEXPECTED(zend_fiber_switch_blocked())) {
757 zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context");
758 RETURN_THROWS();
759 }
760
761 if (fiber->context.status != ZEND_FIBER_STATUS_INIT) {
762 zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started");
763 RETURN_THROWS();
764 }
765
766 if (!zend_fiber_init_context(&fiber->context, zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size))) {
767 RETURN_THROWS();
768 }
769
770 fiber->previous = &fiber->context;
771
772 zend_fiber_transfer transfer = zend_fiber_resume(fiber, NULL, false);
773
774 zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU);
775 }
776
ZEND_METHOD(Fiber,suspend)777 ZEND_METHOD(Fiber, suspend)
778 {
779 zval *value = NULL;
780
781 ZEND_PARSE_PARAMETERS_START(0, 1)
782 Z_PARAM_OPTIONAL
783 Z_PARAM_ZVAL(value);
784 ZEND_PARSE_PARAMETERS_END();
785
786 zend_fiber *fiber = EG(active_fiber);
787
788 if (UNEXPECTED(!fiber)) {
789 zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber");
790 RETURN_THROWS();
791 }
792
793 if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
794 zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber");
795 RETURN_THROWS();
796 }
797
798 if (UNEXPECTED(zend_fiber_switch_blocked())) {
799 zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context");
800 RETURN_THROWS();
801 }
802
803 ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_RUNNING || fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED);
804
805 fiber->stack_bottom->prev_execute_data = NULL;
806
807 zend_fiber_transfer transfer = zend_fiber_suspend(fiber, value);
808
809 zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU);
810 }
811
ZEND_METHOD(Fiber,resume)812 ZEND_METHOD(Fiber, resume)
813 {
814 zend_fiber *fiber;
815 zval *value = NULL;
816
817 ZEND_PARSE_PARAMETERS_START(0, 1)
818 Z_PARAM_OPTIONAL
819 Z_PARAM_ZVAL(value);
820 ZEND_PARSE_PARAMETERS_END();
821
822 if (UNEXPECTED(zend_fiber_switch_blocked())) {
823 zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context");
824 RETURN_THROWS();
825 }
826
827 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
828
829 if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) {
830 zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
831 RETURN_THROWS();
832 }
833
834 fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
835
836 zend_fiber_transfer transfer = zend_fiber_resume(fiber, value, false);
837
838 zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU);
839 }
840
ZEND_METHOD(Fiber,throw)841 ZEND_METHOD(Fiber, throw)
842 {
843 zend_fiber *fiber;
844 zval *exception;
845
846 ZEND_PARSE_PARAMETERS_START(1, 1)
847 Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable)
848 ZEND_PARSE_PARAMETERS_END();
849
850 if (UNEXPECTED(zend_fiber_switch_blocked())) {
851 zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context");
852 RETURN_THROWS();
853 }
854
855 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
856
857 if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) {
858 zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended");
859 RETURN_THROWS();
860 }
861
862 fiber->stack_bottom->prev_execute_data = EG(current_execute_data);
863
864 zend_fiber_transfer transfer = zend_fiber_resume(fiber, exception, true);
865
866 zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU);
867 }
868
ZEND_METHOD(Fiber,isStarted)869 ZEND_METHOD(Fiber, isStarted)
870 {
871 zend_fiber *fiber;
872
873 ZEND_PARSE_PARAMETERS_NONE();
874
875 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
876
877 RETURN_BOOL(fiber->context.status != ZEND_FIBER_STATUS_INIT);
878 }
879
ZEND_METHOD(Fiber,isSuspended)880 ZEND_METHOD(Fiber, isSuspended)
881 {
882 zend_fiber *fiber;
883
884 ZEND_PARSE_PARAMETERS_NONE();
885
886 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
887
888 RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL);
889 }
890
ZEND_METHOD(Fiber,isRunning)891 ZEND_METHOD(Fiber, isRunning)
892 {
893 zend_fiber *fiber;
894
895 ZEND_PARSE_PARAMETERS_NONE();
896
897 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
898
899 RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_RUNNING || fiber->caller != NULL);
900 }
901
ZEND_METHOD(Fiber,isTerminated)902 ZEND_METHOD(Fiber, isTerminated)
903 {
904 zend_fiber *fiber;
905
906 ZEND_PARSE_PARAMETERS_NONE();
907
908 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
909
910 RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_DEAD);
911 }
912
ZEND_METHOD(Fiber,getReturn)913 ZEND_METHOD(Fiber, getReturn)
914 {
915 zend_fiber *fiber;
916 const char *message;
917
918 ZEND_PARSE_PARAMETERS_NONE();
919
920 fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS);
921
922 if (fiber->context.status == ZEND_FIBER_STATUS_DEAD) {
923 if (fiber->flags & ZEND_FIBER_FLAG_THREW) {
924 message = "The fiber threw an exception";
925 } else if (fiber->flags & ZEND_FIBER_FLAG_BAILOUT) {
926 message = "The fiber exited with a fatal error";
927 } else {
928 RETURN_COPY_DEREF(&fiber->result);
929 }
930 } else if (fiber->context.status == ZEND_FIBER_STATUS_INIT) {
931 message = "The fiber has not been started";
932 } else {
933 message = "The fiber has not returned";
934 }
935
936 zend_throw_error(zend_ce_fiber_error, "Cannot get fiber return value: %s", message);
937 RETURN_THROWS();
938 }
939
ZEND_METHOD(Fiber,getCurrent)940 ZEND_METHOD(Fiber, getCurrent)
941 {
942 ZEND_PARSE_PARAMETERS_NONE();
943
944 zend_fiber *fiber = EG(active_fiber);
945
946 if (!fiber) {
947 RETURN_NULL();
948 }
949
950 RETURN_OBJ_COPY(&fiber->std);
951 }
952
ZEND_METHOD(FiberError,__construct)953 ZEND_METHOD(FiberError, __construct)
954 {
955 zend_throw_error(
956 NULL,
957 "The \"%s\" class is reserved for internal use and cannot be manually instantiated",
958 ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)
959 );
960 }
961
962
zend_register_fiber_ce(void)963 void zend_register_fiber_ce(void)
964 {
965 zend_ce_fiber = register_class_Fiber();
966 zend_ce_fiber->create_object = zend_fiber_object_create;
967
968 zend_fiber_handlers = std_object_handlers;
969 zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy;
970 zend_fiber_handlers.free_obj = zend_fiber_object_free;
971 zend_fiber_handlers.get_gc = zend_fiber_object_gc;
972 zend_fiber_handlers.clone_obj = NULL;
973
974 zend_ce_fiber_error = register_class_FiberError(zend_ce_error);
975 zend_ce_fiber_error->create_object = zend_ce_error->create_object;
976 }
977
zend_fiber_init(void)978 void zend_fiber_init(void)
979 {
980 zend_fiber_context *context = ecalloc(1, sizeof(zend_fiber_context));
981
982 #if defined(__SANITIZE_ADDRESS__) || defined(ZEND_FIBER_UCONTEXT)
983 // Main fiber stack is only needed if ASan or ucontext is enabled.
984 context->stack = emalloc(sizeof(zend_fiber_stack));
985
986 #ifdef ZEND_FIBER_UCONTEXT
987 context->handle = &context->stack->ucontext;
988 #endif
989 #endif
990
991 context->status = ZEND_FIBER_STATUS_RUNNING;
992
993 EG(main_fiber_context) = context;
994 EG(current_fiber_context) = context;
995 EG(active_fiber) = NULL;
996
997 zend_fiber_switch_blocking = 0;
998 }
999
zend_fiber_shutdown(void)1000 void zend_fiber_shutdown(void)
1001 {
1002 #if defined(__SANITIZE_ADDRESS__) || defined(ZEND_FIBER_UCONTEXT)
1003 efree(EG(main_fiber_context)->stack);
1004 #endif
1005
1006 efree(EG(main_fiber_context));
1007
1008 zend_fiber_switch_block();
1009 }
1010