Skip to content

Commit 625f164

Browse files
committed
Include internal functions in the observer API
There are two main motivations to this: a) The logic for handling internal and userland observation can be unified. b) Unwinding of observed functions on a bailout does notably not include observers. Even if users of observers were to ensure such handling themselves, it would be impossible to retain the relative ordering - either the user has to unwind all internal observed frames before the automatic unwinding (zend_observer_fcall_end_all) or afterwards, but not properly interleaved. Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
1 parent 0c225a2 commit 625f164

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1672
-1254
lines changed

UPGRADING.INTERNALS

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ PHP 8.2 INTERNALS UPGRADE NOTES
5151
are deprecated (see main UPGRADING notes). To suppress the notice, e.g. to
5252
avoid duplicates when processing the same value multiple times, pass or add
5353
IS_CALLABLE_SUPPRESS_DEPRECATIONS to the check_flags parameter.
54+
* Registered zend_observer_fcall_init handlers are now also called for internal functions.
5455

5556
========================
5657
2. Build system changes

Zend/zend.c

+1
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,7 @@ ZEND_API void zend_activate(void) /* {{{ */
12261226
if (CG(map_ptr_last)) {
12271227
memset(CG(map_ptr_real_base), 0, CG(map_ptr_last) * sizeof(void*));
12281228
}
1229+
zend_init_internal_run_time_cache();
12291230
zend_observer_activate();
12301231
}
12311232
/* }}} */

Zend/zend_API.c

+5
Original file line numberDiff line numberDiff line change
@@ -2694,6 +2694,11 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
26942694
internal_function->scope = scope;
26952695
internal_function->prototype = NULL;
26962696
internal_function->attributes = NULL;
2697+
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
2698+
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
2699+
} else {
2700+
ZEND_MAP_PTR_NEW(internal_function->run_time_cache);
2701+
}
26972702
if (ptr->flags) {
26982703
if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
26992704
if (ptr->flags != ZEND_ACC_DEPRECATED && scope) {

Zend/zend_compile.c

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "zend_inheritance.h"
3434
#include "zend_vm.h"
3535
#include "zend_enum.h"
36+
#include "zend_observer.h"
3637

3738
#define SET_NODE(target, src) do { \
3839
target ## _type = (src)->op_type; \

Zend/zend_compile.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ struct _zend_op_array {
448448
uint32_t required_num_args;
449449
zend_arg_info *arg_info;
450450
HashTable *attributes;
451+
ZEND_MAP_PTR_DEF(void **, run_time_cache);
451452
/* END of common elements */
452453

453454
int cache_size; /* number of run_time_cache_slots * sizeof(void*) */
@@ -456,7 +457,6 @@ struct _zend_op_array {
456457
uint32_t last; /* number of opcodes */
457458

458459
zend_op *opcodes;
459-
ZEND_MAP_PTR_DEF(void **, run_time_cache);
460460
ZEND_MAP_PTR_DEF(HashTable *, static_variables_ptr);
461461
HashTable *static_variables;
462462
zend_string **vars; /* names of CV variables */
@@ -503,6 +503,7 @@ typedef struct _zend_internal_function {
503503
uint32_t required_num_args;
504504
zend_internal_arg_info *arg_info;
505505
HashTable *attributes;
506+
ZEND_MAP_PTR_DEF(void **, run_time_cache);
506507
/* END of common elements */
507508

508509
zif_handler handler;
@@ -527,6 +528,7 @@ union _zend_function {
527528
uint32_t required_num_args;
528529
zend_arg_info *arg_info; /* index -1 represents the return value info, if any */
529530
HashTable *attributes;
531+
ZEND_MAP_PTR_DEF(void **, run_time_cache);
530532
} common;
531533

532534
zend_op_array op_array;

Zend/zend_enum.c

+20-30
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "zend_enum_arginfo.h"
2323
#include "zend_interfaces.h"
2424
#include "zend_enum.h"
25+
#include "zend_extensions.h"
2526

2627
#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
2728
do { \
@@ -401,59 +402,48 @@ static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
401402
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
402403
}
403404

405+
static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) {
406+
zend_string *name = ZSTR_KNOWN(name_id);
407+
zif->type = ZEND_INTERNAL_FUNCTION;
408+
zif->module = EG(current_module);
409+
zif->scope = ce;
410+
ZEND_MAP_PTR_NEW(zif->run_time_cache);
411+
ZEND_MAP_PTR_SET(zif->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
412+
413+
if (!zend_hash_add_ptr(&ce->function_table, name, zif)) {
414+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name));
415+
}
416+
}
417+
404418
void zend_enum_register_funcs(zend_class_entry *ce)
405419
{
406420
const uint32_t fn_flags =
407421
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
408-
zend_internal_function *cases_function =
409-
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
410-
memset(cases_function, 0, sizeof(zend_internal_function));
411-
cases_function->type = ZEND_INTERNAL_FUNCTION;
412-
cases_function->module = EG(current_module);
422+
zend_internal_function *cases_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
413423
cases_function->handler = zend_enum_cases_func;
414424
cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
415-
cases_function->scope = ce;
416425
cases_function->fn_flags = fn_flags;
417426
cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
418-
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_CASES), cases_function)) {
419-
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::cases()", ZSTR_VAL(ce->name));
420-
}
427+
zend_enum_register_func(ce, ZEND_STR_CASES, cases_function);
421428

422429
if (ce->enum_backing_type != IS_UNDEF) {
423-
zend_internal_function *from_function =
424-
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
425-
memset(from_function, 0, sizeof(zend_internal_function));
426-
from_function->type = ZEND_INTERNAL_FUNCTION;
427-
from_function->module = EG(current_module);
430+
zend_internal_function *from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
428431
from_function->handler = zend_enum_from_func;
429432
from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
430-
from_function->scope = ce;
431433
from_function->fn_flags = fn_flags;
432434
from_function->num_args = 1;
433435
from_function->required_num_args = 1;
434436
from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
435-
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_FROM), from_function)) {
436-
zend_error_noreturn(E_COMPILE_ERROR,
437-
"Cannot redeclare %s::from()", ZSTR_VAL(ce->name));
438-
}
437+
zend_enum_register_func(ce, ZEND_STR_FROM, from_function);
439438

