/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 | 3.68k | { |
125 | 3.68k | state->vm_stack = EG(vm_stack); |
126 | 3.68k | state->vm_stack_top = EG(vm_stack_top); |
127 | 3.68k | state->vm_stack_end = EG(vm_stack_end); |
128 | 3.68k | state->vm_stack_page_size = EG(vm_stack_page_size); |
129 | 3.68k | state->current_execute_data = EG(current_execute_data); |
130 | 3.68k | state->error_reporting = EG(error_reporting); |
131 | 3.68k | state->jit_trace_num = EG(jit_trace_num); |
132 | 3.68k | state->bailout = EG(bailout); |
133 | 3.68k | state->active_fiber = EG(active_fiber); |
134 | 3.68k | #ifdef ZEND_CHECK_STACK_LIMIT |
135 | 3.68k | state->stack_base = EG(stack_base); |
136 | 3.68k | state->stack_limit = EG(stack_limit); |
137 | 3.68k | #endif |
138 | 3.68k | } |
139 | | |
140 | | static zend_always_inline void zend_fiber_restore_vm_state(zend_fiber_vm_state *state) |
141 | 2.66k | { |
142 | 2.66k | EG(vm_stack) = state->vm_stack; |
143 | 2.66k | EG(vm_stack_top) = state->vm_stack_top; |
144 | 2.66k | EG(vm_stack_end) = state->vm_stack_end; |
145 | 2.66k | EG(vm_stack_page_size) = state->vm_stack_page_size; |
146 | 2.66k | EG(current_execute_data) = state->current_execute_data; |
147 | 2.66k | EG(error_reporting) = state->error_reporting; |
148 | 2.66k | EG(jit_trace_num) = state->jit_trace_num; |
149 | 2.66k | EG(bailout) = state->bailout; |
150 | 2.66k | EG(active_fiber) = state->active_fiber; |
151 | 2.66k | #ifdef ZEND_CHECK_STACK_LIMIT |
152 | 2.66k | EG(stack_base) = state->stack_base; |
153 | 2.66k | EG(stack_limit) = state->stack_limit; |
154 | 2.66k | #endif |
155 | 2.66k | } |
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 | 2.04k | { |
193 | 2.04k | static size_t page_size = 0; |
194 | | |
195 | 2.04k | if (!page_size) { |
196 | 3 | page_size = zend_get_page_size(); |
197 | 3 | 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 | 3 | } |
202 | | |
203 | 2.04k | return page_size; |
204 | 2.04k | } |
205 | | |
206 | | static zend_fiber_stack *zend_fiber_stack_allocate(size_t size) |
207 | 1.03k | { |
208 | 1.03k | void *pointer; |
209 | 1.03k | const size_t page_size = zend_fiber_get_page_size(); |
210 | 1.03k | 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 | 1.03k | ; |
216 | | |
217 | 1.03k | if (size < minimum_stack_size) { |
218 | 8 | zend_throw_exception_ex(NULL, 0, "Fiber stack size is too small, it needs to be at least %zu bytes", minimum_stack_size); |
219 | 8 | return NULL; |
220 | 8 | } |
221 | | |
222 | 1.02k | const size_t stack_size = (size + page_size - 1) / page_size * page_size; |
223 | 1.02k | 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 | 1.02k | pointer = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); |
250 | | |
251 | 1.02k | 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 | 1.02k | #if defined(MADV_NOHUGEPAGE) |
257 | | /* Multiple reasons to fail, ignore all errors only needed |
258 | | * for linux < 6.8 */ |
259 | 1.02k | (void) madvise(pointer, alloc_size, MADV_NOHUGEPAGE); |
260 | 1.02k | #endif |
261 | | |
262 | 1.02k | zend_mmap_set_name(pointer, alloc_size, "zend_fiber_stack"); |
263 | | |
264 | 1.02k | # if ZEND_FIBER_GUARD_PAGES |
265 | 1.02k | 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 | 1.02k | # endif |
271 | 1.02k | #endif |
272 | | |
273 | 1.02k | zend_fiber_stack *stack = emalloc(sizeof(zend_fiber_stack)); |
274 | | |
275 | 1.02k | stack->pointer = (void *) ((uintptr_t) pointer + ZEND_FIBER_GUARD_PAGES * page_size); |
276 | 1.02k | 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 | 1.02k | return stack; |
306 | 1.02k | } |
307 | | |
308 | | static void zend_fiber_stack_free(zend_fiber_stack *stack) |
309 | 1.01k | { |
310 | | #ifdef VALGRIND_STACK_DEREGISTER |
311 | | VALGRIND_STACK_DEREGISTER(stack->valgrind_stack_id); |
312 | | #endif |
313 | | |
314 | 1.01k | const size_t page_size = zend_fiber_get_page_size(); |
315 | | |
316 | 1.01k | 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 | 1.01k | munmap(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size); |
328 | 1.01k | #endif |
329 | | |
330 | | #if !defined(ZEND_FIBER_UCONTEXT) && BOOST_CONTEXT_SHADOW_STACK |
331 | | munmap(stack->ss_base, stack->ss_size); |
332 | | #endif |
333 | | |
334 | 1.01k | efree(stack); |
335 | 1.01k | } |
336 | | |
337 | | #ifdef ZEND_CHECK_STACK_LIMIT |
338 | | ZEND_API void* zend_fiber_stack_limit(zend_fiber_stack *stack) |
339 | 1.02k | { |
340 | 1.02k | 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 | 1.02k | return (int8_t*)stack->pointer + reserve; |
353 | 1.02k | } |
354 | | |
355 | | ZEND_API void* zend_fiber_stack_base(zend_fiber_stack *stack) |
356 | 1.02k | { |
357 | 1.02k | return (void*)((uintptr_t)stack->pointer + stack->size); |
358 | 1.02k | } |
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 | 1.02k | { |
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 | 1.02k | zend_fiber_transfer transfer = *data.transfer; |
372 | 1.02k | #endif |
373 | | |
374 | 1.02k | 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 | 1.02k | #ifndef ZEND_FIBER_UCONTEXT |
381 | | /* Get the context that resumed us and update its handle to allow for symmetric coroutines. */ |
382 | 1.02k | from->handle = data.handle; |
383 | 1.02k | #endif |
384 | | |
385 | | /* Ensure that previous fiber will be cleaned up (needed by symmetric coroutines). */ |
386 | 1.02k | if (from->status == ZEND_FIBER_STATUS_DEAD) { |
387 | 0 | zend_fiber_destroy_context(from); |
388 | 0 | } |
389 | | |
390 | 1.02k | zend_fiber_context *context = EG(current_fiber_context); |
391 | | |
392 | 1.02k | context->function(&transfer); |
393 | 1.02k | context->status = ZEND_FIBER_STATUS_DEAD; |
394 | | |
395 | | /* Final context switch, the fiber must not be resumed afterwards! */ |
396 | 1.02k | zend_fiber_switch_context(&transfer); |
397 | | |
398 | | /* Abort here because we are in an inconsistent program state. */ |
399 | 1.02k | abort(); |
400 | 1.02k | } |
401 | | |
402 | | ZEND_API void zend_fiber_switch_block(void) |
403 | 248k | { |
404 | 248k | ++zend_fiber_switch_blocking; |
405 | 248k | } |
406 | | |
407 | | ZEND_API void zend_fiber_switch_unblock(void) |
408 | 227 | { |
409 | 227 | ZEND_ASSERT(zend_fiber_switch_blocking && "Fiber switching was not blocked"); |
410 | 227 | --zend_fiber_switch_blocking; |
411 | 227 | } |
412 | | |
413 | | ZEND_API bool zend_fiber_switch_blocked(void) |
414 | 1.90k | { |
415 | 1.90k | return zend_fiber_switch_blocking; |
416 | 1.90k | } |
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 | 1.03k | { |
420 | 1.03k | context->stack = zend_fiber_stack_allocate(stack_size); |
421 | | |
422 | 1.03k | if (UNEXPECTED(!context->stack)) { |
423 | 8 | return FAILURE; |
424 | 8 | } |
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 | 1.02k | 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 | 1.02k | context->handle = make_fcontext(stack, context->stack->size, zend_fiber_trampoline); |
451 | 1.02k | ZEND_ASSERT(context->handle != NULL && "make_fcontext() never returns NULL"); |
452 | 1.02k | #endif |
453 | | |
454 | 1.02k | context->kind = kind; |
455 | 1.02k | context->function = coroutine; |
456 | | |
457 | | // Set status in case memory has not been zeroed. |
458 | 1.02k | context->status = ZEND_FIBER_STATUS_INIT; |
459 | | |
460 | 1.02k | zend_observer_fiber_init_notify(context); |
461 | | |
462 | 1.02k | return SUCCESS; |
463 | 1.02k | } |
464 | | |
465 | | ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context) |
466 | 1.01k | { |
467 | 1.01k | zend_observer_fiber_destroy_notify(context); |
468 | | |
469 | 1.01k | if (context->cleanup) { |
470 | 1.01k | context->cleanup(context); |
471 | 1.01k | } |
472 | | |
473 | 1.01k | zend_fiber_stack_free(context->stack); |
474 | 1.01k | } |
475 | | |
476 | | ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer) |
477 | 3.68k | { |
478 | 3.68k | zend_fiber_context *from = EG(current_fiber_context); |
479 | 3.68k | zend_fiber_context *to = transfer->context; |
480 | 3.68k | zend_fiber_vm_state state; |
481 | | |
482 | 3.68k | ZEND_ASSERT(to && to->handle && to->status != ZEND_FIBER_STATUS_DEAD && "Invalid fiber context"); |
483 | 3.68k | ZEND_ASSERT(from && "From fiber context must be present"); |
484 | 3.68k | ZEND_ASSERT(to != from && "Cannot switch into the running fiber context"); |
485 | | |
486 | | /* Assert that all error transfers hold a Throwable value. */ |
487 | 3.68k | ZEND_ASSERT(( |
488 | 3.68k | !(transfer->flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) || |
489 | 3.68k | (Z_TYPE(transfer->value) == IS_OBJECT && ( |
490 | 3.68k | zend_is_unwind_exit(Z_OBJ(transfer->value)) || |
491 | 3.68k | zend_is_graceful_exit(Z_OBJ(transfer->value)) || |
492 | 3.68k | instanceof_function(Z_OBJCE(transfer->value), zend_ce_throwable) |
493 | 3.68k | )) |
494 | 3.68k | ) && "Error transfer requires a throwable value"); |
495 | | |
496 | 3.68k | zend_observer_fiber_switch_notify(from, to); |
497 | | |
498 | 3.68k | zend_fiber_capture_vm_state(&state); |
499 | | |
500 | 3.68k | to->status = ZEND_FIBER_STATUS_RUNNING; |
501 | | |
502 | 3.68k | if (EXPECTED(from->status == ZEND_FIBER_STATUS_RUNNING)) { |
503 | 2.67k | from->status = ZEND_FIBER_STATUS_SUSPENDED; |
504 | 2.67k | } |
505 | | |
506 | | /* Update transfer context with the current fiber before switching. */ |
507 | 3.68k | transfer->context = from; |
508 | | |
509 | 3.68k | 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 | 3.68k | 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 | 3.68k | *transfer = *data.transfer; |
531 | 3.68k | #endif |
532 | | |
533 | 3.68k | to = transfer->context; |
534 | | |
535 | 3.68k | #ifndef ZEND_FIBER_UCONTEXT |
536 | | /* Get the context that resumed us and update its handle to allow for symmetric coroutines. */ |
537 | 3.68k | to->handle = data.handle; |
538 | 3.68k | #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 | 3.68k | EG(current_fiber_context) = from; |
545 | | |
546 | 3.68k | zend_fiber_restore_vm_state(&state); |
547 | | |
548 | | /* Destroy prior context if it has been marked as dead. */ |
549 | 3.68k | if (to->status == ZEND_FIBER_STATUS_DEAD) { |
550 | 1.01k | zend_fiber_destroy_context(to); |
551 | 1.01k | } |
552 | 3.68k | } |
553 | | |
554 | | static void zend_fiber_cleanup(zend_fiber_context *context) |
555 | 1.01k | { |
556 | 1.01k | zend_fiber *fiber = zend_fiber_from_context(context); |
557 | | |
558 | 1.01k | zend_vm_stack current_stack = EG(vm_stack); |
559 | 1.01k | EG(vm_stack) = fiber->vm_stack; |
560 | 1.01k | zend_vm_stack_destroy(); |
561 | 1.01k | EG(vm_stack) = current_stack; |
562 | 1.01k | fiber->execute_data = NULL; |
563 | 1.01k | fiber->stack_bottom = NULL; |
564 | 1.01k | fiber->caller = NULL; |
565 | 1.01k | } |
566 | | |
567 | | static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) |
568 | 1.02k | { |
569 | 1.02k | ZEND_ASSERT(Z_TYPE(transfer->value) == IS_NULL && "Initial transfer value to fiber context must be NULL"); |
570 | 1.02k | ZEND_ASSERT(!transfer->flags && "No flags should be set on initial transfer"); |
571 | | |
572 | 1.02k | zend_fiber *fiber = EG(active_fiber); |
573 | | |
574 | | /* Determine the current error_reporting ini setting. */ |
575 | 1.02k | 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 | 1.02k | if (!error_reporting && !INI_STR("error_reporting")) { |
578 | 0 | error_reporting = E_ALL; |
579 | 0 | } |
580 | | |
581 | 1.02k | EG(vm_stack) = NULL; |
582 | | |
583 | 1.02k | zend_first_try { |
584 | 1.02k | zend_vm_stack stack = zend_vm_stack_new_page(ZEND_FIBER_VM_STACK_SIZE, NULL); |
585 | 1.02k | EG(vm_stack) = stack; |
586 | 1.02k | EG(vm_stack_top) = stack->top + ZEND_CALL_FRAME_SLOT; |
587 | 1.02k | EG(vm_stack_end) = stack->end; |
588 | 1.02k | EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE; |
589 | | |
590 | 1.02k | fiber->execute_data = (zend_execute_data *) stack->top; |
591 | 1.02k | fiber->stack_bottom = fiber->execute_data; |
592 | | |
593 | 1.02k | memset(fiber->execute_data, 0, sizeof(zend_execute_data)); |
594 | | |
595 | 1.02k | fiber->execute_data->func = &zend_fiber_function; |
596 | 1.02k | fiber->stack_bottom->prev_execute_data = EG(current_execute_data); |
597 | | |
598 | 1.02k | EG(current_execute_data) = fiber->execute_data; |
599 | 1.02k | EG(jit_trace_num) = 0; |
600 | 1.02k | EG(error_reporting) = error_reporting; |
601 | | |
602 | 1.02k | #ifdef ZEND_CHECK_STACK_LIMIT |
603 | 1.02k | EG(stack_base) = zend_fiber_stack_base(fiber->context.stack); |
604 | 1.02k | EG(stack_limit) = zend_fiber_stack_limit(fiber->context.stack); |
605 | 1.02k | #endif |
606 | | |
607 | 1.02k | fiber->fci.retval = &fiber->result; |
608 | | |
609 | 1.02k | zend_call_function(&fiber->fci, &fiber->fci_cache); |
610 | | |
611 | | /* Cleanup callback and unset field to prevent GC / duplicate dtor issues. */ |
612 | 1.02k | zval_ptr_dtor(&fiber->fci.function_name); |
613 | 1.02k | ZVAL_UNDEF(&fiber->fci.function_name); |
614 | | |
615 | 1.02k | if (EG(exception)) { |
616 | 754 | if (!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED) |
617 | 625 | || !(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) |
618 | 754 | ) { |
619 | 171 | fiber->flags |= ZEND_FIBER_FLAG_THREW; |
620 | 171 | transfer->flags = ZEND_FIBER_TRANSFER_FLAG_ERROR; |
621 | | |
622 | 171 | ZVAL_OBJ_COPY(&transfer->value, EG(exception)); |
623 | 171 | } |
624 | | |
625 | 754 | zend_clear_exception(); |
626 | 754 | } |
627 | 1.02k | } 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 | 1.02k | fiber->context.cleanup = &zend_fiber_cleanup; |
633 | 1.02k | fiber->vm_stack = EG(vm_stack); |
634 | | |
635 | 1.02k | transfer->context = fiber->caller; |
636 | 1.02k | } |
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 | 2.01k | ) { |
642 | 2.01k | if (transfer->flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) { |
643 | | /* Use internal throw to skip the Throwable-check that would fail for (graceful) exit. */ |
644 | 770 | zend_throw_exception_internal(Z_OBJ(transfer->value)); |
645 | 770 | RETURN_THROWS(); |
646 | 770 | } |
647 | | |
648 | 1.24k | if (return_value != NULL) { |
649 | 1.11k | RETURN_COPY_VALUE(&transfer->value); |
650 | 1.11k | } else { |
651 | 128 | zval_ptr_dtor(&transfer->value); |
652 | 128 | } |
653 | 1.24k | } |
654 | | |
655 | | static zend_always_inline zend_fiber_transfer zend_fiber_switch_to( |
656 | | zend_fiber_context *context, zval *value, bool exception |
657 | 2.67k | ) { |
658 | 2.67k | zend_fiber_transfer transfer = { |
659 | 2.67k | .context = context, |
660 | 2.67k | .flags = exception ? ZEND_FIBER_TRANSFER_FLAG_ERROR : 0, |
661 | 2.67k | }; |
662 | | |
663 | 2.67k | if (value) { |
664 | 842 | ZVAL_COPY(&transfer.value, value); |
665 | 1.82k | } else { |
666 | 1.82k | ZVAL_NULL(&transfer.value); |
667 | 1.82k | } |
668 | | |
669 | 2.67k | zend_fiber_switch_context(&transfer); |
670 | | |
671 | | /* Forward bailout into current fiber. */ |
672 | 2.67k | if (UNEXPECTED(transfer.flags & ZEND_FIBER_TRANSFER_FLAG_BAILOUT)) { |
673 | 29 | EG(active_fiber) = NULL; |
674 | 29 | zend_bailout(); |
675 | 29 | } |
676 | | |
677 | 2.64k | return transfer; |
678 | 2.67k | } |
679 | | |
680 | | static zend_always_inline zend_fiber_transfer zend_fiber_resume_internal(zend_fiber *fiber, zval *value, bool exception) |
681 | 1.84k | { |
682 | 1.84k | zend_fiber *previous = EG(active_fiber); |
683 | | |
684 | 1.84k | if (previous) { |
685 | 217 | previous->execute_data = EG(current_execute_data); |
686 | 217 | } |
687 | | |
688 | 1.84k | fiber->caller = EG(current_fiber_context); |
689 | 1.84k | EG(active_fiber) = fiber; |
690 | | |
691 | 1.84k | zend_fiber_transfer transfer = zend_fiber_switch_to(fiber->previous, value, exception); |
692 | | |
693 | 1.84k | EG(active_fiber) = previous; |
694 | | |
695 | 1.84k | return transfer; |
696 | 1.84k | } |
697 | | |
698 | | static zend_always_inline zend_fiber_transfer zend_fiber_suspend_internal(zend_fiber *fiber, zval *value) |
699 | 826 | { |
700 | 826 | ZEND_ASSERT(!(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)); |
701 | 826 | ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_RUNNING || fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED); |
702 | 826 | ZEND_ASSERT(fiber->caller != NULL); |
703 | | |
704 | 826 | zend_fiber_context *caller = fiber->caller; |
705 | 826 | fiber->previous = EG(current_fiber_context); |
706 | 826 | fiber->caller = NULL; |
707 | 826 | fiber->execute_data = EG(current_execute_data); |
708 | | |
709 | 826 | return zend_fiber_switch_to(caller, value, false); |
710 | 826 | } |
711 | | |
712 | | ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value) |
713 | 116 | { |
714 | 116 | ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_INIT); |
715 | | |
716 | 116 | 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 | 116 | fiber->previous = &fiber->context; |
721 | | |
722 | 116 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, NULL, false); |
723 | | |
724 | 116 | zend_fiber_delegate_transfer_result(&transfer, EG(current_execute_data), return_value); |
725 | | |
726 | 116 | return SUCCESS; |
727 | 116 | } |
728 | | |
729 | | ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value) |
730 | 6 | { |
731 | 6 | ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); |
732 | | |
733 | 6 | fiber->stack_bottom->prev_execute_data = EG(current_execute_data); |
734 | | |
735 | 6 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, value, /* exception */ false); |
736 | | |
737 | 6 | zend_fiber_delegate_transfer_result(&transfer, EG(current_execute_data), return_value); |
738 | 6 | } |
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 | 72 | { |
753 | 72 | fiber->stack_bottom->prev_execute_data = NULL; |
754 | | |
755 | 72 | zend_fiber_transfer transfer = zend_fiber_suspend_internal(fiber, value); |
756 | | |
757 | 72 | zend_fiber_delegate_transfer_result(&transfer, EG(current_execute_data), return_value); |
758 | 72 | } |
759 | | |
760 | | static zend_object *zend_fiber_object_create(zend_class_entry *ce) |
761 | 1.11k | { |
762 | 1.11k | zend_fiber *fiber = emalloc(sizeof(zend_fiber)); |
763 | 1.11k | memset(fiber, 0, sizeof(zend_fiber)); |
764 | | |
765 | 1.11k | zend_object_std_init(&fiber->std, ce); |
766 | 1.11k | return &fiber->std; |
767 | 1.11k | } |
768 | | |
769 | | static void zend_fiber_object_destroy(zend_object *object) |
770 | 1.07k | { |
771 | 1.07k | zend_fiber *fiber = (zend_fiber *) object; |
772 | | |
773 | 1.07k | if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED) { |
774 | 446 | return; |
775 | 446 | } |
776 | | |
777 | 625 | zend_object *exception = EG(exception); |
778 | 625 | EG(exception) = NULL; |
779 | | |
780 | 625 | zval graceful_exit; |
781 | 625 | ZVAL_OBJ(&graceful_exit, zend_create_graceful_exit()); |
782 | | |
783 | 625 | fiber->flags |= ZEND_FIBER_FLAG_DESTROYED; |
784 | | |
785 | 625 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, &graceful_exit, true); |
786 | | |
787 | 625 | zval_ptr_dtor(&graceful_exit); |
788 | | |
789 | 625 | if (transfer.flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) { |
790 | 42 | EG(exception) = Z_OBJ(transfer.value); |
791 | | |
792 | 42 | if (!exception && EG(current_execute_data) && EG(current_execute_data)->func |
793 | 16 | && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { |
794 | 9 | zend_rethrow_exception(EG(current_execute_data)); |
795 | 9 | } |
796 | | |
797 | 42 | zend_exception_set_previous(EG(exception), exception); |
798 | | |
799 | 42 | if (!EG(current_execute_data)) { |
800 | 16 | zend_exception_error(EG(exception), E_ERROR); |
801 | 16 | } |
802 | 583 | } else { |
803 | 583 | zval_ptr_dtor(&transfer.value); |
804 | 583 | EG(exception) = exception; |
805 | 583 | } |
806 | 625 | } |
807 | | |
808 | | static void zend_fiber_object_free(zend_object *object) |
809 | 1.11k | { |
810 | 1.11k | zend_fiber *fiber = (zend_fiber *) object; |
811 | | |
812 | 1.11k | zval_ptr_dtor(&fiber->fci.function_name); |
813 | 1.11k | zval_ptr_dtor(&fiber->result); |
814 | | |
815 | 1.11k | zend_object_std_dtor(&fiber->std); |
816 | 1.11k | } |
817 | | |
818 | | static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *num) |
819 | 2.93k | { |
820 | 2.93k | zend_fiber *fiber = (zend_fiber *) object; |
821 | 2.93k | zend_get_gc_buffer *buf = zend_get_gc_buffer_create(); |
822 | | |
823 | 2.93k | zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name); |
824 | 2.93k | zend_get_gc_buffer_add_zval(buf, &fiber->result); |
825 | | |
826 | 2.93k | if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL) { |
827 | 1.50k | zend_get_gc_buffer_use(buf, table, num); |
828 | 1.50k | return NULL; |
829 | 1.50k | } |
830 | | |
831 | 1.43k | HashTable *lastSymTable = NULL; |
832 | 1.43k | zend_execute_data *ex = fiber->execute_data; |
833 | 7.29k | for (; ex; ex = ex->prev_execute_data) { |
834 | 5.85k | HashTable *symTable; |
835 | 5.85k | if (ZEND_CALL_INFO(ex) & ZEND_CALL_GENERATOR) { |
836 | | /* The generator object is stored in ex->return_value */ |
837 | 378 | 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 | 378 | if (!(generator->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { |
847 | 0 | continue; |
848 | 0 | } |
849 | 378 | symTable = zend_generator_frame_gc(buf, generator); |
850 | 5.47k | } else { |
851 | 5.47k | symTable = zend_unfinished_execution_gc_ex(ex, ex->func && ZEND_USER_CODE(ex->func->type) ? ex->call : NULL, buf, false); |
852 | 5.47k | } |
853 | 5.85k | if (symTable) { |
854 | 254 | if (lastSymTable) { |
855 | 106 | zval *val; |
856 | 390 | ZEND_HASH_FOREACH_VAL(lastSymTable, val) { |
857 | 390 | if (EXPECTED(Z_TYPE_P(val) == IS_INDIRECT)) { |
858 | 96 | val = Z_INDIRECT_P(val); |
859 | 96 | } |
860 | 390 | zend_get_gc_buffer_add_zval(buf, val); |
861 | 390 | } ZEND_HASH_FOREACH_END(); |
862 | 106 | } |
863 | 254 | lastSymTable = symTable; |
864 | 254 | } |
865 | 5.85k | } |
866 | | |
867 | 1.43k | zend_get_gc_buffer_use(buf, table, num); |
868 | | |
869 | 1.43k | return lastSymTable; |
870 | 2.93k | } |
871 | | |
872 | | ZEND_METHOD(Fiber, __construct) |
873 | 1.00k | { |
874 | 1.00k | zend_fcall_info fci; |
875 | 1.00k | zend_fcall_info_cache fcc; |
876 | | |
877 | 3.02k | ZEND_PARSE_PARAMETERS_START(1, 1) |
878 | 4.01k | Z_PARAM_FUNC(fci, fcc) |
879 | 1.00k | ZEND_PARSE_PARAMETERS_END(); |
880 | | |
881 | 1.00k | zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
882 | | |
883 | 1.00k | if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_INIT || Z_TYPE(fiber->fci.function_name) != IS_UNDEF)) { |
884 | 12 | zend_throw_error(zend_ce_fiber_error, "Cannot call constructor twice"); |
885 | 12 | RETURN_THROWS(); |
886 | 12 | } |
887 | | |
888 | 992 | fiber->fci = fci; |
889 | 992 | fiber->fci_cache = fcc; |
890 | | |
891 | | // Keep a reference to closures or callable objects while the fiber is running. |
892 | 992 | Z_TRY_ADDREF(fiber->fci.function_name); |
893 | 992 | } |
894 | | |
895 | | ZEND_METHOD(Fiber, start) |
896 | 921 | { |
897 | 921 | zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
898 | | |
899 | 2.76k | ZEND_PARSE_PARAMETERS_START(0, -1) |
900 | 2.76k | Z_PARAM_VARIADIC_WITH_NAMED(fiber->fci.params, fiber->fci.param_count, fiber->fci.named_params); |
901 | 2.76k | ZEND_PARSE_PARAMETERS_END(); |
902 | | |
903 | 921 | 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 | 921 | if (fiber->context.status != ZEND_FIBER_STATUS_INIT) { |
909 | 6 | zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started"); |
910 | 6 | RETURN_THROWS(); |
911 | 6 | } |
912 | | |
913 | 915 | if (zend_fiber_init_context(&fiber->context, zend_ce_fiber, zend_fiber_execute, EG(fiber_stack_size)) == FAILURE) { |
914 | 8 | RETURN_THROWS(); |
915 | 8 | } |
916 | | |
917 | 907 | fiber->previous = &fiber->context; |
918 | | |
919 | 907 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, NULL, false); |
920 | | |
921 | 907 | zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); |
922 | 907 | } |
923 | | |
924 | | ZEND_METHOD(Fiber, suspend) |
925 | 792 | { |
926 | 792 | zval *value = NULL; |
927 | | |
928 | 2.37k | ZEND_PARSE_PARAMETERS_START(0, 1) |
929 | 2.37k | Z_PARAM_OPTIONAL |
930 | 2.37k | Z_PARAM_ZVAL(value); |
931 | 1.93k | ZEND_PARSE_PARAMETERS_END(); |
932 | | |
933 | 792 | zend_fiber *fiber = EG(active_fiber); |
934 | | |
935 | 792 | if (UNEXPECTED(!fiber)) { |
936 | 22 | zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber"); |
937 | 22 | RETURN_THROWS(); |
938 | 22 | } |
939 | | |
940 | 770 | if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) { |
941 | 11 | zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber"); |
942 | 11 | RETURN_THROWS(); |
943 | 11 | } |
944 | | |
945 | 759 | if (UNEXPECTED(zend_fiber_switch_blocked())) { |
946 | 5 | zend_throw_error(zend_ce_fiber_error, "Cannot switch fibers in current execution context"); |
947 | 5 | RETURN_THROWS(); |
948 | 5 | } |
949 | | |
950 | 754 | ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_RUNNING || fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED); |
951 | | |
952 | 754 | fiber->stack_bottom->prev_execute_data = NULL; |
953 | | |
954 | 754 | zend_fiber_transfer transfer = zend_fiber_suspend_internal(fiber, value); |
955 | | |
956 | 754 | zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); |
957 | 754 | } |
958 | | |
959 | | ZEND_METHOD(Fiber, resume) |
960 | 197 | { |
961 | 197 | zend_fiber *fiber; |
962 | 197 | zval *value = NULL; |
963 | | |
964 | 591 | ZEND_PARSE_PARAMETERS_START(0, 1) |
965 | 591 | Z_PARAM_OPTIONAL |
966 | 591 | Z_PARAM_ZVAL(value); |
967 | 476 | ZEND_PARSE_PARAMETERS_END(); |
968 | | |
969 | 197 | 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 | 197 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
975 | | |
976 | 197 | if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) { |
977 | 23 | zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); |
978 | 23 | RETURN_THROWS(); |
979 | 23 | } |
980 | | |
981 | 174 | fiber->stack_bottom->prev_execute_data = EG(current_execute_data); |
982 | | |
983 | 174 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, value, false); |
984 | | |
985 | 174 | zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); |
986 | 174 | } |
987 | | |
988 | | ZEND_METHOD(Fiber, throw) |
989 | 24 | { |
990 | 24 | zend_fiber *fiber; |
991 | 24 | zval *exception; |
992 | | |
993 | 72 | ZEND_PARSE_PARAMETERS_START(1, 1) |
994 | 96 | Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable) |
995 | 24 | ZEND_PARSE_PARAMETERS_END(); |
996 | | |
997 | 24 | 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 | 24 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1003 | | |
1004 | 24 | if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) { |
1005 | 8 | zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); |
1006 | 8 | RETURN_THROWS(); |
1007 | 8 | } |
1008 | | |
1009 | 16 | fiber->stack_bottom->prev_execute_data = EG(current_execute_data); |
1010 | | |
1011 | 16 | zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, exception, true); |
1012 | | |
1013 | 16 | zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); |
1014 | 16 | } |
1015 | | |
1016 | | ZEND_METHOD(Fiber, isStarted) |
1017 | 43 | { |
1018 | 43 | zend_fiber *fiber; |
1019 | | |
1020 | 43 | ZEND_PARSE_PARAMETERS_NONE(); |
1021 | | |
1022 | 43 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1023 | | |
1024 | 43 | RETURN_BOOL(fiber->context.status != ZEND_FIBER_STATUS_INIT); |
1025 | 43 | } |
1026 | | |
1027 | | ZEND_METHOD(Fiber, isSuspended) |
1028 | 37 | { |
1029 | 37 | zend_fiber *fiber; |
1030 | | |
1031 | 37 | ZEND_PARSE_PARAMETERS_NONE(); |
1032 | | |
1033 | 37 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1034 | | |
1035 | 37 | RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); |
1036 | 37 | } |
1037 | | |
1038 | | ZEND_METHOD(Fiber, isRunning) |
1039 | 37 | { |
1040 | 37 | zend_fiber *fiber; |
1041 | | |
1042 | 37 | ZEND_PARSE_PARAMETERS_NONE(); |
1043 | | |
1044 | 37 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1045 | | |
1046 | 37 | RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_RUNNING || fiber->caller != NULL); |
1047 | 37 | } |
1048 | | |
1049 | | ZEND_METHOD(Fiber, isTerminated) |
1050 | 46 | { |
1051 | 46 | zend_fiber *fiber; |
1052 | | |
1053 | 46 | ZEND_PARSE_PARAMETERS_NONE(); |
1054 | | |
1055 | 46 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1056 | | |
1057 | 46 | RETURN_BOOL(fiber->context.status == ZEND_FIBER_STATUS_DEAD); |
1058 | 46 | } |
1059 | | |
1060 | | ZEND_METHOD(Fiber, getReturn) |
1061 | 60 | { |
1062 | 60 | zend_fiber *fiber; |
1063 | 60 | const char *message; |
1064 | | |
1065 | 60 | ZEND_PARSE_PARAMETERS_NONE(); |
1066 | | |
1067 | 58 | fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); |
1068 | | |
1069 | 58 | if (fiber->context.status == ZEND_FIBER_STATUS_DEAD) { |
1070 | 44 | if (fiber->flags & ZEND_FIBER_FLAG_THREW) { |
1071 | 6 | message = "The fiber threw an exception"; |
1072 | 38 | } else if (fiber->flags & ZEND_FIBER_FLAG_BAILOUT) { |
1073 | 0 | message = "The fiber exited with a fatal error"; |
1074 | 38 | } else { |
1075 | 38 | RETURN_COPY_DEREF(&fiber->result); |
1076 | 38 | } |
1077 | 44 | } else if (fiber->context.status == ZEND_FIBER_STATUS_INIT) { |
1078 | 8 | message = "The fiber has not been started"; |
1079 | 8 | } else { |
1080 | 6 | message = "The fiber has not returned"; |
1081 | 6 | } |
1082 | | |
1083 | 20 | zend_throw_error(zend_ce_fiber_error, "Cannot get fiber return value: %s", message); |
1084 | 20 | RETURN_THROWS(); |
1085 | 20 | } |
1086 | | |
1087 | | ZEND_METHOD(Fiber, getCurrent) |
1088 | 492 | { |
1089 | 492 | ZEND_PARSE_PARAMETERS_NONE(); |
1090 | | |
1091 | 492 | zend_fiber *fiber = EG(active_fiber); |
1092 | | |
1093 | 492 | if (!fiber) { |
1094 | 340 | RETURN_NULL(); |
1095 | 340 | } |
1096 | | |
1097 | 152 | RETURN_OBJ_COPY(&fiber->std); |
1098 | 152 | } |
1099 | | |
1100 | | ZEND_METHOD(FiberError, __construct) |
1101 | 10 | { |
1102 | 10 | zend_throw_error( |
1103 | 10 | NULL, |
1104 | 10 | "The \"%s\" class is reserved for internal use and cannot be manually instantiated", |
1105 | 10 | ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name) |
1106 | 10 | ); |
1107 | 10 | } |
1108 | | |
1109 | | |
1110 | | void zend_register_fiber_ce(void) |
1111 | 16 | { |
1112 | 16 | zend_ce_fiber = register_class_Fiber(); |
1113 | 16 | zend_ce_fiber->create_object = zend_fiber_object_create; |
1114 | 16 | zend_ce_fiber->default_object_handlers = &zend_fiber_handlers; |
1115 | | |
1116 | 16 | zend_fiber_handlers = std_object_handlers; |
1117 | 16 | zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy; |
1118 | 16 | zend_fiber_handlers.free_obj = zend_fiber_object_free; |
1119 | 16 | zend_fiber_handlers.get_gc = zend_fiber_object_gc; |
1120 | 16 | zend_fiber_handlers.clone_obj = NULL; |
1121 | | |
1122 | 16 | zend_ce_fiber_error = register_class_FiberError(zend_ce_error); |
1123 | 16 | zend_ce_fiber_error->create_object = zend_ce_error->create_object; |
1124 | 16 | } |
1125 | | |
1126 | | void zend_fiber_init(void) |
1127 | 247k | { |
1128 | 247k | 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 | 247k | context->status = ZEND_FIBER_STATUS_RUNNING; |
1140 | | |
1141 | 247k | EG(main_fiber_context) = context; |
1142 | 247k | EG(current_fiber_context) = context; |
1143 | 247k | EG(active_fiber) = NULL; |
1144 | | |
1145 | 247k | zend_fiber_switch_blocking = 0; |
1146 | 247k | } |
1147 | | |
1148 | | void zend_fiber_shutdown(void) |
1149 | 247k | { |
1150 | | #if defined(__SANITIZE_ADDRESS__) || defined(ZEND_FIBER_UCONTEXT) |
1151 | | efree(EG(main_fiber_context)->stack); |
1152 | | #endif |
1153 | | |
1154 | 247k | efree(EG(main_fiber_context)); |
1155 | | |
1156 | 247k | zend_fiber_switch_block(); |
1157 | 247k | } |