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