Coverage Report

Created: 2026-06-02 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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.9k
{
101
38.9k
    EG(current_observed_frame) = NULL;
102
38.9k
}
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
158k
{
374
316k
  for (const zend_llist_element *element = zend_observer_error_callbacks.head; element; element = element->next) {
375
158k
    zend_observer_error_cb callback = *(zend_observer_error_cb *) (element->data);
376
158k
    callback(type, error_filename, error_lineno, message);
377
158k
  }
378
158k
}
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
}