xref: /PHP-8.4/Zend/Optimizer/zend_func_info.c (revision eb1cdb5b)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine, Func Info                                               |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | https://www.php.net/license/3_01.txt                                 |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Dmitry Stogov <dmitry@php.net>                              |
16    |          Xinchen Hui <laruence@php.net>                              |
17    +----------------------------------------------------------------------+
18 */
19 
20 #include "zend_compile.h"
21 #include "zend_extensions.h"
22 #include "zend_ssa.h"
23 #include "zend_optimizer_internal.h"
24 #include "zend_inference.h"
25 #include "zend_call_graph.h"
26 #include "zend_func_info.h"
27 #include "zend_inference.h"
28 #ifdef _WIN32
29 #include "win32/ioutil.h"
30 #endif
31 
32 typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa);
33 
34 typedef struct _func_info_t {
35 	const char *name;
36 	unsigned    name_len;
37 	uint32_t    info;
38 	info_func_t info_func;
39 } func_info_t;
40 
41 #define F0(name, info) \
42 	{name, sizeof(name)-1, (info), NULL}
43 #define F1(name, info) \
44 	{name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL}
45 #define FN(name, info) \
46 	{name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL}
47 #define FC(name, callback) \
48 	{name, sizeof(name)-1, 0, callback}
49 
50 #include "zend_func_infos.h"
51 
zend_range_info(const zend_call_info * call_info,const zend_ssa * ssa)52 static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
53 {
54 	ZEND_ASSERT(!call_info->is_frameless);
55 
56 	if (!call_info->send_unpack
57 	 && (call_info->num_args == 2 || call_info->num_args == 3)
58 	 && ssa
59 	 && !(ssa->cfg.flags & ZEND_SSA_TSSA)) {
60 		zend_op_array *op_array = call_info->caller_op_array;
61 		uint32_t t1 = _ssa_op1_info(op_array, ssa, call_info->arg_info[0].opline,
62 			ssa->ops ? &ssa->ops[call_info->arg_info[0].opline - op_array->opcodes] : NULL);
63 		uint32_t t2 = _ssa_op1_info(op_array, ssa, call_info->arg_info[1].opline,
64 			ssa->ops ? &ssa->ops[call_info->arg_info[1].opline - op_array->opcodes] : NULL);
65 		uint32_t t3 = 0;
66 		uint32_t tmp = MAY_BE_RC1 | MAY_BE_ARRAY;
67 
68 		if (call_info->num_args == 3) {
69 			t3 = _ssa_op1_info(op_array, ssa, call_info->arg_info[2].opline,
70 				ssa->ops ? &ssa->ops[call_info->arg_info[2].opline - op_array->opcodes] : NULL);
71 		}
72 		if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) {
73 			tmp |= MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
74 		}
75 		if ((t1 & (MAY_BE_DOUBLE|MAY_BE_STRING))
76 				|| (t2 & (MAY_BE_DOUBLE|MAY_BE_STRING))
77 				|| (t3 & (MAY_BE_DOUBLE|MAY_BE_STRING))) {
78 			tmp |= MAY_BE_ARRAY_OF_DOUBLE;
79 		}
80 		if ((t1 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))
81 				&& (t2 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
82 			tmp |= MAY_BE_ARRAY_OF_LONG;
83 		}
84 		if (tmp & MAY_BE_ARRAY_OF_ANY) {
85 			tmp |= MAY_BE_ARRAY_PACKED;
86 		}
87 		return tmp;
88 	} else {
89 		/* May throw */
90 		return MAY_BE_RC1 | MAY_BE_ARRAY | MAY_BE_ARRAY_EMPTY | MAY_BE_ARRAY_PACKED | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
91 	}
92 }
93 
94 static const func_info_t old_func_infos[] = {
95 	FC("range",                        zend_range_info),
96 };
97 
98 static HashTable func_info;
99 ZEND_API int zend_func_info_rid = -1;
100 
zend_get_internal_func_info(const zend_function * callee_func,const zend_call_info * call_info,const zend_ssa * ssa)101 uint32_t zend_get_internal_func_info(
102 		const zend_function *callee_func, const zend_call_info *call_info, const zend_ssa *ssa) {
103 	if (callee_func->common.scope) {
104 		/* This is a method, not a function. */
105 		return 0;
106 	}
107 
108 	zend_string *name = callee_func->common.function_name;
109 	if (!name) {
110 		/* zend_pass_function has no name. */
111 		return 0;
112 	}
113 
114 	zval *zv = zend_hash_find_known_hash(&func_info, name);
115 	if (!zv) {
116 		return 0;
117 	}
118 
119 	func_info_t *info = Z_PTR_P(zv);
120 	if (info->info_func) {
121 		return call_info ? info->info_func(call_info, ssa) : 0;
122 	} else {
123 		uint32_t ret = info->info;
124 
125 		if (ret & MAY_BE_ARRAY) {
126 			ret |= MAY_BE_ARRAY_EMPTY;
127 		}
128 		return ret;
129 	}
130 }
131 
zend_get_func_info(const zend_call_info * call_info,const zend_ssa * ssa,zend_class_entry ** ce,bool * ce_is_instanceof)132 ZEND_API uint32_t zend_get_func_info(
133 		const zend_call_info *call_info, const zend_ssa *ssa,
134 		zend_class_entry **ce, bool *ce_is_instanceof)
135 {
136 	uint32_t ret = 0;
137 	const zend_function *callee_func = call_info->callee_func;
138 	*ce = NULL;
139 	*ce_is_instanceof = 0;
140 
141 	if (callee_func->type == ZEND_INTERNAL_FUNCTION) {
142 		uint32_t internal_ret = zend_get_internal_func_info(callee_func, call_info, ssa);
143 #if !ZEND_DEBUG
144 		if (internal_ret) {
145 			return internal_ret;
146 		}
147 #endif
148 
149 		ret = zend_get_return_info_from_signature_only(
150 			callee_func, /* script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
151 
152 #if ZEND_DEBUG
153 		if (internal_ret) {
154 			zend_string *name = callee_func->common.function_name;
155 			/* Check whether the func_info information is a subset of the information we can
156 			 * compute from the specified return type, otherwise it contains redundant types. */
157 			if (internal_ret & ~ret) {
158 				fprintf(stderr, "Inaccurate func info for %s()\n", ZSTR_VAL(name));
159 			}
160 			/* Check whether the func info is completely redundant with arginfo. */
161 			if (internal_ret == ret) {
162 				fprintf(stderr, "Useless func info for %s()\n", ZSTR_VAL(name));
163 			}
164 			/* If the return type is not mixed, check that the types match exactly if we exclude
165 			 * RC and array information. */
166 			uint32_t ret_any = ret & MAY_BE_ANY, internal_ret_any = internal_ret & MAY_BE_ANY;
167 			if (ret_any != MAY_BE_ANY) {
168 				uint32_t diff = internal_ret_any ^ ret_any;
169 				/* Func info may contain "true" types as well as isolated "null" and "false". */
170 				if (diff && !(diff == MAY_BE_FALSE && (ret & MAY_BE_FALSE))
171 						&& (internal_ret_any & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
172 					fprintf(stderr, "Incorrect func info for %s()\n", ZSTR_VAL(name));
173 				}
174 			}
175 			return internal_ret;
176 		}
177 #endif
178 	} else {
179 		if (!call_info->is_prototype) {
180 			// FIXME: the order of functions matters!!!
181 			zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
182 			if (info) {
183 				ret = info->return_info.type;
184 				*ce = info->return_info.ce;
185 				*ce_is_instanceof = info->return_info.is_instanceof;
186 			}
187 		}
188 		if (!ret) {
189 			ret = zend_get_return_info_from_signature_only(
190 				callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
191 			/* It's allowed to override a method that return non-reference with a method that returns a reference */
192 			if (call_info->is_prototype && (ret & ~MAY_BE_REF)) {
193 				ret |= MAY_BE_REF;
194 				*ce = NULL;
195 			}
196 		}
197 	}
198 	return ret;
199 }
200 
zend_func_info_add(const func_info_t * func_infos,size_t n)201 static void zend_func_info_add(const func_info_t *func_infos, size_t n)
202 {
203 	for (size_t i = 0; i < n; i++) {
204 		zend_string *key = zend_string_init_interned(func_infos[i].name, func_infos[i].name_len, 1);
205 
206 		if (zend_hash_add_ptr(&func_info, key, (void**)&func_infos[i]) == NULL) {
207 			fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", func_infos[i].name);
208 		}
209 
210 		zend_string_release_ex(key, 1);
211 	}
212 }
213 
zend_func_info_startup(void)214 zend_result zend_func_info_startup(void)
215 {
216 	if (zend_func_info_rid == -1) {
217 		zend_func_info_rid = zend_get_resource_handle("Zend Optimizer");
218 		if (zend_func_info_rid < 0) {
219 			return FAILURE;
220 		}
221 
222 		zend_hash_init(&func_info, sizeof(old_func_infos)/sizeof(func_info_t) + sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1);
223 
224 		zend_func_info_add(old_func_infos, sizeof(old_func_infos)/sizeof(func_info_t));
225 		zend_func_info_add(func_infos, sizeof(func_infos)/sizeof(func_info_t));
226 	}
227 
228 	return SUCCESS;
229 }
230 
zend_func_info_shutdown(void)231 zend_result zend_func_info_shutdown(void)
232 {
233 	if (zend_func_info_rid != -1) {
234 		zend_hash_destroy(&func_info);
235 		zend_func_info_rid = -1;
236 	}
237 	return SUCCESS;
238 }
239