Skip to content

Commit

Permalink
Include internal functions in the observer API
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
bwoebi committed Jul 30, 2022
1 parent 0c225a2 commit 625f164
Show file tree
Hide file tree
Showing 49 changed files with 1,672 additions and 1,254 deletions.
1 change: 1 addition & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ PHP 8.2 INTERNALS UPGRADE NOTES
are deprecated (see main UPGRADING notes). To suppress the notice, e.g. to
avoid duplicates when processing the same value multiple times, pass or add
IS_CALLABLE_SUPPRESS_DEPRECATIONS to the check_flags parameter.
* Registered zend_observer_fcall_init handlers are now also called for internal functions.

========================
2. Build system changes
Expand Down
1 change: 1 addition & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,7 @@ ZEND_API void zend_activate(void) /* {{{ */
if (CG(map_ptr_last)) {
memset(CG(map_ptr_real_base), 0, CG(map_ptr_last) * sizeof(void*));
}
zend_init_internal_run_time_cache();
zend_observer_activate();
}
/* }}} */
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -2694,6 +2694,11 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
internal_function->scope = scope;
internal_function->prototype = NULL;
internal_function->attributes = NULL;
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
} else {
ZEND_MAP_PTR_NEW(internal_function->run_time_cache);
}
if (ptr->flags) {
if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
if (ptr->flags != ZEND_ACC_DEPRECATED && scope) {
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "zend_inheritance.h"
#include "zend_vm.h"
#include "zend_enum.h"
#include "zend_observer.h"

#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \
Expand Down
4 changes: 3 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ struct _zend_op_array {
uint32_t required_num_args;
zend_arg_info *arg_info;
HashTable *attributes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
/* END of common elements */

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

zend_op *opcodes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
ZEND_MAP_PTR_DEF(HashTable *, static_variables_ptr);
HashTable *static_variables;
zend_string **vars; /* names of CV variables */
Expand Down Expand Up @@ -503,6 +503,7 @@ typedef struct _zend_internal_function {
uint32_t required_num_args;
zend_internal_arg_info *arg_info;
HashTable *attributes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
/* END of common elements */

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

zend_op_array op_array;
Expand Down
50 changes: 20 additions & 30 deletions Zend/zend_enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "zend_enum_arginfo.h"
#include "zend_interfaces.h"
#include "zend_enum.h"
#include "zend_extensions.h"

#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
do { \
Expand Down Expand Up @@ -401,59 +402,48 @@ static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}

static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) {
zend_string *name = ZSTR_KNOWN(name_id);
zif->type = ZEND_INTERNAL_FUNCTION;
zif->module = EG(current_module);
zif->scope = ce;
ZEND_MAP_PTR_NEW(zif->run_time_cache);
ZEND_MAP_PTR_SET(zif->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));

if (!zend_hash_add_ptr(&ce->function_table, name, zif)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
}

void zend_enum_register_funcs(zend_class_entry *ce)
{
const uint32_t fn_flags =
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
zend_internal_function *cases_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(cases_function, 0, sizeof(zend_internal_function));
cases_function->type = ZEND_INTERNAL_FUNCTION;
cases_function->module = EG(current_module);
zend_internal_function *cases_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
cases_function->handler = zend_enum_cases_func;
cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
cases_function->scope = ce;
cases_function->fn_flags = fn_flags;
cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_CASES), cases_function)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::cases()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_CASES, cases_function);

if (ce->enum_backing_type != IS_UNDEF) {
zend_internal_function *from_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(from_function, 0, sizeof(zend_internal_function));
from_function->type = ZEND_INTERNAL_FUNCTION;
from_function->module = EG(current_module);
zend_internal_function *from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
from_function->handler = zend_enum_from_func;
from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
from_function->scope = ce;
from_function->fn_flags = fn_flags;
from_function->num_args = 1;
from_function->required_num_args = 1;
from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_FROM), from_function)) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot redeclare %s::from()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_FROM, from_function);

zend_internal_function *try_from_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(try_from_function, 0, sizeof(zend_internal_function));
try_from_function->type = ZEND_INTERNAL_FUNCTION;
try_from_function->module = EG(current_module);
zend_internal_function *try_from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
try_from_function->handler = zend_enum_try_from_func;
try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
try_from_function->scope = ce;
try_from_function->fn_flags = fn_flags;
try_from_function->num_args = 1;
try_from_function->required_num_args = 1;
try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
if (!zend_hash_add_ptr(
&ce->function_table, ZSTR_KNOWN(ZEND_STR_TRYFROM_LOWERCASE), try_from_function)) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_TRYFROM_LOWERCASE, try_from_function);
}
}