440-
zend_internal_function *try_from_function =
441-
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
442-
memset(try_from_function, 0, sizeof(zend_internal_function));
443-
try_from_function->type = ZEND_INTERNAL_FUNCTION;
444-
try_from_function->module = EG(current_module);
439+
zend_internal_function *try_from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
445440
try_from_function->handler = zend_enum_try_from_func;
446441
try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
447-
try_from_function->scope = ce;
448442
try_from_function->fn_flags = fn_flags;
449443
try_from_function->num_args = 1;
450444
try_from_function->required_num_args = 1;
451445
try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
452-
if (!zend_hash_add_ptr(
453-
&ce->function_table, ZSTR_KNOWN(ZEND_STR_TRYFROM_LOWERCASE), try_from_function)) {
454-
zend_error_noreturn(E_COMPILE_ERROR,
455-
"Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name));
456-
}
446+
zend_enum_register_func(ce, ZEND_STR_TRYFROM_LOWERCASE, try_from_function);
457447
}
458448
}
459449

Zend/zend_execute.c

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ ZEND_API const zend_internal_function zend_pass_function = {
144144
0, /* required_num_args */
145145
(zend_internal_arg_info *) zend_pass_function_arg_info + 1, /* arg_info */
146146
NULL, /* attributes */
147+
NULL, /* run_time_cache */
147148
ZEND_FN(pass), /* handler */
148149
NULL, /* module */
149150
{NULL,NULL,NULL,NULL} /* reserved */

Zend/zend_execute_API.c

+2
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
935935
#if ZEND_DEBUG
936936
bool should_throw = zend_internal_call_should_throw(func, call);
937937
#endif
938+
ZEND_OBSERVER_FCALL_BEGIN(call);
938939
if (EXPECTED(zend_execute_internal == NULL)) {
939940
/* saves one function call if zend_execute_internal is not used */
940941
func->internal_function.handler(call, fci->retval);
@@ -953,6 +954,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
953954
? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval));
954955
}
955956
#endif
957+
ZEND_OBSERVER_FCALL_END(call, fci->retval);
956958
EG(current_execute_data) = call->prev_execute_data;
957959
zend_vm_stack_free_args(call);
958960
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {

Zend/zend_extensions.c

+34
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,40 @@ ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int ha
280280
return handle;
281281
}
282282

283+
ZEND_API size_t zend_internal_run_time_cache_reserved_size() {
284+
return zend_op_array_extension_handles * sizeof(void *);
285+
}
286+
287+
ZEND_API void zend_init_internal_run_time_cache() {
288+
size_t rt_size = zend_internal_run_time_cache_reserved_size();
289+
if (rt_size) {
290+
size_t functions = zend_hash_num_elements(CG(function_table));
291+
zend_class_entry *ce;
292+
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
293+
functions += zend_hash_num_elements(&ce->function_table);
294+
} ZEND_HASH_FOREACH_END();
295+
296+
char *ptr = zend_arena_calloc(&CG(arena), functions, rt_size);
297+
zend_internal_function *zif;
298+
ZEND_HASH_MAP_FOREACH_PTR(CG(function_table), zif) {
299+
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
300+
{
301+
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
302+
ptr += rt_size;
303+
}
304+
} ZEND_HASH_FOREACH_END();
305+
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
306+
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) {
307+
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
308+
{
309+
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
310+
ptr += rt_size;
311+
}
312+
} ZEND_HASH_FOREACH_END();
313+
} ZEND_HASH_FOREACH_END();
314+
}
315+
}
316+
283317
ZEND_API zend_extension *zend_get_extension(const char *extension_name)
284318
{
285319
zend_llist_element *element;

Zend/zend_extensions.h

+3
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ void zend_startup_extensions_mechanism(void);
145145
void zend_startup_extensions(void);
146146
void zend_shutdown_extensions(void);
147147

148+
ZEND_API size_t zend_internal_run_time_cache_reserved_size(void);
149+
ZEND_API void zend_init_internal_run_time_cache(void);
150+
148151
BEGIN_EXTERN_C()
149152
ZEND_API zend_result zend_load_extension(const char *path);
150153
ZEND_API zend_result zend_load_extension_handle(DL_HANDLE handle, const char *path);

Zend/zend_observer.c

+22-26
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
#include "zend_llist.h"
2424
#include "zend_vm.h"
2525

26-
#define ZEND_OBSERVER_DATA(op_array) \
27-
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)
26+
#define ZEND_OBSERVER_DATA(function) \
27+
ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension)
2828

