xref: /PHP-8.1/ext/zend_test/observer.c (revision 1015f1ff)
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 
24 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
25 
observer_show_opcode_in_user_handler(zend_execute_data * execute_data)26 static int observer_show_opcode_in_user_handler(zend_execute_data *execute_data)
27 {
28 	if (ZT_G(observer_show_output)) {
29 		php_printf("%*s<!-- opcode: '%s' in user handler -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
30 	}
31 
32 	return ZEND_USER_OPCODE_DISPATCH;
33 }
34 
observer_set_user_opcode_handler(const char * opcode_names,user_opcode_handler_t handler)35 static void observer_set_user_opcode_handler(const char *opcode_names, user_opcode_handler_t handler)
36 {
37 	const char *s = NULL, *e = opcode_names;
38 
39 	while (1) {
40 		if (*e == ' ' || *e == ',' || *e == '\0') {
41 			if (s) {
42 				zend_uchar opcode = zend_get_opcode_id(s, e - s);
43 				if (opcode <= ZEND_VM_LAST_OPCODE) {
44 					zend_set_user_opcode_handler(opcode, handler);
45 				} else {
46 					zend_error(E_WARNING, "Invalid opcode name %.*s", (int) (e - s), e);
47 				}
48 				s = NULL;
49 			}
50 		} else {
51 			if (!s) {
52 				s = e;
53 			}
54 		}
55 		if (*e == '\0') {
56 			break;
57 		}
58 		e++;
59 	}
60 }
61 
observer_show_opcode(zend_execute_data * execute_data)62 static void observer_show_opcode(zend_execute_data *execute_data)
63 {
64 	if (!ZT_G(observer_show_opcode)) {
65 		return;
66 	}
67 	php_printf("%*s<!-- opcode: '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
68 }
69 
observer_begin(zend_execute_data * execute_data)70 static void observer_begin(zend_execute_data *execute_data)
71 {
72 	if (!ZT_G(observer_show_output)) {
73 		return;
74 	}
75 
76 	if (execute_data->func && execute_data->func->common.function_name) {
77 		if (execute_data->func->common.scope) {
78 			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));
79 		} else {
80 			php_printf("%*s<%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name));
81 		}
82 	} else {
83 		php_printf("%*s<file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
84 	}
85 	ZT_G(observer_nesting_depth)++;
86 	observer_show_opcode(execute_data);
87 }
88 
get_retval_info(zval * retval,smart_str * buf)89 static void get_retval_info(zval *retval, smart_str *buf)
90 {
91 	if (!ZT_G(observer_show_return_type) && !ZT_G(observer_show_return_value)) {
92 		return;
93 	}
94 
95 	smart_str_appendc(buf, ':');
96 	if (retval == NULL) {
97 		smart_str_appendl(buf, "NULL", 4);
98 	} else if (ZT_G(observer_show_return_value)) {
99 		if (Z_TYPE_P(retval) == IS_OBJECT) {
100 			smart_str_appendl(buf, "object(", 7);
101 			smart_str_append(buf, Z_OBJCE_P(retval)->name);
102 			smart_str_appendl(buf, ")#", 2);
103 			smart_str_append_long(buf, Z_OBJ_HANDLE_P(retval));
104 		} else {
105 			php_var_export_ex(retval, 2 * ZT_G(observer_nesting_depth) + 3, buf);
106 		}
107 	} else if (ZT_G(observer_show_return_type)) {
108 		smart_str_appends(buf, zend_zval_type_name(retval));
109 	}
110 	smart_str_0(buf);
111 }
112 
observer_end(zend_execute_data * execute_data,zval * retval)113 static void observer_end(zend_execute_data *execute_data, zval *retval)
114 {
115 	if (!ZT_G(observer_show_output)) {
116 		return;
117 	}
118 
119 	if (EG(exception)) {
120 		php_printf("%*s<!-- Exception: %s -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name));
121 	}
122 	observer_show_opcode(execute_data);
123 	ZT_G(observer_nesting_depth)--;
124 	if (execute_data->func && execute_data->func->common.function_name) {
125 		smart_str retval_info = {0};
126 		get_retval_info(retval, &retval_info);
127 		if (execute_data->func->common.scope) {
128 			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) : "");
129 		} else {
130 			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) : "");
131 		}
132 		smart_str_free(&retval_info);
133 	} else {
134 		php_printf("%*s</file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
135 	}
136 }
137 
observer_show_init(zend_function * fbc)138 static void observer_show_init(zend_function *fbc)
139 {
140 	if (fbc->common.function_name) {
141 		if (fbc->common.scope) {
142 			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));
143 		} else {
144 			php_printf("%*s<!-- init %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
145 		}
146 	} else {
147 		php_printf("%*s<!-- init '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
148 	}
149 }
150 
observer_show_init_backtrace(zend_execute_data * execute_data)151 static void observer_show_init_backtrace(zend_execute_data *execute_data)
152 {
153 	zend_execute_data *ex = execute_data;
154 	php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
155 	do {
156 		zend_function *fbc = ex->func;
157 		int indent = 2 * ZT_G(observer_nesting_depth) + 4;
158 		if (fbc->common.function_name) {
159 			if (fbc->common.scope) {
160 				php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
161 			} else {
162 				php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
163 			}
164 		} else {
165 			php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
166 		}
167 	} while ((ex = ex->prev_execute_data) != NULL);
168 	php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
169 }
170 
observer_fcall_init(zend_execute_data * execute_data)171 static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
172 {
173 	zend_function *fbc = execute_data->func;
174 	if (ZT_G(observer_show_output)) {
175 		observer_show_init(fbc);
176 		if (ZT_G(observer_show_init_backtrace)) {
177 			observer_show_init_backtrace(execute_data);
178 		}
179 		observer_show_opcode(execute_data);
180 	}
181 
182 	if (ZT_G(observer_observe_all)) {
183 		return (zend_observer_fcall_handlers){observer_begin, observer_end};
184 	} else if (fbc->common.function_name) {
185 		if (ZT_G(observer_observe_functions)) {
186 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
187 		} else if (ZT_G(observer_observe_function_names) && zend_hash_exists(ZT_G(observer_observe_function_names), fbc->common.function_name)) {
188 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
189 		}
190 	} else {
191 		if (ZT_G(observer_observe_includes)) {
192 			return (zend_observer_fcall_handlers){observer_begin, observer_end};
193 		}
194 	}
195 	return (zend_observer_fcall_handlers){NULL, NULL};
196 }
197 
fiber_init_observer(zend_fiber_context * initializing)198 static void fiber_init_observer(zend_fiber_context *initializing) {
199 	if (ZT_G(observer_fiber_init)) {
200 		php_printf("<!-- alloc: %p -->\n", initializing);
201 	}
202 }
203 
fiber_destroy_observer(zend_fiber_context * destroying)204 static void fiber_destroy_observer(zend_fiber_context *destroying) {
205 	if (ZT_G(observer_fiber_destroy)) {
206 		php_printf("<!-- destroy: %p -->\n", destroying);
207 	}
208 }
209 
fiber_address_observer(zend_fiber_context * from,zend_fiber_context * to)210 static void fiber_address_observer(zend_fiber_context *from, zend_fiber_context *to)
211 {
212 	if (ZT_G(observer_fiber_switch)) {
213 		php_printf("<!-- switching from fiber %p to %p -->\n", from, to);
214 	}
215 }
216 
fiber_enter_observer(zend_fiber_context * from,zend_fiber_context * to)217 static void fiber_enter_observer(zend_fiber_context *from, zend_fiber_context *to)
218 {
219 	if (ZT_G(observer_fiber_switch)) {
220 		if (to->status == ZEND_FIBER_STATUS_INIT) {
221 			php_printf("<init '%p'>\n", to);
222 		} else if (to->kind == zend_ce_fiber) {
223 			zend_fiber *fiber = zend_fiber_from_context(to);
224 			if (fiber->caller != from) {
225 				return;
226 			}
227 
228 			if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
229 				php_printf("<destroying '%p'>\n", to);
230 			} else if (to->status != ZEND_FIBER_STATUS_DEAD) {
231 				php_printf("<resume '%p'>\n", to);
232 			}
233 		}
234 	}
235 }
236 
fiber_suspend_observer(zend_fiber_context * from,zend_fiber_context * to)237 static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context *to)
238 {
239 	if (ZT_G(observer_fiber_switch)) {
240 		if (from->status == ZEND_FIBER_STATUS_DEAD) {
241 			zend_fiber *fiber = (from->kind == zend_ce_fiber) ? zend_fiber_from_context(from) : NULL;
242 
243 			if (fiber && fiber->flags & ZEND_FIBER_FLAG_THREW) {
244 				php_printf("<threw '%p'>\n", from);
245 			} else if (fiber && fiber->flags & ZEND_FIBER_FLAG_DESTROYED) {
246 				php_printf("<destroyed '%p'>\n", from);
247 			} else {
248 				php_printf("<returned '%p'>\n", from);
249 			}
250 		} else if (from->kind == zend_ce_fiber) {
251 			zend_fiber *fiber = zend_fiber_from_context(from);
252 			if (fiber->caller == NULL) {
253 				php_printf("<suspend '%p'>\n", from);
254 			}
255 		}
256 	}
257 }
258 
259 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)260 static void zend_test_execute_internal(zend_execute_data *execute_data, zval *return_value) {
261 	zend_function *fbc = execute_data->func;
262 
263 	if (fbc->common.function_name) {
264 		if (fbc->common.scope) {
265 			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));
266 		} else {
267 			php_printf("%*s<!-- internal enter %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
268 		}
269 	} else {
270 		php_printf("%*s<!-- internal enter '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
271 	}
272 
273 	if (zend_test_prev_execute_internal) {
274 		zend_test_prev_execute_internal(execute_data, return_value);
275 	} else {
276 		fbc->internal_function.handler(execute_data, return_value);
277 	}
278 }
279 
ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)280 static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
281 {
282 	zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
283 	if (*p) {
284 		zend_hash_release(*p);
285 	}
286 	*p = NULL;
287 	if (new_value && ZSTR_LEN(new_value)) {
288 		*p = malloc(sizeof(HashTable));
289 		_zend_hash_init(*p, 8, ZVAL_PTR_DTOR, 1);
290 		const char *start = ZSTR_VAL(new_value), *ptr;
291 		while ((ptr = strchr(start, ','))) {
292 			zend_hash_str_add_empty_element(*p, start, ptr - start);
293 			start = ptr + 1;
294 		}
295 		zend_hash_str_add_empty_element(*p, start, ZSTR_VAL(new_value) + ZSTR_LEN(new_value) - start);
296 	}
297 	return SUCCESS;
298 }
299 
300 PHP_INI_BEGIN()
301 	STD_PHP_INI_BOOLEAN("zend_test.observer.enabled", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_enabled, zend_zend_test_globals, zend_test_globals)
302 	STD_PHP_INI_BOOLEAN("zend_test.observer.show_output", "1", PHP_INI_SYSTEM, OnUpdateBool, observer_show_output, zend_zend_test_globals, zend_test_globals)
303 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
304 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
305 	STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
306 	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)
307 	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)
308 	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)
309 	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)
310 	STD_PHP_INI_BOOLEAN("zend_test.observer.show_opcode", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_opcode, zend_zend_test_globals, zend_test_globals)
311 	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)
312 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_init", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_init, zend_zend_test_globals, zend_test_globals)
313 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_switch", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_switch, zend_zend_test_globals, zend_test_globals)
314 	STD_PHP_INI_BOOLEAN("zend_test.observer.fiber_destroy", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_fiber_destroy, zend_zend_test_globals, zend_test_globals)
315 	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()316 PHP_INI_END()
317 
318 void zend_test_observer_init(INIT_FUNC_ARGS)
319 {
320 	// Loading via dl() not supported with the observer API
321 	if (type != MODULE_TEMPORARY) {
322 		REGISTER_INI_ENTRIES();
323 		if (ZT_G(observer_enabled)) {
324 			zend_observer_fcall_register(observer_fcall_init);
325 		}
326 	} else {
327 		(void)ini_entries;
328 	}
329 
330 	if (ZT_G(observer_enabled) && ZT_G(observer_show_opcode_in_user_handler)) {
331 		observer_set_user_opcode_handler(ZT_G(observer_show_opcode_in_user_handler), observer_show_opcode_in_user_handler);
332 	}
333 
334 	if (ZT_G(observer_enabled)) {
335 		zend_observer_fiber_init_register(fiber_init_observer);
336 		zend_observer_fiber_switch_register(fiber_address_observer);
337 		zend_observer_fiber_switch_register(fiber_enter_observer);
338 		zend_observer_fiber_switch_register(fiber_suspend_observer);
339 		zend_observer_fiber_destroy_register(fiber_destroy_observer);
340 	}
341 
342 	if (ZT_G(observer_execute_internal)) {
343 		zend_test_prev_execute_internal = zend_execute_internal;
344 		zend_execute_internal = zend_test_execute_internal;
345 	}
346 }
347 
zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)348 void zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS)
349 {
350 	if (type != MODULE_TEMPORARY) {
351 		UNREGISTER_INI_ENTRIES();
352 	}
353 
354 	if (ZT_G(observer_observe_function_names)) {
355 		zend_hash_release(ZT_G(observer_observe_function_names));
356 	}
357 }
358