Skip to content

Commit a22a872

Browse files
authored
Add next handler parameter to zend_observer_remove_begin/end_handler (php#13807)
The usage of the current API within an observer handler leads to bugs like https://bugs.xdebug.org/view.php?id=2232. Given two observer handlers, next to each other. The first one is executed. It removes itself. The second observer handler gets moved to the place of the first. The first one returns. The handler in the second slot is fetched, but is now NULL, because the it's now in the slot of the first observer; i.e. the second handler is skipped and the begin/end symmetry guarantee is violated. Providing the next handler to the caller is a zero-cost way to avoid any impact in the paths of zend_observe_fcall_begin/end. Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
1 parent a0da32a commit a22a872

File tree

4 files changed

+21
-9
lines changed

4 files changed

+21
-9
lines changed

UPGRADING.INTERNALS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ PHP 8.4 INTERNALS UPGRADE NOTES
7777
void(*)(void *, size_t) to
7878
void(*)(void *, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
7979

80+
* zend_observer_remove_begin_handler() and zend_observer_remove_end_handler()
81+
got each a new parameter returning an observer which must be called, if the
82+
removal happened during observer execution.
83+
8084

8185
========================
8286
2. Build system changes

Zend/zend_observer.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,26 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
147147
}
148148
}
149149

150-
static bool zend_observer_remove_handler(void **first_handler, void *old_handler) {
150+
/* We need to provide the ability to retrieve the handler which will move onto the position the current handler was.
151+
* The fundamental problem is that, if a handler is removed while it's being executed, it will move handlers around:
152+
* the previous next handler is now at the place where the current handler was.
153+
* Hence, the next handler executed will be the one after the next handler.
154+
* Callees must thus invoke the next handler themselves, with the same arguments they were passed. */
155+
static bool zend_observer_remove_handler(void **first_handler, void *old_handler, void **next_handler) {
151156
size_t registered_observers = zend_observers_fcall_list.count;
152157

153158
void **last_handler = first_handler + registered_observers - 1;
154159
for (void **cur_handler = first_handler; cur_handler <= last_handler; ++cur_handler) {
155160
if (*cur_handler == old_handler) {
156161
if (registered_observers == 1 || (cur_handler == first_handler && cur_handler[1] == NULL)) {
157162
*cur_handler = ZEND_OBSERVER_NOT_OBSERVED;
163+
*next_handler = NULL;
158164
} else {
159165
if (cur_handler != last_handler) {
160166
memmove(cur_handler, cur_handler + 1, sizeof(cur_handler) * (last_handler - cur_handler));
161167
}
162168
*last_handler = NULL;
169+
*next_handler = *cur_handler;
163170
}
164171
return true;
165172
}
@@ -184,8 +191,8 @@ ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_obse
184191
}
185192
}
186193

187-
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
188-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
194+
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin, zend_observer_fcall_begin_handler *next) {
195+
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin, (void **)next);
189196
}
190197

191198
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
@@ -200,9 +207,9 @@ ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observ
200207
*end_handler = end;
201208
}
202209

203-
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
210+
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end, zend_observer_fcall_end_handler *next) {
204211
size_t registered_observers = zend_observers_fcall_list.count;
205-
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
212+
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end, (void **)next);
206213
}
207214

208215
static inline zend_execute_data **prev_observed_frame(zend_execute_data *execute_data) {

Zend/zend_observer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);
6262
// Call during runtime, but only if you have used zend_observer_fcall_register.
6363
// 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.
6464
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
65-
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
65+
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin, zend_observer_fcall_begin_handler *next);
6666
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
67-
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
67+
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end, zend_observer_fcall_end_handler *next);
6868

6969
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
7070
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs

ext/zend_test/observer.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,9 @@ static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
297297
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
298298
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
299299
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
300-
zend_observer_remove_begin_handler(func, observer_begin);
301-
zend_observer_remove_end_handler(func, observer_end);
300+
void *old_handler;
301+
zend_observer_remove_begin_handler(func, observer_begin, (zend_observer_fcall_begin_handler *)&old_handler);
302+
zend_observer_remove_end_handler(func, observer_end, (zend_observer_fcall_end_handler *)&old_handler);
302303
}
303304
} ZEND_HASH_FOREACH_END();
304305
}

0 commit comments

Comments
 (0)