2929
#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)
3030

31-
#define ZEND_OBSERVABLE_FN(fn_flags) \
32-
(!(fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
31+
#define ZEND_OBSERVABLE_FN(function) \
32+
(ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
3333

3434
zend_llist zend_observers_fcall_list;
3535
zend_llist zend_observer_error_callbacks;
@@ -100,12 +100,9 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
100100
{
101101
zend_llist *list = &zend_observers_fcall_list;
102102
zend_function *function = execute_data->func;
103-
zend_op_array *op_array = &function->op_array;
104103

105-
ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);
106-
107-
ZEND_ASSERT(RUN_TIME_CACHE(op_array));
108-
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
104+
ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
105+
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
109106
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
110107

111108
*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
@@ -152,9 +149,9 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler
152149
return false;
153150
}
154151

155-
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
152+
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
156153
size_t registered_observers = zend_observers_fcall_list.count;
157-
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(op_array), *last_handler = first_handler + registered_observers - 1;
154+
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
158155
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
159156
*first_handler = begin;
160157
} else {
@@ -169,13 +166,13 @@ ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_obse
169166
}
170167
}
171168

172-
ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
173-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array), begin);
169+
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
170+
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
174171
}
175172

176-
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
173+
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
177174
size_t registered_observers = zend_observers_fcall_list.count;
178-
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(op_array) + registered_observers;
175+
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(function) + registered_observers;
179176
// to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front
180177
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
181178
// there's no space for new handlers, then it's forbidden to call this function
@@ -185,9 +182,9 @@ ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observ
185182
*end_handler = end;
186183
}
187184

188-
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
185+
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
189186
size_t registered_observers = zend_observers_fcall_list.count;
190-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array) + registered_observers, end);
187+
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
191188
}
192189

193190
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
@@ -196,14 +193,13 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
196193
return;
197194
}
198195

199-
zend_op_array *op_array = &execute_data->func->op_array;
200-
uint32_t fn_flags = op_array->fn_flags;
196+
zend_function *function = execute_data->func;
201197

202-
if (!ZEND_OBSERVABLE_FN(fn_flags)) {
198+
if (!ZEND_OBSERVABLE_FN(function)) {
203199
return;
204200
}
205201

206-
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
202+
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
207203
if (!*handler) {
208204
zend_observer_fcall_install(execute_data);
209205
}
@@ -243,11 +239,11 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute
243239
static inline bool zend_observer_is_skipped_frame(zend_execute_data *execute_data) {
244240
zend_function *func = execute_data->func;
245241

246-
if (!func || func->type == ZEND_INTERNAL_FUNCTION || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
242+
if (!func || !ZEND_OBSERVABLE_FN(func)) {
247243
return true;
248244
}
249245

250-
zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(&func->op_array))[zend_observers_fcall_list.count];
246+
zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(func))[zend_observers_fcall_list.count];
251247
if (end_handler == NULL || end_handler == ZEND_OBSERVER_NOT_OBSERVED) {
252248
return true;
253249
}
@@ -259,11 +255,11 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_d
259255
{
260256
zend_function *func = execute_data->func;
261257

262-
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
258+
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func)) {
263259
return;
264260
}
265261

266-
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(&func->op_array) + zend_observers_fcall_list.count;
262+
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
267263
// TODO: Fix exceptions from generators
268264
// ZEND_ASSERT(fcall_data);
269265
if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
@@ -291,7 +287,7 @@ ZEND_API void zend_observer_fcall_end_all(void)
291287
{
292288
zend_execute_data *ex = current_observed_frame;
293289
while (ex != NULL) {
294-
if (ex->func && ex->func->type != ZEND_INTERNAL_FUNCTION) {
290+
if (ex->func) {
295291
zend_observer_fcall_end(ex, NULL);
296292
}
297293
ex = ex->prev_execute_data;

Zend/zend_observer.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);
5858

5959
// Call during runtime, but only if you have used zend_observer_fcall_register.
6060
// You must not have more than one begin and one end handler active at the same time. Remove the old one first, if there is an existing one.
61-
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
62-
ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
63-
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
64-
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
61+
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
62+
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
63+
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
64+
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
6565

6666
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
6767
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs

0 commit comments

Comments
 (0)