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