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