Expand Down
1 change: 1 addition & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ ZEND_API const zend_internal_function zend_pass_function = {
0, /* required_num_args */
(zend_internal_arg_info *) zend_pass_function_arg_info + 1, /* arg_info */
NULL, /* attributes */
NULL, /* run_time_cache */
ZEND_FN(pass), /* handler */
NULL, /* module */
{NULL,NULL,NULL,NULL} /* reserved */
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
#if ZEND_DEBUG
bool should_throw = zend_internal_call_should_throw(func, call);
#endif
ZEND_OBSERVER_FCALL_BEGIN(call);
if (EXPECTED(zend_execute_internal == NULL)) {
/* saves one function call if zend_execute_internal is not used */
func->internal_function.handler(call, fci->retval);
Expand All @@ -953,6 +954,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval));
}
#endif
ZEND_OBSERVER_FCALL_END(call, fci->retval);
EG(current_execute_data) = call->prev_execute_data;
zend_vm_stack_free_args(call);
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
Expand Down
34 changes: 34 additions & 0 deletions Zend/zend_extensions.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,40 @@ ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int ha
return handle;
}

ZEND_API size_t zend_internal_run_time_cache_reserved_size() {
return zend_op_array_extension_handles * sizeof(void *);
}

ZEND_API void zend_init_internal_run_time_cache() {
size_t rt_size = zend_internal_run_time_cache_reserved_size();
if (rt_size) {
size_t functions = zend_hash_num_elements(CG(function_table));
zend_class_entry *ce;
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
functions += zend_hash_num_elements(&ce->function_table);
} ZEND_HASH_FOREACH_END();

char *ptr = zend_arena_calloc(&CG(arena), functions, rt_size);
zend_internal_function *zif;
ZEND_HASH_MAP_FOREACH_PTR(CG(function_table), zif) {
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
{
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
ptr += rt_size;
}
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) {
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
{
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
ptr += rt_size;
}
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
}
}

ZEND_API zend_extension *zend_get_extension(const char *extension_name)
{
zend_llist_element *element;
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_extensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ void zend_startup_extensions_mechanism(void);
void zend_startup_extensions(void);
void zend_shutdown_extensions(void);

ZEND_API size_t zend_internal_run_time_cache_reserved_size(void);
ZEND_API void zend_init_internal_run_time_cache(void);

BEGIN_EXTERN_C()
ZEND_API zend_result zend_load_extension(const char *path);
ZEND_API zend_result zend_load_extension_handle(DL_HANDLE handle, const char *path);
Expand Down
48 changes: 22 additions & 26 deletions Zend/zend_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
#include "zend_llist.h"
#include "zend_vm.h"

#define ZEND_OBSERVER_DATA(op_array) \
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)
#define ZEND_OBSERVER_DATA(function) \
ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension)

#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)

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

zend_llist zend_observers_fcall_list;
zend_llist zend_observer_error_callbacks;
Expand Down Expand Up @@ -100,12 +100,9 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
{
zend_llist *list = &zend_observers_fcall_list;
zend_function *function = execute_data->func;
zend_op_array *op_array = &function->op_array;

ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);

ZEND_ASSERT(RUN_TIME_CACHE(op_array));
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;

*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
Expand Down Expand Up @@ -152,9 +149,9 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler
return false;
}

ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
size_t registered_observers = zend_observers_fcall_list.count;
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(op_array), *last_handler = first_handler + registered_observers - 1;
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
*first_handler = begin;
} else {
Expand All @@ -169,13 +166,13 @@ ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_obse
}
}

ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array), begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
}

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

ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
size_t registered_observers = zend_observers_fcall_list.count;
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array) + registered_observers, end);
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
}

static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
Expand All @@ -196,14 +193,13 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
return;
}

zend_op_array *op_array = &execute_data->func->op_array;
uint32_t fn_flags = op_array->fn_flags;
zend_function *function = execute_data->func;

if (!ZEND_OBSERVABLE_FN(fn_flags)) {
if (!ZEND_OBSERVABLE_FN(function)) {
return;
}

zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
if (!*handler) {
zend_observer_fcall_install(execute_data);
}
Expand Down Expand Up @@ -243,11 +239,11 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute
static inline bool zend_observer_is_skipped_frame(zend_execute_data *execute_data) {
zend_function *func = execute_data->func;

if (!func || func->type == ZEND_INTERNAL_FUNCTION || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
if (!func || !ZEND_OBSERVABLE_FN(func)) {
return true;
}

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

if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func)) {
return;
}

zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(&func->op_array) + zend_observers_fcall_list.count;
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
// TODO: Fix exceptions from generators
// ZEND_ASSERT(fcall_data);
if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
Expand Down Expand Up @@ -291,7 +287,7 @@ ZEND_API void zend_observer_fcall_end_all(void)
{
zend_execute_data *ex = current_observed_frame;
while (ex != NULL) {
if (ex->func && ex->func->type != ZEND_INTERNAL_FUNCTION) {
if (ex->func) {
zend_observer_fcall_end(ex, NULL);
}
ex = ex->prev_execute_data;
Expand Down
8 changes: 4 additions & 4 deletions Zend/zend_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);

// Call during runtime, but only if you have used zend_observer_fcall_register.
// 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.
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end);

ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs
Expand Down
Loading

0 comments on commit 625f164

Please sign in to comment.