Coverage Report

Created: 2025-06-13 06:43

/src/php-src/Zend/zend_observer.c
Line
Count
Source (jump to first uncovered line)
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
0
#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
44
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init)
45
0
{
46
0
  zend_llist_add_element(&zend_observers_fcall_list, &init);
47
0
}
48
49
// Called by engine before MINITs
50
ZEND_API void zend_observer_startup(void)
51
16
{
52
16
  zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1);
53
16
  zend_llist_init(&zend_observer_function_declared_callbacks, sizeof(zend_observer_function_declared_cb), NULL, 1);
54
16
  zend_llist_init(&zend_observer_class_linked_callbacks, sizeof(zend_observer_class_linked_cb), NULL, 1);
55
16
  zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1);
56
16
  zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1);
57
16
  zend_llist_init(&zend_observer_fiber_switch, sizeof(zend_observer_fiber_switch_handler), NULL, 1);
58
16
  zend_llist_init(&zend_observer_fiber_destroy, sizeof(zend_observer_fiber_destroy_handler), NULL, 1);
59
60
16
  zend_observer_fcall_op_array_extension = -1;
61
16
  zend_observer_fcall_internal_function_extension = -1;
62
16
}
63
64
ZEND_API void zend_observer_post_startup(void)
65
16
{
66
16
  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
0
    zend_observer_fcall_op_array_extension =
70
0
      zend_get_op_array_extension_handles("Zend Observer", (int) zend_observers_fcall_list.count * 2);
71
72
0
    zend_observer_fcall_internal_function_extension =
73
0
      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
0
    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
0
    ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op));
83
0
    ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 1);
84
0
    ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 2);
85
86
    // Add an observer temporary to store previous observed frames
87
0
    zend_internal_function *zif;
88
0
    ZEND_HASH_FOREACH_PTR(CG(function_table), zif) {
89
0
      ++zif->T;
90
0
    } ZEND_HASH_FOREACH_END();
91
0
    zend_class_entry *ce;
92
0
    ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
93
0
      ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) {
94
0
        ++zif->T;
95
0
      } ZEND_HASH_FOREACH_END();
96
0
    } ZEND_HASH_FOREACH_END();
97
0
  }
