Skip to content

Add an API to manipulate observers at runtime #9062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 62 additions & 9 deletions Zend/zend_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ ZEND_API void zend_observer_activate(void)
current_observed_frame = NULL;
}

ZEND_API void zend_observer_deactivate(void)
{
// now empty and unused, but kept for ABI compatibility
}

ZEND_API void zend_observer_shutdown(void)
{
zend_llist_destroy(&zend_observers_fcall_list);
Expand All @@ -112,7 +107,7 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
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_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;
*end_handlers = ZEND_OBSERVER_NOT_OBSERVED;

Expand All @@ -127,7 +122,7 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
*(end_handlers++) = handlers.end;
}
}

// end handlers are executed in reverse order
for (--end_handlers; end_handlers_start < end_handlers; --end_handlers, ++end_handlers_start) {
zend_observer_fcall_end_handler tmp = *end_handlers;
Expand All @@ -136,6 +131,65 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
}
}

static bool zend_observer_remove_handler(void **first_handler, void *old_handler) {
size_t registered_observers = zend_observers_fcall_list.count;

void **last_handler = first_handler + registered_observers - 1;
for (void **cur_handler = first_handler; cur_handler <= last_handler; ++cur_handler) {
if (*cur_handler == old_handler) {
if (registered_observers == 1 || (cur_handler == first_handler && cur_handler[1] == NULL)) {
*cur_handler = ZEND_OBSERVER_NOT_OBSERVED;
} else {
if (cur_handler != last_handler) {
memmove(cur_handler, cur_handler + 1, sizeof(cur_handler) * (last_handler - cur_handler));
} else {
*last_handler = NULL;
}
}
return true;
}
}
return false;
}

ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, 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;
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
*first_handler = begin;
} else {
for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
if (*cur_handler == NULL) {
*cur_handler = begin;
return;
}
}
// there's no space for new handlers, then it's forbidden to call this function
ZEND_UNREACHABLE();
}
}

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 void zend_observer_add_end_handler(zend_op_array *op_array, 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;
// 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
ZEND_ASSERT(end_handler[registered_observers - 1] == NULL);
memmove(end_handler + 1, end_handler, registered_observers - 1);
}
*end_handler = end;
}

ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, 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);
}

static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
{
if (!ZEND_OBSERVER_ENABLED) {
Expand Down Expand Up @@ -205,8 +259,7 @@ 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->common.fn_flags)) {
return;
}

Expand Down
8 changes: 7 additions & 1 deletion Zend/zend_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_da
// Call during minit/startup ONLY
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_startup(void); // Called by engine before MINITs
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs
ZEND_API void zend_observer_activate(void);
ZEND_API void zend_observer_deactivate(void);
ZEND_API void zend_observer_shutdown(void);

ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(
Expand Down
36 changes: 32 additions & 4 deletions ext/zend_test/observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,19 +259,47 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
{
zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_SHUTDOWN && (ZT_G(observer_observe_all) || !*p)) {
return FAILURE;
}

zend_string *funcname;
zend_function *func;
if (*p) {
zend_hash_release(*p);
if (EG(function_table)) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
zend_observer_remove_begin_handler(&func->op_array, observer_begin);
zend_observer_remove_end_handler(&func->op_array, observer_end);
}
} ZEND_HASH_FOREACH_END();
}
if (stage == PHP_INI_STAGE_STARTUP || stage == PHP_INI_STAGE_SHUTDOWN) {
zend_hash_release(*p);
*p = NULL;
} else {
zend_hash_clean(*p);
}
}
*p = NULL;
if (new_value && ZSTR_LEN(new_value)) {
*p = malloc(sizeof(HashTable));
if (!*p) {
*p = malloc(sizeof(HashTable));
}
_zend_hash_init(*p, 8, ZVAL_PTR_DTOR, 1);
const char *start = ZSTR_VAL(new_value), *ptr;
while ((ptr = strchr(start, ','))) {
zend_hash_str_add_empty_element(*p, start, ptr - start);
start = ptr + 1;
}
zend_hash_str_add_empty_element(*p, start, ZSTR_VAL(new_value) + ZSTR_LEN(new_value) - start);
if (EG(function_table)) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
zend_observer_add_begin_handler(&func->op_array, observer_begin);
zend_observer_add_end_handler(&func->op_array, observer_end);
}
} ZEND_HASH_FOREACH_END();
}
}
return SUCCESS;
}
Expand All @@ -282,7 +310,7 @@ PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_ENTRY("zend_test.observer.observe_function_names", "", PHP_INI_SYSTEM, zend_test_observer_OnUpdateCommaList, observer_observe_function_names, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_ENTRY("zend_test.observer.observe_function_names", "", PHP_INI_ALL, zend_test_observer_OnUpdateCommaList, observer_observe_function_names, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_type", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_type, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_value", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_value, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observer.show_init_backtrace", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_init_backtrace, zend_zend_test_globals, zend_test_globals)
Expand Down
40 changes: 40 additions & 0 deletions ext/zend_test/tests/observer_basic_06.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
Observer: Basic observability of functions only (with run-time swapping)
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.observe_function_names=foo
--FILE--
<?php
function foo()
{
echo 'Foo' . PHP_EOL;
}

function bar()
{
echo 'Bar' . PHP_EOL;
}

foo();
bar();

ini_set("zend_test.observer.observe_function_names", "bar");

foo();
bar();

?>
--EXPECTF--
<!-- init '%s%eobserver_basic_06.php' -->
<!-- init foo() -->
<foo>
Foo
</foo>
<!-- init bar() -->
Bar
Foo
<bar>
Bar
</bar>