xref: /PHP-8.4/ext/zend_test/observer.c (revision 8d30ed4f)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author:                                                              |
14   +----------------------------------------------------------------------+
15 */
16 
17 #include "php.h"
18 #include "php_test.h"
19 #include "observer.h"
20 #include "zend_observer.h"
21 #include "zend_smart_str.h"
22 #include "ext/standard/php_var.h"
23 #include "zend_generators.h"
24 
25 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
26 
observer_show_opcode_in_user_handler(zend_execute_data * execute_data)27 static int observer_show_opcode_in_user_handler(zend_execute_data *execute_data)
28 {
29 	if (ZT_G(observer_show_output)) {
30 		php_printf("%*s<!-- opcode: '%s' in user handler -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
31 	}
32 
33 	return ZEND_USER_OPCODE_DISPATCH;
34 }
35 
observer_set_user_opcode_handler(const char * opcode_names,user_opcode_handler_t handler)36 static void observer_set_user_opcode_handler(const char *opcode_names, user_opcode_handler_t handler)
37 {
38 	const char *s = NULL, *e = opcode_names;
39 
40 	while (1) {
41 		if (*e == ' ' || *e == ',' || *e == '\0') {
42 			if (s) {
43 				uint8_t opcode = zend_get_opcode_id(s, e - s);
44 				if (opcode <= ZEND_VM_LAST_OPCODE) {
45 					zend_set_user_opcode_handler(opcode, handler);
46 				} else {
47 					zend_error(E_WARNING, "Invalid opcode name %.*s", (int) (e - s), e);
48 				}
49 				s = NULL;
50 			}
51 		} else {
52 			if (!s) {
53 				s = e;
54 			}
55 		}
56 		if (*e == '\0') {
57 			break;
58 		}
59 		e++;
60 	}
61 }
62 
observer_show_opcode(zend_execute_data * execute_data)63 static void observer_show_opcode(zend_execute_data *execute_data)
64 {
65 	if (!ZT_G(observer_show_opcode) || !ZEND_USER_CODE(EX(func)->type)) {
66 		return;
67 	}
68 	php_printf("%*s<!-- opcode: '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
69 }
70 
assert_observer_opline(zend_execute_data * execute_data)71 static inline void assert_observer_opline(zend_execute_data *execute_data) {
72 	ZEND_ASSERT(!ZEND_USER_CODE(EX(func)->type) ||
73 		(EX(opline) >= EX(func)->op_array.opcodes && EX(opline) < EX(func)->op_array.opcodes + EX(func)->op_array.last) ||
74 		(EX(opline) >= EG(exception_op) && EX(opline) < EG(exception_op) + 3));
75 }
76 
observer_begin(zend_execute_data * execute_data)77 static void observer_begin(zend_execute_data *execute_data)
78 {
79 	assert_observer_opline(execute_data);
80 
81 	if (!ZT_G(observer_show_output)) {
82 		return;
83 	}
84 
85 	if (execute_data->func && execute_data->func->common.function_name) {
86 		if (execute_data->func->common.scope) {
87 			php_printf("%*s<%s::%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name));
88 		} else {
89 			php_printf("%*s<%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name));
90 		}
91 	} else {
92 		php_printf("%*s<file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
93 	}
94 	ZT_G(observer_nesting_depth)++;
95 	observer_show_opcode(execute_data);
96 }
97 
get_retval_info(zval * retval,smart_str * buf)98 static void get_retval_info(zval *retval, smart_str *buf)
99 {
100 	if (!ZT_G(observer_show_return_type) && !ZT_G(observer_show_return_value)) {
101 		return;
102 	}
103 
104 	smart_str_appendc(buf, ':');
105 	if (retval == NULL) {
106 		smart_str_appendl(buf, "NULL", 4);
107 	} else if (ZT_G(observer_show_return_value)) {
108 		if (Z_TYPE_P(retval) == IS_OBJECT) {
109 			smart_str_appendl(buf, "object(", 7);
110 			smart_str_append(buf, Z_OBJCE_P(retval)->name);
111 			smart_str_appendl(buf, ")#", 2);
112 			smart_str_append_long(buf, Z_OBJ_HANDLE_P(retval));
113 		} else {
114 			php_var_export_ex(retval, 2 * ZT_G(observer_nesting_depth) + 3, buf);
115 		}
116 	} else if (ZT_G(observer_show_return_type)) {
117 		smart_str_appends(buf, zend_zval_type_name(retval));
118 	}
119 	smart_str_0(buf);
120 }
121 
observer_end(zend_execute_data * execute_data,zval * retval)122 static void observer_end(zend_execute_data *execute_data, zval *retval)
123 {
124 	assert_observer_opline(execute_data);
125 
126 	if (!ZT_G(observer_show_output)) {
127 		return;
128 	}
129 
130 	if (EG(exception)) {
131 		php_printf("%*s<!-- Exception: %s -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name));
132 	}
133 	observer_show_opcode(execute_data);
134 	ZT_G(observer_nesting_depth)--;
135 	if (execute_data->func && execute_data->func->common.function_name) {
136 		smart_str retval_info = {0};
137 		get_retval_info(retval, &retval_info);
138 		if (execute_data->func->common.scope) {
139 			php_printf("%*s</%s::%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
140 		} else {
141 			php_printf("%*s</%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
142 		}
143 		smart_str_free(&retval_info);
144 	} else {
145 		php_printf("%*s</file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
146 	}
147 }
148 
observer_show_init(zend_function * fbc)149 static void observer_show_init(zend_function *fbc)
150 {
151 	if (fbc->common.function_name) {
152 		if (fbc->common.scope) {
153 			php_printf("%*s<!-- init %s::%s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
154 		} else {
155 			php_printf("%*s<!-- init %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
156 		}
157 	} else {
158 		php_printf("%*s<!-- init '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
159 	}
160 }
161 
observer_show_init_backtrace(zend_execute_data * execute_data)162 static void observer_show_init_backtrace(zend_execute_data *execute_data)
163 {
164 	zend_execute_data *ex = execute_data;
165 	php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
166 	do {
167 		if (UNEXPECTED(!ex->func)) {
168 			ex = zend_generator_check_placeholder_frame(ex);
169 			ZEND_ASSERT(ex->func);
170 		}
171 
172 		zend_function *fbc = ex->func;
173 		int indent = 2 * ZT_G(observer_nesting_depth) + 4;
174 		if (fbc->common.function_name) {
175 			if (fbc->common.scope) {
176 				php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
177 			} else {
178 				php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
179 			}
180 		} else {
181 			php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
182 		}
183 	} while ((ex = ex->prev_execute_data) != NULL);
184 	php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
185 }
186 
observer_fcall_init(zend_execute_data * execute_data)187 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
188 {
189 	zend_function *fbc = execute_data->func;
190 	if (ZT_G(observer_show_output)) {
191 		observer_show_init(fbc);
192 		if (ZT_G(observer_show_init_backtrace)) {
193 			observer_show_init_backtrace(execute_data);
194 		}
195 		observer_show_opcode(execute_data);
196 	}
197 
198 	if (ZT_G(observer_observe_all)) {
199 		return (zend_observer_fcall_handlers){observer_begin, observer_end};
200 	} else if (fbc->common.function_name) {
201 		if (ZT_G(observer_observe_functions)) {
202 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
203 		} else if (zend_hash_exists(ZT_G(observer_observe_function_names), fbc->common.function_name)) {
204 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
205 		}
206 	} else {
207 		if (ZT_G(observer_observe_includes)) {
208 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
209 		}
210 	}
211 	return (zend_observer_fcall_handlers){NULL, NULL};
212 }
213 
fiber_init_observer(zend_fiber_context * initializing)214 static void fiber_init_observer(zend_fiber_context *initializing) {
215 	if (ZT_G(observer_fiber_init)) {
216 		php_printf("<!-- alloc: %p -->\n", initializing);
217 	}
218 }
219 
fiber_destroy_observer(zend_fiber_context * destroying)220 static void fiber_destroy_observer(zend_fiber_context *destroying) {
221 	if (ZT_G(observer_fiber_destroy)) {
222 		php_printf("<!-- destroy: %p -->\n", destroying);
223 	}
224 }
225 
fiber_address_observer(zend_fiber_context * from,zend_fiber_context * to)226 static void fiber_address_observer(zend_fiber_context *from, zend_fiber_context *to)
227 {
228 	if (ZT_G(observer_fiber_switch)) {
229 		php_printf("<!-- switching from fiber %p to %p -->\n", from, to);
230 	}
231 }
232 
fiber_enter_observer(zend_fiber_context * from,zend_fiber_context * to)233 static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *to)
234 {
235 	if (ZT_G(observer_fiber_switch)) {
236 		if (to->status == ZEND_FIBER_STATUS_INIT) {
237 			php_printf("<init '%p'>\n", to);
238 		} else if (to->kind == zend_ce_fiber) {
239 			zend_fiber *fiber = zend_fiber_from_context(to);
240 			if (fiber->caller != from) {
241 				return;
242 			}
243 
244 			if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
245 				php_printf("<destroying '%p'>\n", to);
246 			} else if (to->status != ZEND_FIBER_STATUS_DEAD) {
247 				php_printf("<resume '%p'>\n", to);
248 			}
249 		}
250 	}
251 }
252 
fiber_suspend_observer(zend_fiber_context * from,zend_fiber_context * to)253 static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context *to)
254 {
255 	if (ZT_G(observer_fiber_switch)) {
256 		if (from->status == ZEND_FIBER_STATUS_DEAD) {
257 			zend_fiber *fiber = (from->kind == zend_ce_fiber) ? zend_fiber_from_context(from) : NULL;
258 
259 			if (fiber && fiber->flags & ZEND_FIBER_FLAG_THREW) {
260 				php_printf("<threw '%p'>\n", from);
261 			} else if (fiber && fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
262 				php_printf("<destroyed '%p'>\n", from);
263 			} else {
264 				php_printf("<returned '%p'>\n", from);
265 			}
266 		} else if (from->kind == zend_ce_fiber) {
267 			zend_fiber *fiber = zend_fiber_from_context(from);
268 			if (fiber->caller == NULL) {
269 				php_printf("<suspend '%p'>\n", from);
270 			}
271 		}
272 	}
273 }
274 
declared_function_observer(zend_op_array * op_array,zend_string * name)275 void declared_function_observer(zend_op_array *op_array, zend_string *name) {
276 	if (ZT_G(observer_observe_declaring)) {
277 		php_printf("%*s<!-- declared function '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
278 	}
279 }
280 
declared_class_observer(zend_class_entry * ce,zend_string * name)281 void declared_class_observer(zend_class_entry *ce, zend_string *name) {
282 	if (ZT_G(observer_observe_declaring)) {
283 		php_printf("%*s<!-- declared class '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
284 	}
285 }
286 
287 static void (*zend_test_prev_execute_internal)(zend_execute_data *execute_data, zval *return_value);
zend_test_execute_internal(zend_execute_data * execute_data,zval * return_value)288 static void zend_test_execute_internal(zend_execute_data *execute_data, zval *return_value) {
289 	zend_function *fbc = execute_data->func;
290 
291 	if (fbc->common.function_name) {
292 		if (fbc->common.scope) {
293 			php_printf("%*s<!-- internal enter %s::%s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
294 		} else {
295 			php_printf("%*s<!-- internal enter %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
296 		}
297 	} else if (ZEND_USER_CODE(fbc->type)) {
298 		php_printf("%*s<!-- internal enter '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
299 	}
300 
301 	if (zend_test_prev_execute_internal) {
302 		zend_test_prev_execute_internal(execute_data, return_value);
303 	} else {
304 		fbc->internal_function.handler(execute_data, return_value);
305 	}
306 }
307 
ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)308 static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
309 {
310 	zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
311 	zend_string *funcname;
312 	zend_function *func;
313 	if (!ZT_G(observer_enabled)) {
314 		return FAILURE;
315 	}
316 	if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
317 		ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
318 			if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
319 				void *old_handler;
320 				zend_observer_remove_begin_handler(func, observer_begin, (zend_observer_fcall_begin_handler *)&old_handler);
321 				zend_observer_remove_end_handler(func, observer_end, (zend_observer_fcall_end_handler *)&old_handler);
322 			}
323 		} ZEND_HASH_FOREACH_END();
324 	}
325 	zend_hash_clean(*p);
326 	if (new_value && ZSTR_LEN(new_value)) {
327 		const char *start = ZSTR_VAL(new_value), *ptr;
328 		while ((ptr = strchr(start, ','))) {
329 			zend_string *str = zend_string_init(start, ptr - start, 1);
330 			GC_MAKE_PERSISTENT_LOCAL(str);
331 			zend_hash_add_empty_element(*p, str);
332 			zend_string_release(str);
333 			start = ptr + 1;
334 		}
335 		zend_string *str = zend_string_init(start, ZSTR_VAL(new_value) + ZSTR_LEN(new_value) - start, 1);
336 		GC_MAKE_PERSISTENT_LOCAL(str);
337 		zend_hash_add_empty_element(*p, str);
338 		zend_string_release(str);
339 		if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
340 			ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
341 				if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
342 					zend_observer_add_begin_handler(func, observer_begin);
343 					zend_observer_add_end_handler(func, observer_end);
344 				}
345 			} ZEND_HASH_FOREACH_END();
346 		}
347 	}
348 	return SUCCESS;
349 }
350 
351 PHP_INI_BEGIN()
352 	STD_PHP_INI_BOOLEAN("zend_test.observer.enabled", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_enabled, zend_zend_test_globals, zend_test_globals)
353 	STD_PHP_INI_BOOLEAN("zend_test.observer.show_output", "1", PHP_INI_SYSTEM, OnUpdateBool, observer_show_output, zend_zend_test_globals, zend_test_globals)
354 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
355 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
356 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
357 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_declaring", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_declaring, zend_zend_test_globals, zend_test_globals)
358 	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)
359 	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)
360 	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)
361 	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)
362 	STD_PHP_INI_BOOLEAN("zend_test.observer.show_opcode", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_opcode, zend_zend_test_globals, zend_test_globals)
363 	STD_PHP_INI_ENTRY("zend_test.observer.show_opcode_in_user_handler", "", PHP_INI_SYSTEM, OnUpdateString, observer_show_opcode_in_user_handler, zend_zend_test_globals, zend_test_globals)
364 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_init", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_init, zend_zend_test_globals, zend_test_globals)
365 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_switch", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_switch, zend_zend_test_globals, zend_test_globals)
366 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_destroy", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_destroy, zend_zend_test_globals, zend_test_globals)
367 	STD_PHP_INI_BOOLEAN("zend_test.observer.execute_internal", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_execute_internal, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()368 PHP_INI_END()
369 
370 void zend_test_observer_init(INIT_FUNC_ARGS)
371 {
372 	// Loading via dl() not supported with the observer API
373 	if (type != MODULE_TEMPORARY) {
374 		REGISTER_INI_ENTRIES();
375 		if (ZT_G(observer_enabled)) {
376 			zend_observer_fcall_register(observer_fcall_init);
377 		}
378 	} else {
379 		(void)ini_entries;
380 	}
381 
382 	if (ZT_G(observer_enabled) && ZT_G(observer_show_opcode_in_user_handler)) {
383 		observer_set_user_opcode_handler(ZT_G(observer_show_opcode_in_user_handler), observer_show_opcode_in_user_handler);
384 	}
385 
386 	if (ZT_G(observer_enabled)) {
387 		zend_observer_fiber_init_register(fiber_init_observer);
388 		zend_observer_fiber_switch_register(fiber_address_observer);
389 		zend_observer_fiber_switch_register(fiber_enter_observer);
390 		zend_observer_fiber_switch_register(fiber_suspend_observer);
391 		zend_observer_fiber_destroy_register(fiber_destroy_observer);
392 
393 		zend_observer_function_declared_register(declared_function_observer);
394 		zend_observer_class_linked_register(declared_class_observer);
395 	}
396 
397 	if (ZT_G(observer_execute_internal)) {
398 		zend_test_prev_execute_internal = zend_execute_internal;
399 		zend_execute_internal = zend_test_execute_internal;
400 	}
401 }
402 
zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)403 void zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)
404 {
405 	if (type != MODULE_TEMPORARY) {
406 		UNREGISTER_INI_ENTRIES();
407 	}
408 }
409 
zend_test_observer_ginit(zend_zend_test_globals * zend_test_globals)410 void zend_test_observer_ginit(zend_zend_test_globals *zend_test_globals) {
411 	zend_test_globals->observer_observe_function_names = malloc(sizeof(HashTable));
412 	_zend_hash_init(zend_test_globals->observer_observe_function_names, 8, ZVAL_PTR_DTOR, 1);
413 	GC_MAKE_PERSISTENT_LOCAL(zend_test_globals->observer_observe_function_names);
414 }
415 
zend_test_observer_gshutdown(zend_zend_test_globals * zend_test_globals)416 void zend_test_observer_gshutdown(zend_zend_test_globals *zend_test_globals) {
417 	zend_hash_release(zend_test_globals->observer_observe_function_names);
418 }
419