98
16
}
99
100
ZEND_API void zend_observer_activate(void)
101
300k
{
102
300k
    EG(current_observed_frame) = NULL;
103
300k
}
104
105
ZEND_API void zend_observer_shutdown(void)
106
0
{
107
0
  zend_llist_destroy(&zend_observers_fcall_list);
108
0
  zend_llist_destroy(&zend_observer_function_declared_callbacks);
109
0
  zend_llist_destroy(&zend_observer_class_linked_callbacks);
110
0
  zend_llist_destroy(&zend_observer_error_callbacks);
111
0
  zend_llist_destroy(&zend_observer_fiber_init);
112
0
  zend_llist_destroy(&zend_observer_fiber_switch);
113
0
  zend_llist_destroy(&zend_observer_fiber_destroy);
114
0
}
115
116
static void zend_observer_fcall_install(zend_execute_data *execute_data)
117
0
{
118
0
  zend_llist *list = &zend_observers_fcall_list;
119
0
  zend_function *function = execute_data->func;
120
121
0
  ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
122
0
  zend_observer_fcall_begin_handler *begin_handlers = ZEND_OBSERVER_DATA(function), *begin_handlers_start = begin_handlers;
123
0
  zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
124
125
0
  *begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
126
0
  *end_handlers = ZEND_OBSERVER_NOT_OBSERVED;
127
0
  bool has_handlers = false;
128
129
0
  for (zend_llist_element *element = list->head; element; element = element->next) {
130
0
    zend_observer_fcall_init init;
131
0
    memcpy(&init, element->data, sizeof init);
132
0
    zend_observer_fcall_handlers handlers = init(execute_data);
133
0
    if (handlers.begin) {
134
0
      *(begin_handlers++) = handlers.begin;
135
0
      has_handlers = true;
136
0
    }
137
0
    if (handlers.end) {
138
0
      *(end_handlers++) = handlers.end;
139
0
      has_handlers = true;
140
0
    }
141
0
  }
142
143
  // end handlers are executed in reverse order
144
0
  for (--end_handlers; end_handlers_start < end_handlers; --end_handlers, ++end_handlers_start) {
145
0
    zend_observer_fcall_end_handler tmp = *end_handlers;
146
0
    *end_handlers = *end_handlers_start;
147
0
    *end_handlers_start = tmp;
148
0
  }
149
150
0
  if (!has_handlers) {
151
0
    *begin_handlers_start = ZEND_OBSERVER_NONE_OBSERVED;
152
0
  }
153
0
}
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. */
160
0
static bool zend_observer_remove_handler(void **first_handler, void *old_handler, void **next_handler) {
161
0
  size_t registered_observers = zend_observers_fcall_list.count;
162
163
0
  void **last_handler = first_handler + registered_observers - 1;
164
0
  for (void **cur_handler = first_handler; cur_handler <= last_handler; ++cur_handler) {
165
0
    if (*cur_handler == old_handler) {
166
0
      if (registered_observers == 1 || (cur_handler == first_handler && cur_handler[1] == NULL)) {
167
0
        *cur_handler = ZEND_OBSERVER_NOT_OBSERVED;
168
0
        *next_handler = NULL;
169
0
      } else {
170
0
        if (cur_handler != last_handler) {
171
0
          memmove(cur_handler, cur_handler + 1, sizeof(cur_handler) * (last_handler - cur_handler));
172
0
        }
173
0
        *last_handler = NULL;
174
0
        *next_handler = *cur_handler;
175
0
      }
176
0
      return true;
177
0
    }
178
0
  }
179
0
  return false;
180
0
}
181
182
0
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
183
0
  size_t registered_observers = zend_observers_fcall_list.count;
184
0
  zend_observer_fcall_begin_handler *first_handler = ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
185
0
  if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED || *first_handler == ZEND_OBSERVER_NONE_OBSERVED) {
186
0
    *first_handler = begin;
187
0
  } else {
188
0
    for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
189
0
      if (*cur_handler == NULL) {
190
0
        *cur_handler = begin;
191
0
        return;
192
0
      }
193
0
    }
194
    // there's no space for new handlers, then it's forbidden to call this function
195
0
    ZEND_UNREACHABLE();
196
0
  }
197
0
}
198
199
0
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin, zend_observer_fcall_begin_handler *next) {
200
0
  void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
201
0
  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
0
    if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
204
0
      size_t registered_observers = zend_observers_fcall_list.count;
205
0
      if (begin_handlers[registered_observers] /* first end handler */ == ZEND_OBSERVER_NOT_OBSERVED) {
206
0
        *begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
207
0
      }
208
0
    }
209
0
    return true;
210
0
  }
211
0
  return false;
212
0
}
213
214
0
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
215
0
  size_t registered_observers = zend_observers_fcall_list.count;
216
0
  void **begin_handler = (void **)ZEND_OBSERVER_DATA(function);
217
0
  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
0
  if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
220
    // there's no space for new handlers, then it's forbidden to call this function
221
0
    ZEND_ASSERT(end_handler[registered_observers - 1] == NULL);
222
0
    memmove(end_handler + 1, end_handler, sizeof(end_handler) * (registered_observers - 1));
223
0
  } else if (*begin_handler == ZEND_OBSERVER_NONE_OBSERVED) {
224
0
    *begin_handler = ZEND_OBSERVER_NOT_OBSERVED;
225
0
  }
226
0
  *end_handler = end;
227
0
}
228
229
0
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end, zend_observer_fcall_end_handler *next) {
230
0
  size_t registered_observers = zend_observers_fcall_list.count;
231
0
  void **begin_handlers = (void **)ZEND_OBSERVER_DATA(function);
232
0
  void **end_handlers = begin_handlers + registered_observers;
233
0
  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
0
    if (*begin_handlers == ZEND_OBSERVER_NOT_OBSERVED && *end_handlers == ZEND_OBSERVER_NOT_OBSERVED) {
236
0
      *begin_handlers = ZEND_OBSERVER_NONE_OBSERVED;
237
0
    }
238
0
    return true;
239
0
  }
