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