Skip to content

Commit 9e2de4c

Browse files
committed
Add an API to manipulate observers at runtime
Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
1 parent f957e3e commit 9e2de4c

File tree

4 files changed

+141
-14
lines changed

4 files changed

+141
-14
lines changed

Zend/zend_observer.c

+62-9
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,6 @@ ZEND_API void zend_observer_activate(void)
8787
current_observed_frame = NULL;
8888
}
8989

90-
ZEND_API void zend_observer_deactivate(void)
91-
{
92-
// now empty and unused, but kept for ABI compatibility
93-
}
94-
9590
ZEND_API void zend_observer_shutdown(void)
9691
{
9792
zend_llist_destroy(&zend_observers_fcall_list);
@@ -112,7 +107,7 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
112107
ZEND_ASSERT(RUN_TIME_CACHE(op_array));
113108
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
114109
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
115-
110+
116111
*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
117112
*end_handlers = ZEND_OBSERVER_NOT_OBSERVED;
118113

@@ -127,7 +122,7 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
127122
*(end_handlers++) = handlers.end;
128123
}
129124
}
130-
125+
131126
// end handlers are executed in reverse order
132127
for (--end_handlers; end_handlers_start < end_handlers; --end_handlers, ++end_handlers_start) {
133128
zend_observer_fcall_end_handler tmp = *end_handlers;
@@ -136,6 +131,65 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
136131
}
137132
}
138133

134+
static bool zend_observer_remove_handler(void **first_handler, void *old_handler) {
135+
size_t registered_observers = zend_observers_fcall_list.count;
136+
137+
void **last_handler = first_handler + registered_observers - 1;
138+
for (void **cur_handler = first_handler; cur_handler <= last_handler; ++cur_handler) {
139+
if (*cur_handler == old_handler) {
140+
if (registered_observers == 1 || (cur_handler == first_handler && cur_handler[1] == NULL)) {
141+
*cur_handler = ZEND_OBSERVER_NOT_OBSERVED;
142+
} else {
143+
if (cur_handler != last_handler) {
144+
memmove(cur_handler, cur_handler + 1, sizeof(cur_handler) * (last_handler - cur_handler));
145+
} else {
146+
*last_handler = NULL;
147+
}
148+
}
149+
return true;
150+
}
151+
}
152+
return false;
153+
}
154+
155+
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
156+
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;
158+
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
159+
*first_handler = begin;
160+
} else {
161+
for (zend_observer_fcall_begin_handler *cur_handler = first_handler + 1; cur_handler <= last_handler; ++cur_handler) {
162+
if (*cur_handler == NULL) {
163+
*cur_handler = begin;
164+
return;
165+
}
166+
}
167+
// there's no space for new handlers, then it's forbidden to call this function
168+
ZEND_UNREACHABLE();
169+
}
170+
}
171+
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);
174+
}
175+
176+
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
177+
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;
179+
// to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front
180+
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
181+
// there's no space for new handlers, then it's forbidden to call this function
182+
ZEND_ASSERT(end_handler[registered_observers - 1] == NULL);
183+
memmove(end_handler + 1, end_handler, registered_observers - 1);
184+
}
185+
*end_handler = end;
186+
}
187+
188+
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
189+
size_t registered_observers = zend_observers_fcall_list.count;
190+
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array) + registered_observers, end);
191+
}
192+
139193
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
140194
{
141195
if (!ZEND_OBSERVER_ENABLED) {
@@ -205,8 +259,7 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_d
205259
{
206260
zend_function *func = execute_data->func;
207261

208-
if (!ZEND_OBSERVER_ENABLED
209-
|| !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
262+
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
210263
return;
211264
}
212265

Zend/zend_observer.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,16 @@ typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_da
5656
// Call during minit/startup ONLY
5757
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);
5858

59+
// Call during runtime, but only if you have used zend_observer_fcall_register.
60+
// 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);
65+
5966
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
6067
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs
6168
ZEND_API void zend_observer_activate(void);
62-
ZEND_API void zend_observer_deactivate(void);
6369
ZEND_API void zend_observer_shutdown(void);
6470

6571
ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(

ext/zend_test/observer.c

+32-4
Original file line numberDiff line numberDiff line change
@@ -259,19 +259,47 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
259259
static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
260260
{
261261
zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
262+
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_SHUTDOWN && (ZT_G(observer_observe_all) || !*p)) {
263+
return FAILURE;
264+
}
265+
266+
zend_string *funcname;
267+
zend_function *func;
262268
if (*p) {
263-
zend_hash_release(*p);
269+
if (EG(function_table)) {
270+
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
271+
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
272+
zend_observer_remove_begin_handler(&func->op_array, observer_begin);
273+
zend_observer_remove_end_handler(&func->op_array, observer_end);
274+
}
275+
} ZEND_HASH_FOREACH_END();
276+
}
277+
if (stage == PHP_INI_STAGE_STARTUP || stage == PHP_INI_STAGE_SHUTDOWN) {
278+
zend_hash_release(*p);
279+
*p = NULL;
280+
} else {
281+
zend_hash_clean(*p);
282+
}
264283
}
265-
*p = NULL;
266284
if (new_value && ZSTR_LEN(new_value)) {
267-
*p = malloc(sizeof(HashTable));
285+
if (!*p) {
286+
*p = malloc(sizeof(HashTable));
287+
}
268288
_zend_hash_init(*p, 8, ZVAL_PTR_DTOR, 1);
269289
const char *start = ZSTR_VAL(new_value), *ptr;
270290
while ((ptr = strchr(start, ','))) {
271291
zend_hash_str_add_empty_element(*p, start, ptr - start);
272292
start = ptr + 1;
273293
}
274294
zend_hash_str_add_empty_element(*p, start, ZSTR_VAL(new_value) + ZSTR_LEN(new_value) - start);
295+
if (EG(function_table)) {
296+
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
297+
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
298+
zend_observer_add_begin_handler(&func->op_array, observer_begin);
299+
zend_observer_add_end_handler(&func->op_array, observer_end);
300+
}
301+
} ZEND_HASH_FOREACH_END();
302+
}
275303
}
276304
return SUCCESS;
277305
}
@@ -282,7 +310,7 @@ PHP_INI_BEGIN()
282310
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
283311
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
284312
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
285-
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)
313+
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)
286314
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)
287315
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)
288316
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)
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Observer: Basic observability of functions only (with run-time swapping)
3+
--EXTENSIONS--
4+
zend_test
5+
--INI--
6+
zend_test.observer.enabled=1
7+
zend_test.observer.observe_function_names=foo
8+
--FILE--
9+
<?php
10+
function foo()
11+
{
12+
echo 'Foo' . PHP_EOL;
13+
}
14+
15+
function bar()
16+
{
17+
echo 'Bar' . PHP_EOL;
18+
}
19+
20+
foo();
21+
bar();
22+
23+
ini_set("zend_test.observer.observe_function_names", "bar");
24+
25+
foo();
26+
bar();
27+
28+
?>
29+
--EXPECTF--
30+
<!-- init '%s%eobserver_basic_06.php' -->
31+
<!-- init foo() -->
32+
<foo>
33+
Foo
34+
</foo>
35+
<!-- init bar() -->
36+
Bar
37+
Foo
38+
<bar>
39+
Bar
40+
</bar>

0 commit comments

Comments
 (0)