240
0
  return false;
241
0
}
242
243
0
static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {
244
0
  zend_function *func = EX(func);
245
0
  ZEND_ASSERT(func);
246
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));
247
0
}
248
249
0
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) {
250
0
  if (!ZEND_OBSERVER_ENABLED) {
251
0
    return;
252
0
  }
253
254
0
  zend_observer_fcall_begin_specialized(execute_data, true);
255
0
}
256
257
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin_prechecked(zend_execute_data *execute_data, zend_observer_fcall_begin_handler *handler)
258
0
{
259
0
  zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;
260
261
0
  if (!*handler) {
262
0
    zend_observer_fcall_install(execute_data);
263
0
    if (zend_observer_handler_is_unobserved(handler)) {
264
0
      return;
265
0
    }
266
0
  }
267
268
0
  zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end;
269
0
  if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
270
0
    *prev_observed_frame(execute_data) = EG(current_observed_frame);
271
0
    EG(current_observed_frame) = execute_data;
272
273
0
    if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { // this function must not be called if ZEND_OBSERVER_NONE_OBSERVED, hence sufficient to check
274
0
      return;
275
0
    }
276
0
  }
277
278
0
  do {
279
0
    (*handler)(execute_data);
280
0
  } while (++handler != possible_handlers_end && *handler != NULL);
281
0
}
282
283
ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *execute_data)
284
0
{
285
0
  _zend_observe_fcall_begin(execute_data);
286
0
}
287
288
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute_data)
289
0
{
290
0
  ZEND_ASSUME(EX(func));
291
0
  if (!(EX(func)->common.fn_flags & ZEND_ACC_GENERATOR)) {
292
0
    _zend_observe_fcall_begin(execute_data);
293
0
  }
294
0
}
295
296
0
static inline void call_end_observers(zend_execute_data *execute_data, zval *return_value) {
297
0
  zend_function *func = EX(func);
298
0
  ZEND_ASSERT(func);
299
300
0
  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
0
  if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
304
0
    return;
305
0
  }
306
307
0
  zend_observer_fcall_end_handler *possible_handlers_end = handler + zend_observers_fcall_list.count;
308
0
  do {
309
0
    (*handler)(execute_data, return_value);
310
0
  } while (++handler != possible_handlers_end && *handler != NULL);
311
0
}
312
313
ZEND_API void ZEND_FASTCALL zend_observer_fcall_end_prechecked(zend_execute_data *execute_data, zval *return_value)
314
0
{
315
0
  call_end_observers(execute_data, return_value);
316
0
  EG(current_observed_frame) = *prev_observed_frame(execute_data);
317
0
}
318
319
ZEND_API void zend_observer_fcall_end_all(void)
320
718
{
321
718
  zend_execute_data *execute_data = EG(current_observed_frame), *original_execute_data = EG(current_execute_data);
322
718
    EG(current_observed_frame) = NULL;
323
718
  while (execute_data) {
324
0
    EG(current_execute_data) = execute_data;
325
0
    call_end_observers(execute_data, NULL);
326
0
    execute_data = *prev_observed_frame(execute_data);
327
0
  }
328
718
  EG(current_execute_data) = original_execute_data;
329
718
}
330
331
ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb)
332
0
{
333
0
  zend_observer_function_declared_observed = true;
334
0
  zend_llist_add_element(&zend_observer_function_declared_callbacks, &cb);
335
0
}
336
337
ZEND_API void ZEND_FASTCALL _zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name)
338
0
{
339
0
  if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) {
340
0
    return;
341
0
  }
342
343
0
  for (zend_llist_element *element = zend_observer_function_declared_callbacks.head; element; element = element->next) {
344
0
    zend_observer_function_declared_cb callback = *(zend_observer_function_declared_cb *) (element->data);
345
0
    callback(op_array, name);
346
0
  }
