/src/php-src/Zend/zend_observer.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: Levi Morrison <levim@php.net> | |
15 | | | Sammy Kaye Powers <sammyk@php.net> | |
16 | | | Bob Weinand <bobwei9@hotmail.com> | |
17 | | +----------------------------------------------------------------------+ |
18 | | */ |
19 | | |
20 | | #include "zend_observer.h" |
21 | | |
22 | | #include "zend_extensions.h" |
23 | | #include "zend_llist.h" |
24 | | #include "zend_vm.h" |
25 | | |
26 | 0 | #define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2) |
27 | | |
28 | | static zend_llist zend_observers_fcall_list; |
29 | | static zend_llist zend_observer_function_declared_callbacks; |
30 | | static zend_llist zend_observer_class_linked_callbacks; |
31 | | static zend_llist zend_observer_error_callbacks; |
32 | | static zend_llist zend_observer_fiber_init; |
33 | | static zend_llist zend_observer_fiber_switch; |
34 | | static zend_llist zend_observer_fiber_destroy; |
35 | | |
36 | | int zend_observer_fcall_op_array_extension; |
37 | | int zend_observer_fcall_internal_function_extension; |
38 | | bool zend_observer_errors_observed; |
39 | | bool zend_observer_function_declared_observed; |
40 | | bool zend_observer_class_linked_observed; |
41 | | |
42 | | // Call during minit/startup ONLY |
43 | | ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) |
44 | 0 | { |
45 | 0 | zend_llist_add_element(&zend_observers_fcall_list, &init); |
46 | 0 | } |
47 | | |
48 | | // Called by engine before MINITs |
49 | | ZEND_API void zend_observer_startup(void) |
50 | 2 | { |
51 | 2 | zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1); |
52 | 2 | zend_llist_init(&zend_observer_function_declared_callbacks, sizeof(zend_observer_function_declared_cb), NULL, 1); |
53 | 2 | zend_llist_init(&zend_observer_class_linked_callbacks, sizeof(zend_observer_class_linked_cb), NULL, 1); |
54 | 2 | zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1); |
55 | 2 | zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1); |
56 | 2 | zend_llist_init(&zend_observer_fiber_switch, sizeof(zend_observer_fiber_switch_handler), NULL, 1); |
57 | 2 | zend_llist_init(&zend_observer_fiber_destroy, sizeof(zend_observer_fiber_destroy_handler), NULL, 1); |
58 | | |
59 | 2 | zend_observer_fcall_op_array_extension = -1; |
60 | 2 | zend_observer_fcall_internal_function_extension = -1; |
61 | 2 | } |
62 | | |
63 | | ZEND_API void zend_observer_post_startup(void) |
64 | 2 | { |
65 | 2 | if (zend_observers_fcall_list.count) { |
66 | | /* We don't want to get an extension handle unless an ext installs an observer |
67 | | * Allocate each a begin and an end pointer */ |
68 | 0 | zend_observer_fcall_op_array_extension = |
69 | 0 | zend_get_op_array_extension_handles("Zend Observer", (int) zend_observers_fcall_list.count * 2); |
70 | |
|
71 | 0 | zend_observer_fcall_internal_function_extension = |
72 | 0 | zend_get_internal_function_extension_handles("Zend Observer", (int) zend_observers_fcall_list.count * 2); |
73 | | |
74 | | /* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op() |
75 | | * is called before any extensions have registered as an observer. So we |
76 | | * adjust the offset to the observed handler when we know we need to observe. */ |
77 | 0 | ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op)); |
78 | | |
79 | | /* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions |
80 | | * exist when zend_init_exception_op() is called. */ |
81 | 0 | ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)); |
82 | 0 | ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 1); |
83 | 0 | ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 2); |
84 | | |
85 | | // Add an observer temporary to store previous observed frames |
86 | 0 | zend_internal_function *zif; |
87 | 0 | ZEND_HASH_FOREACH_PTR(CG(function_table), zif) { |
88 | 0 | ++zif->T; |
89 | 0 | } ZEND_HASH_FOREACH_END(); |
90 | 0 | zend_class_entry *ce; |
91 | 0 | ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { |
92 | 0 | ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) { |
93 | 0 | ++zif->T; |
94 | 0 | } ZEND_HASH_FOREACH_END(); |
95 | 0 | } ZEND_HASH_FOREACH_END(); |
96 | 0 | } |
97 | 2 | } |
98 | | |
99 | | ZEND_API void zend_observer_activate(void) |
100 | 38.2k | { |
101 | 38.2k | EG(current_observed_frame) = NULL; |
102 | 38.2k | } |
103 | | |
104 | | ZEND_API void zend_observer_shutdown(void) |
105 | 0 | { |
106 | 0 | zend_llist_destroy(&zend_observers_fcall_list); |
107 | 0 | zend_llist_destroy(&zend_observer_function_declared_callbacks); |
108 | 0 | zend_llist_destroy(&zend_observer_class_linked_callbacks); |
109 | 0 | zend_llist_destroy(&zend_observer_error_callbacks); |
110 | 0 | zend_llist_destroy(&zend_observer_fiber_init); |
111 | 0 | zend_llist_destroy(&zend_observer_fiber_switch); |
112 | 0 | zend_llist_destroy(&zend_observer_fiber_destroy); |
113 | 0 | } |
114 | | |
115 | | static void zend_observer_fcall_install(zend_execute_data *execute_data) |
116 | 0 | { |
117 | 0 | const zend_llist *list = &zend_observers_fcall_list; |
118 | 0 | const zend_function *function = execute_data->func; |
119 | |
|
120 | 0 | ZEND_ASSERT(RUN_TIME_CACHE(&function->common)); |
121 | 0 | zend_observer_fcall_begin_handler *begin_handlers = ZEND_OBSERVER_DATA(function), *begin_handlers_start = begin_handlers; |
122 | 0 | zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers; |
123 | |
|
124 | 0 | *begin_handlers = ZEND_OBSERVER_NOT_OBSERVED; |
125 | 0 | *end_handlers = ZEND_OBSERVER_NOT_OBSERVED; |
126 | 0 | bool has_handlers = false; |
127 | |
|
128 | 0 | for (const zend_llist_element *element = list->head; element; element = element->next) { |
129 | 0 | zend_observer_fcall_init init; |
130 | 0 | memcpy(&init, element->data, sizeof init); |
131 | 0 | zend_observer_fcall_handlers handlers = init(execute_data); |
132 | 0 | if (handlers.begin) { |
133 | 0 | *(begin_handlers++) = handlers.begin; |
134 | 0 | has_handlers = true; |
135 | 0 | } |
136 | 0 | if (handlers.end) { |
137 | 0 | *(end_handlers++) = handlers.end; |
138 | 0 | has_handlers = true; |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | | // end handlers are executed in reverse order |
143 | 0 | for (--end_handlers; end_handlers_start < end_handlers; --end_handlers, ++end_handlers_start) { |
144 | 0 | zend_observer_fcall_end_handler tmp = *end_handlers; |
145 | 0 | *end_handlers = *end_handlers_start; |
146 | 0 | *end_handlers_start = tmp; |
147 | 0 | } |
148 | |
|
149 | 0 | if (!has_handlers) { |
150 | 0 | *begin_handlers_start = ZEND_OBSERVER_NONE_OBSERVED; |
151 | 0 | } |
152 | 0 | } |
153 | | |
154 | | /* We need to provide the ability to retrieve the handler which will move onto the position the current handler was. |
155 | | * The fundamental problem is that, if a handler is removed while it's being executed, it will move handlers around: |
156 | | * the previous next handler is now at the place where the current handler was. |
157 | | * Hence, the next handler executed will be the one after the next handler. |
158 | | * Callees must thus invoke the next handler themselves, with the same arguments they were passed. */ |
159 | 0 | static bool zend_observer_remove_handler(void **first_handler, const void *old_handler, void **next_handler) { |
160 | 0 | size_t registered_observers = zend_observers_fcall_list.count; |
161 | |
|
162 | 0 | void **last_handler = first_handler + registered_observers - 1; |
163 | 0 | for (void **cur_handler = first_handler; cur_handler <= last_handler; ++cur_handler) { |
164 | 0 | if (*cur_handler == old_handler) { |
165 | 0 | if (registered_observers == 1 || (cur_handler == first_handler && cur_handler[1] == NULL)) { |
166 | 0 | *cur_handler = ZEND_OBSERVER_NOT_OBSERVED; |
167 | 0 | *next_handler = NULL; |
168 | 0 | } else { |
169 | 0 | if (cur_handler != last_handler) { |
170 | 0 | memmove(cur_handler, cur_handler + 1, sizeof(*cur_handler) * (last_handler - cur_handler)); |
171 | 0 | } |
172 | 0 | *last_handler = NULL; |
173 | 0 | *next_handler = *cur_handler; |
174 | 0 | } |
175 | 0 | return true; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | return false; |
179 | 0 | } |
180 | | |
181 | 0 | ZEND_API void zend_observer_add_begin_handler(const zend_function *function, zend_observer_fcall_begin_handler begin) { |
182 | 0 | size_t registered_observers = zend_observers_fcall_list.count; |
183 | 0 | zend_observer_fcall_begin_handler *first_handler = ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1; |
184 | 0 | if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED || *first_handler == ZEND_OBSERVER_NONE_OBSERVED) { |
185 | 0 | *first_handler = begin; |
186 | 0 | } else { |
187 | 0 | for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) { |
188 | 0 | if (*cur_handler == NULL) { |
189 | 0 | *cur_handler = begin; |
190 | 0 | return; |
191 | 0 | } |
192 | 0 | } |
193 | | // there's no space for new handlers, then it's forbidden to call this function |
194 | 0 | ZEND_UNREACHABLE(); |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | 0 | ZEND_API bool zend_observer_remove_begin_handler(const zend_function *function, zend_observer_fcall_begin_handler begin, zend_observer_fcall_begin_handler *next) { |
199 | 0 | void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function); |
200 | 0 | if (zend_observer_remove_handler(begin_handlers, begin, (void**)next)) { |
201 | | // Ensure invariant: ZEND_OBSERVER_NONE_OBSERVED in begin_handlers if both are not observed |
202 | 0 | if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED) { |
203 | 0 | size_t registered_observers = zend_observers_fcall_list.count; |
204 | 0 | if (begin_handlers[registered_observers] /* first end handler */ == ZEND_OBSERVER_NOT_OBSERVED) { |
205 | 0 | *begin_handlers = ZEND_OBSERVER_NONE_OBSERVED; |
206 | 0 | } |
207 | 0 | } |
208 | 0 | return true; |
209 | 0 | } |
210 | 0 | return false; |
211 | 0 | } |
212 | | |
213 | 0 | ZEND_API void zend_observer_add_end_handler(const zend_function *function, zend_observer_fcall_end_handler end) { |
214 | 0 | size_t registered_observers = zend_observers_fcall_list.count; |
215 | 0 | void **begin_handler = (void **)ZEND_OBSERVER_DATA(function); |
216 | 0 | zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)begin_handler + registered_observers; |
217 | | // to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front |
218 | 0 | if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) { |
219 | | // there's no space for new handlers, then it's forbidden to call this function |
220 | 0 | ZEND_ASSERT(end_handler[registered_observers - 1] == NULL); |
221 | 0 | memmove(end_handler + 1, end_handler, sizeof(*end_handler) * (registered_observers - 1)); |
222 | 0 | } else if (*begin_handler == ZEND_OBSERVER_NONE_OBSERVED) { |
223 | 0 | *begin_handler = ZEND_OBSERVER_NOT_OBSERVED; |
224 | 0 | } |
225 | 0 | *end_handler = end; |
226 | 0 | } |
227 | | |
228 | 0 | ZEND_API bool zend_observer_remove_end_handler(const zend_function *function, zend_observer_fcall_end_handler end, zend_observer_fcall_end_handler *next) { |
229 | 0 | size_t registered_observers = zend_observers_fcall_list.count; |
230 | 0 | void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function); |
231 | 0 | void **end_handlers = begin_handlers + registered_observers; |
232 | 0 | if (zend_observer_remove_handler(end_handlers, end, (void**)next)) { |
233 | | // Ensure invariant: ZEND_OBSERVER_NONE_OBSERVED in begin_handlers if both are not observed |
234 | 0 | if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED && *end_handlers == ZEND_OBSERVER_NOT_OBSERVED) { |
235 | 0 | *begin_handlers = ZEND_OBSERVER_NONE_OBSERVED; |
236 | 0 | } |
237 | 0 | return true; |
238 | 0 | } |
239 | 0 | return false; |
240 | 0 | } |
241 | | |
242 | 0 | static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) { |
243 | 0 | const zend_function *func = EX(func); |
244 | 0 | ZEND_ASSERT(func); |
245 | 0 | return (zend_execute_data **)&Z_PTR_P(EX_VAR_NUM((ZEND_USER_CODE(func->type) ? func->op_array.last_var : ZEND_CALL_NUM_ARGS(execute_data)) + func->common.T - 1)); |
246 | 0 | } |
247 | | |
248 | 0 | static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) { |
249 | 0 | if (!ZEND_OBSERVER_ENABLED) { |
250 | 0 | return; |
251 | 0 | } |
252 | | |
253 | 0 | zend_observer_fcall_begin_specialized(execute_data, true); |
254 | 0 | } |
255 | | |
256 | | ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *handler) |
257 | 0 | { |
258 | 0 | const zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count; |
259 | |
|
260 | 0 | if (!*handler) { |
261 | 0 | zend_observer_fcall_install(execute_data); |
262 | 0 | if (zend_observer_handler_is_unobserved(handler)) { |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | 0 | const zend_observer_fcall_end_handler *end_handler = (const zend_observer_fcall_end_handler *)possible_handlers_end; |
268 | 0 | if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) { |
269 | 0 | *prev_observed_frame(execute_data) = EG(current_observed_frame); |
270 | 0 | EG(current_observed_frame) = execute_data; |
271 | |
|
272 | 0 | if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { // this function must not be called if ZEND_OBSERVER_NONE_OBSERVED, hence sufficient to check |
273 | 0 | return; |
274 | 0 | } |
275 | 0 | } |
276 | | |
277 | 0 | do { |
278 | 0 | (*handler)(execute_data); |
279 | 0 | } while (++handler != possible_handlers_end && *handler != NULL); |
280 | 0 | } |
281 | | |
282 | | ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *execute_data) |
283 | 0 | { |
284 | 0 | _zend_observe_fcall_begin(execute_data); |
285 | 0 | } |
286 | | |
287 | | ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data) |
288 | 0 | { |
289 | 0 | ZEND_ASSUME(EX(func)); |
290 | 0 | if (!(EX(func)->common.fn_flags & ZEND_ACC_GENERATOR)) { |
291 | 0 | _zend_observe_fcall_begin(execute_data); |
292 | 0 | } |
293 | 0 | } |
294 | | |
295 | 0 | static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) { |
296 | 0 | const zend_function *func = EX(func); |
297 | 0 | ZEND_ASSERT(func); |
298 | |
|
299 | 0 | zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count; |
300 | | // TODO: Fix exceptions from generators |
301 | | // ZEND_ASSERT(fcall_data); |
302 | 0 | if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) { |
303 | 0 | return; |
304 | 0 | } |
305 | | |
306 | 0 | zend_observer_fcall_end_handler *possible_handlers_end = handler + zend_observers_fcall_list.count; |
307 | 0 | do { |
308 | 0 | (*handler)(execute_data, return_value); |
309 | 0 | } while (++handler != possible_handlers_end && *handler != NULL); |
310 | 0 | } |
311 | | |
312 | | ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value) |
313 | 0 | { |
314 | 0 | call_end_observers(execute_data, return_value); |
315 | 0 | EG(current_observed_frame) = *prev_observed_frame(execute_data); |
316 | 0 | } |
317 | | |
318 | | ZEND_API void zend_observer_fcall_end_all(void) |
319 | 0 | { |
320 | 0 | zend_execute_data *execute_data = EG(current_observed_frame), *original_execute_data = EG(current_execute_data); |
321 | 0 | EG(current_observed_frame) = NULL; |
322 | 0 | while (execute_data) { |
323 | 0 | EG(current_execute_data) = execute_data; |
324 | 0 | call_end_observers(execute_data, NULL); |
325 | 0 | execute_data = *prev_observed_frame(execute_data); |
326 | 0 | } |
327 | 0 | EG(current_execute_data) = original_execute_data; |
328 | 0 | } |
329 | | |
330 | | ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb) |
331 | 0 | { |
332 | 0 | zend_observer_function_declared_observed = true; |
333 | 0 | zend_llist_add_element(&zend_observer_function_declared_callbacks, &cb); |
334 | 0 | } |
335 | | |
336 | | ZEND_API void ZEND_FASTCALL _zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name) |
337 | 0 | { |
338 | 0 | if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) { |
339 | 0 | return; |
340 | 0 | } |
341 | | |
342 | 0 | for (const zend_llist_element *element = zend_observer_function_declared_callbacks.head; element; element = element->next) { |
343 | 0 | zend_observer_function_declared_cb callback = *(zend_observer_function_declared_cb *) (element->data); |
344 | 0 | callback(op_array, name); |
345 | 0 | } |
346 | 0 | } |
347 | | |
348 | | ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb) |
349 | 0 | { |
350 | 0 | zend_observer_class_linked_observed = true; |
351 | 0 | zend_llist_add_element(&zend_observer_class_linked_callbacks, &cb); |
352 | 0 | } |
353 | | |
354 | | ZEND_API void ZEND_FASTCALL _zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name) |
355 | 0 | { |
356 | 0 | if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) { |
357 | 0 | return; |
358 | 0 | } |
359 | | |
360 | 0 | for (const zend_llist_element *element = zend_observer_class_linked_callbacks.head; element; element = element->next) { |
361 | 0 | zend_observer_class_linked_cb callback = *(zend_observer_class_linked_cb *) (element->data); |
362 | 0 | callback(ce, name); |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) |
367 | 2 | { |
368 | 2 | zend_observer_errors_observed = true; |
369 | 2 | zend_llist_add_element(&zend_observer_error_callbacks, &cb); |
370 | 2 | } |
371 | | |
372 | | ZEND_API void _zend_observer_error_notify(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message) |
373 | 259k | { |
374 | 518k | for (const zend_llist_element *element = zend_observer_error_callbacks.head; element; element = element->next) { |
375 | 259k | zend_observer_error_cb callback = *(zend_observer_error_cb *) (element->data); |
376 | 259k | callback(type, error_filename, error_lineno, message); |
377 | 259k | } |
378 | 259k | } |
379 | | |
380 | | ZEND_API void zend_observer_fiber_init_register(zend_observer_fiber_init_handler handler) |
381 | 0 | { |
382 | 0 | zend_llist_add_element(&zend_observer_fiber_init, &handler); |
383 | 0 | } |
384 | | |
385 | | ZEND_API void zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler) |
386 | 0 | { |
387 | 0 | zend_llist_add_element(&zend_observer_fiber_switch, &handler); |
388 | 0 | } |
389 | | |
390 | | ZEND_API void zend_observer_fiber_destroy_register(zend_observer_fiber_destroy_handler handler) |
391 | 0 | { |
392 | 0 | zend_llist_add_element(&zend_observer_fiber_destroy, &handler); |
393 | 0 | } |
394 | | |
395 | | ZEND_API void ZEND_FASTCALL zend_observer_fiber_init_notify(zend_fiber_context *initializing) |
396 | 0 | { |
397 | 0 | zend_observer_fiber_init_handler callback; |
398 | |
|
399 | 0 | initializing->top_observed_frame = NULL; |
400 | |
|
401 | 0 | for (const zend_llist_element *element = zend_observer_fiber_init.head; element; element = element->next) { |
402 | 0 | callback = *(zend_observer_fiber_init_handler *) element->data; |
403 | 0 | callback(initializing); |
404 | 0 | } |
405 | 0 | } |
406 | | |
407 | | ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context *from, zend_fiber_context *to) |
408 | 0 | { |
409 | 0 | zend_observer_fiber_switch_handler callback; |
410 | |
|
411 | 0 | if (from->status == ZEND_FIBER_STATUS_DEAD) { |
412 | 0 | zend_observer_fcall_end_all(); // fiber is either finished (call will do nothing) or has bailed out |
413 | 0 | } |
414 | |
|
415 | 0 | for (const zend_llist_element *element = zend_observer_fiber_switch.head; element; element = element->next) { |
416 | 0 | callback = *(zend_observer_fiber_switch_handler *) element->data; |
417 | 0 | callback(from, to); |
418 | 0 | } |
419 | |
|
420 | 0 | from->top_observed_frame = EG(current_observed_frame); |
421 | 0 | EG(current_observed_frame) = to->top_observed_frame; |
422 | 0 | } |
423 | | |
424 | | ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying) |
425 | 0 | { |
426 | 0 | zend_observer_fiber_destroy_handler callback; |
427 | |
|
428 | 0 | for (const zend_llist_element *element = zend_observer_fiber_destroy.head; element; element = element->next) { |
429 | 0 | callback = *(zend_observer_fiber_destroy_handler *) element->data; |
430 | 0 | callback(destroying); |
431 | 0 | } |
432 | 0 | } |