347
0
}
348
349
ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb)
350
0
{
351
0
  zend_observer_class_linked_observed = true;
352
0
  zend_llist_add_element(&zend_observer_class_linked_callbacks, &cb);
353
0
}
354
355
ZEND_API void ZEND_FASTCALL _zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name)
356
0
{
357
0
  if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) {
358
0
    return;
359
0
  }
360
361
0
  for (zend_llist_element *element = zend_observer_class_linked_callbacks.head; element; element = element->next) {
362
0
    zend_observer_class_linked_cb callback = *(zend_observer_class_linked_cb *) (element->data);
363
0
    callback(ce, name);
364
0
  }
365
0
}
366
367
ZEND_API void zend_observer_error_register(zend_observer_error_cb cb)
368
16
{
369
16
  zend_observer_errors_observed = true;
370
16
  zend_llist_add_element(&zend_observer_error_callbacks, &cb);
371
16
}
372
373
ZEND_API void _zend_observer_error_notify(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message)
374
3.35M
{
375
6.71M
  for (zend_llist_element *element = zend_observer_error_callbacks.head; element; element = element->next) {
376
3.35M
    zend_observer_error_cb callback = *(zend_observer_error_cb *) (element->data);
377
3.35M
    callback(type, error_filename, error_lineno, message);
378
3.35M
  }
379
3.35M
}
380
381
ZEND_API void zend_observer_fiber_init_register(zend_observer_fiber_init_handler handler)
382
0
{
383
0
  zend_llist_add_element(&zend_observer_fiber_init, &handler);
384
0
}
385
386
ZEND_API void zend_observer_fiber_switch_register(zend_observer_fiber_switch_handler handler)
387
0
{
388
0
  zend_llist_add_element(&zend_observer_fiber_switch, &handler);
389
0
}
390
391
ZEND_API void zend_observer_fiber_destroy_register(zend_observer_fiber_destroy_handler handler)
392
0
{
393
0
  zend_llist_add_element(&zend_observer_fiber_destroy, &handler);
394
0
}
395
396
ZEND_API void ZEND_FASTCALL zend_observer_fiber_init_notify(zend_fiber_context *initializing)
397
723
{
398
723
  zend_llist_element *element;
399
723
  zend_observer_fiber_init_handler callback;
400
401
723
  initializing->top_observed_frame = NULL;
402
403
723
  for (element = zend_observer_fiber_init.head; element; element = element->next) {
404
0
    callback = *(zend_observer_fiber_init_handler *) element->data;
405
0
    callback(initializing);
406
0
  }
407
723
}
408
409
ZEND_API void ZEND_FASTCALL zend_observer_fiber_switch_notify(zend_fiber_context *from, zend_fiber_context *to)
410
2.49k
{
411
2.49k
  zend_llist_element *element;
412
2.49k
  zend_observer_fiber_switch_handler callback;
413
414
2.49k
  if (from->status == ZEND_FIBER_STATUS_DEAD) {
415
718
    zend_observer_fcall_end_all(); // fiber is either finished (call will do nothing) or has bailed out
416
718
  }
417
418
2.49k
  for (element = zend_observer_fiber_switch.head; element; element = element->next) {
419
0
    callback = *(zend_observer_fiber_switch_handler *) element->data;
420
0
    callback(from, to);
421
0
  }
422
423
2.49k
  from->top_observed_frame = EG(current_observed_frame);
424
2.49k
    EG(current_observed_frame) = to->top_observed_frame;
425
2.49k
}
426
427
ZEND_API void ZEND_FASTCALL zend_observer_fiber_destroy_notify(zend_fiber_context *destroying)
428
718
{
429
718
  zend_llist_element *element;
430
718
  zend_observer_fiber_destroy_handler callback;
431
432
718
  for (element = zend_observer_fiber_destroy.head; element; element = element->next) {
433
0
    callback = *(zend_observer_fiber_destroy_handler *) element->data;
434
0
    callback(destroying);
435
0
  }
436
718
}