/src/php-src/Zend/zend_partial.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend Engine | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © Zend Technologies Ltd., a subsidiary company of | |
6 | | | Perforce Software, Inc., and Contributors. | |
7 | | +----------------------------------------------------------------------+ |
8 | | | This source file is subject to the Modified BSD License that is | |
9 | | | bundled with this package in the file LICENSE, and is available | |
10 | | | through the World Wide Web at <https://www.php.net/license/>. | |
11 | | | | |
12 | | | SPDX-License-Identifier: BSD-3-Clause | |
13 | | +----------------------------------------------------------------------+ |
14 | | | Authors: Arnaud Le Blanc <arnaud.lb@gmail.com> | |
15 | | +----------------------------------------------------------------------+ |
16 | | */ |
17 | | |
18 | | /** |
19 | | * Partial Function Application: |
20 | | * |
21 | | * A partial application is compiled to the usual sequence of function call |
22 | | * opcodes (INIT_FCALL, SEND_VAR, etc), but the sequence ends with a |
23 | | * CALLABLE_CONVERT_PARTIAL opcode instead of DO_FCALL, similarly to |
24 | | * first class callables. Placeholders are compiled to SEND_PLACEHOLDER opcodes: |
25 | | * |
26 | | * $f = f($a, ?) |
27 | | * |
28 | | * 0001 INIT_FCALL f |
29 | | * 0002 SEND_VAR CV($a) |
30 | | * 0003 SEND_PLACEHOLDER |
31 | | * 0004 CV($f) = CALLABLE_CONVERT_PARTIAL |
32 | | * |
33 | | * SEND_PLACEHOLDER sets the argument slot type to _IS_PLACEHOLDER. |
34 | | * |
35 | | * CALLABLE_CONVERT_PARTIAL uses the information available on the stack to |
36 | | * create a Closure and return it, consuming the stack frame in the process |
37 | | * like an internal function call. |
38 | | * |
39 | | * We create the Closure by generating the relevant AST and compiling it to an |
40 | | * op_array. The op_array is cached in the Opcache SHM and inline caches. |
41 | | * |
42 | | * This file implements the Closure generation logic |
43 | | * (see zend_partial_create(), zp_compile()). |
44 | | */ |
45 | | |
46 | | #include "zend.h" |
47 | | #include "zend_API.h" |
48 | | #include "zend_arena.h" |
49 | | #include "zend_ast.h" |
50 | | #include "zend_compile.h" |
51 | | #include "zend_closures.h" |
52 | | #include "zend_attributes.h" |
53 | | #include "zend_exceptions.h" |
54 | | #include "zend_partial.h" |
55 | | #include "ext/opcache/ZendAccelerator.h" |
56 | | |
57 | 10.6k | static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) { |
58 | 10.6k | return Z_TYPE_P(p) == _IS_PLACEHOLDER; |
59 | 10.6k | } |
60 | | |
61 | 221 | static zend_always_inline bool zp_is_static_closure(const zend_function *function) { |
62 | 221 | return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)); |
63 | 221 | } |
64 | | |
65 | 882 | static zend_always_inline bool zp_is_non_static_closure(const zend_function *function) { |
66 | 882 | return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == ZEND_ACC_CLOSURE); |
67 | 882 | } |
68 | | |
69 | | /* Whether argument at offset 'offset' is const. Such arguments can be burned into the generated op_array */ |
70 | 4.70k | static inline bool zp_is_const_arg(uint32_t const_args, uint32_t offset) { |
71 | 4.70k | if (offset < sizeof(const_args) * CHAR_BIT) { |
72 | 4.68k | return const_args & (UINT32_C(1) << offset); |
73 | 4.68k | } |
74 | 17 | return false; |
75 | 4.70k | } |
76 | | |
77 | | static zend_never_inline ZEND_COLD void zp_args_underflow( |
78 | | const zend_function *function, uint32_t args, uint32_t expected) |
79 | 26 | { |
80 | 26 | zend_string *symbol = get_function_or_method_name(function); |
81 | 26 | const char *limit = function->common.num_args <= function->common.required_num_args ? |
82 | 21 | "exactly" : "at least"; |
83 | | |
84 | 26 | zend_argument_count_error( |
85 | 26 | "Partial application of %s() expects %s %d arguments, %d given", |
86 | 26 | ZSTR_VAL(symbol), limit, expected, args); |
87 | | |
88 | 26 | zend_string_release(symbol); |
89 | 26 | } |
90 | | |
91 | | static zend_never_inline ZEND_COLD void zp_args_overflow( |
92 | | const zend_function *function, uint32_t args, uint32_t expected) |
93 | 36 | { |
94 | 36 | zend_string *symbol = get_function_or_method_name(function); |
95 | | |
96 | 36 | zend_argument_count_error( |
97 | 36 | "Partial application of %s() expects at most %d arguments, %d given", |
98 | 36 | ZSTR_VAL(symbol), expected, args); |
99 | | |
100 | 36 | zend_string_release(symbol); |
101 | 36 | } |
102 | | |
103 | | static zend_result zp_args_check(const zend_function *function, |
104 | | uint32_t argc, const zval *argv, |
105 | | const zend_array *extra_named_args, |
106 | 1.15k | bool uses_variadic_placeholder) { |
107 | | |
108 | 1.15k | if (extra_named_args) { |
109 | 384 | ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(extra_named_args, zend_string *key, zval *arg) { |
110 | 384 | if (UNEXPECTED(Z_IS_PLACEHOLDER_P(arg))) { |
111 | 9 | zend_throw_error(NULL, |
112 | 9 | "Cannot use named placeholder for unknown or variadic parameter $%s", |
113 | 9 | ZSTR_VAL(key)); |
114 | 9 | return FAILURE; |
115 | 9 | } |
116 | 384 | } ZEND_HASH_FOREACH_END(); |
117 | 96 | } |
118 | | |
119 | 1.14k | if (argc < function->common.required_num_args) { |
120 | 88 | if (uses_variadic_placeholder) { |
121 | | /* Missing args will be turned into placeholders */ |
122 | 62 | return SUCCESS; |
123 | 62 | } |
124 | | |
125 | 26 | zp_args_underflow(function, argc, function->common.required_num_args); |
126 | 26 | return FAILURE; |
127 | 1.06k | } else if (argc > function->common.num_args |
128 | 178 | && !(function->common.fn_flags & ZEND_ACC_VARIADIC)) { |
129 | 36 | zp_args_overflow(function, argc, function->common.num_args); |
130 | 36 | return FAILURE; |
131 | 36 | } |
132 | | |
133 | 1.02k | return SUCCESS; |
134 | 1.14k | } |
135 | | |
136 | | typedef struct zp_names { |
137 | | zend_string *variadic_param; |
138 | | zend_string *extra_named_params; |
139 | | zend_string *inner_closure; |
140 | | zend_string *params[1]; /* argc elements */ |
141 | | } zp_names; |
142 | | |
143 | | static bool zp_name_exists(const zp_names *names, uint32_t argc, const zend_string *name) |
144 | 1.13k | { |
145 | 4.82k | for (uint32_t i = 0; i < argc; i++) { |
146 | 3.95k | if (names->params[i] && zend_string_equals(names->params[i], name)) { |
147 | 273 | return true; |
148 | 273 | } |
149 | 3.95k | } |
150 | 862 | if (names->variadic_param && zend_string_equals(names->variadic_param, name)) { |
151 | 30 | return true; |
152 | 30 | } |
153 | 832 | if (names->extra_named_params && zend_string_equals(names->extra_named_params, name)) { |
154 | 0 | return true; |
155 | 0 | } |
156 | 832 | if (names->inner_closure && zend_string_equals(names->inner_closure, name)) { |
157 | 0 | return true; |
158 | 0 | } |
159 | | |
160 | 832 | return false; |
161 | 832 | } |
162 | | |
163 | | static void zp_names_dtor(zp_names *names, uint32_t argc) |
164 | 1.08k | { |
165 | 3.19k | for (uint32_t i = 0; i < argc; i++) { |
166 | 2.10k | if (names->params[i]) { |
167 | 1.81k | zend_string_release(names->params[i]); |
168 | 1.81k | } |
169 | 2.10k | } |
170 | 1.08k | if (names->variadic_param) { |
171 | 164 | zend_string_release(names->variadic_param); |
172 | 164 | } |
173 | 1.08k | if (names->extra_named_params) { |
174 | 87 | zend_string_release(names->extra_named_params); |
175 | 87 | } |
176 | 1.08k | if (names->inner_closure) { |
177 | 148 | zend_string_release(names->inner_closure); |
178 | 148 | } |
179 | 1.08k | } |
180 | | |
181 | | static zend_string *zp_get_func_param_name(const zend_function *function, uint32_t arg_offset) |
182 | 1.97k | { |
183 | 1.97k | return zend_string_copy(function->common.arg_info[arg_offset].name); |
184 | 1.97k | } |
185 | | |
186 | | /* Assign a name for every variable that will be used in the generated closure, |
187 | | * including params and used vars. */ |
188 | | static zp_names *zp_assign_names(uint32_t argc, zval *argv, |
189 | | zend_function *function, bool variadic_partial, |
190 | | zend_array *extra_named_params, uint32_t const_args) |
191 | 1.08k | { |
192 | 1.08k | zp_names *names = zend_arena_calloc(&CG(ast_arena), |
193 | 1.08k | 1, zend_safe_address_guarded(argc, sizeof(*names->params), offsetof(zp_names, params))); |
194 | | |
195 | | /* Assign names for params. We never rename those. */ |
196 | 1.08k | for (uint32_t offset = 0, num_args = MIN(argc, function->common.num_args); |
197 | 2.99k | offset < num_args; offset++) { |
198 | 1.90k | if (Z_IS_PLACEHOLDER_P(&argv[offset])) { |
199 | 1.21k | names->params[offset] = zp_get_func_param_name(function, offset); |
200 | 1.21k | } |
201 | 1.90k | } |
202 | | |
203 | | /* Assign name for the variadic param. Never renamed. */ |
204 | 1.08k | if (variadic_partial && (function->common.fn_flags & ZEND_ACC_VARIADIC)) { |
205 | 164 | names->variadic_param = zp_get_func_param_name(function, function->common.num_args); |
206 | 164 | } |
207 | | |
208 | | /* Assign names for placeholders that bind to the variadic param: |
209 | | * |
210 | | * function f($a, ...$args) {} |
211 | | * f(?, ?, ...); // The second placeholder binds into the variadic param. |
212 | | * |
213 | | * By default these are named $origNameN with N the offset from the |
214 | | * variadic param. In case of clash we increment N until a free name is |
215 | | * found. */ |
216 | 1.28k | for (uint32_t offset = function->common.num_args; offset < argc; offset++) { |
217 | 200 | ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC); |
218 | 200 | if (!Z_IS_PLACEHOLDER_P(&argv[offset])) { |
219 | 85 | continue; |
220 | 85 | } |
221 | 115 | zend_string *orig_name = zp_get_func_param_name(function, function->common.num_args); |
222 | 115 | zend_string *new_name; |
223 | 120 | for (uint32_t n = 0;; n++) { |
224 | 120 | new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); |
225 | 120 | if (!zp_name_exists(names, argc, new_name)) { |
226 | 115 | break; |
227 | 115 | } |
228 | 5 | zend_string_release(new_name); |
229 | 5 | } |
230 | 115 | names->params[offset] = new_name; |
231 | 115 | zend_string_release(orig_name); |
232 | 115 | } |
233 | | |
234 | | /* Assign names for pre-bound params (lexical vars). |
235 | | * There may be clashes, we ensure to generate unique names. */ |
236 | 3.19k | for (uint32_t offset = 0; offset < argc; offset++) { |
237 | 2.10k | if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset]) || zp_is_const_arg(const_args, offset)) { |
238 | 1.62k | continue; |
239 | 1.62k | } |
240 | 482 | uint32_t n = 2; |
241 | 482 | zend_string *orig_name = zp_get_func_param_name(function, MIN(offset, function->common.num_args)); |
242 | 482 | zend_string *new_name = zend_string_copy(orig_name); |
243 | 775 | while (zp_name_exists(names, argc, new_name)) { |
244 | 293 | zend_string_release(new_name); |
245 | 293 | new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n); |
246 | 293 | n++; |
247 | 293 | } |
248 | 482 | names->params[offset] = new_name; |
249 | 482 | zend_string_release(orig_name); |
250 | 482 | } |
251 | | |
252 | | /* Assign name for $extra_named_params */ |
253 | 1.08k | if (extra_named_params) { |
254 | 87 | uint32_t n = 2; |
255 | 87 | zend_string *new_name = ZSTR_INIT_LITERAL("extra_named_params", 0); |
256 | 87 | while (zp_name_exists(names, argc, new_name)) { |
257 | 0 | zend_string_release(new_name); |
258 | 0 | new_name = zend_strpprintf(0, "%s%" PRIu32, "extra_named_params", n); |
259 | 0 | n++; |
260 | 0 | } |
261 | 87 | names->extra_named_params = new_name; |
262 | 87 | } |
263 | | |
264 | | /* Assign name for $fn */ |
265 | 1.08k | if (function->common.fn_flags & ZEND_ACC_CLOSURE) { |
266 | 148 | uint32_t n = 2; |
267 | 148 | zend_string *new_name = ZSTR_INIT_LITERAL("fn", 0); |
268 | 153 | while (zp_name_exists(names, argc, new_name)) { |
269 | 5 | zend_string_release(new_name); |
270 | 5 | new_name = zend_strpprintf(0, "%s%" PRIu32, "fn", n); |
271 | 5 | n++; |
272 | 5 | } |
273 | 148 | names->inner_closure = new_name; |
274 | 148 | } |
275 | | |
276 | 1.08k | return names; |
277 | 1.08k | } |
278 | | |
279 | | ZEND_ATTRIBUTE_CONST static inline bool zp_is_power_of_two(uint32_t x) |
280 | 1.56k | { |
281 | 1.56k | return (x > 0) && !(x & (x - 1)); |
282 | 1.56k | } |
283 | | |
284 | | ZEND_ATTRIBUTE_CONST static bool zp_is_simple_type(uint32_t type_mask) |
285 | 1.56k | { |
286 | 1.56k | ZEND_ASSERT(!(type_mask & ~_ZEND_TYPE_MAY_BE_MASK)); |
287 | | |
288 | 1.56k | return zp_is_power_of_two(type_mask) |
289 | 325 | || type_mask == MAY_BE_BOOL |
290 | 277 | || type_mask == MAY_BE_ANY; |
291 | 1.56k | } |
292 | | |
293 | | static zend_ast *zp_simple_type_to_ast(uint32_t type) |
294 | 810 | { |
295 | 810 | zend_string *name; |
296 | | |
297 | 810 | switch (type) { |
298 | 9 | case MAY_BE_NULL: |
299 | 9 | name = ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE); |
300 | 9 | break; |
301 | 0 | case MAY_BE_TRUE: |
302 | 0 | name = ZSTR_KNOWN(ZEND_STR_TRUE); |
303 | 0 | break; |
304 | 0 | case MAY_BE_FALSE: |
305 | 0 | name = ZSTR_KNOWN(ZEND_STR_FALSE); |
306 | 0 | break; |
307 | 242 | case MAY_BE_LONG: |
308 | 242 | name = ZSTR_KNOWN(ZEND_STR_INT); |
309 | 242 | break; |
310 | 27 | case MAY_BE_DOUBLE: |
311 | 27 | name = ZSTR_KNOWN(ZEND_STR_FLOAT); |
312 | 27 | break; |
313 | 188 | case MAY_BE_STRING: |
314 | 188 | name = ZSTR_KNOWN(ZEND_STR_STRING); |
315 | 188 | break; |
316 | 24 | case MAY_BE_BOOL: |
317 | 24 | name = ZSTR_KNOWN(ZEND_STR_BOOL); |
318 | 24 | break; |
319 | 32 | case MAY_BE_VOID: |
320 | 32 | name = ZSTR_KNOWN(ZEND_STR_VOID); |
321 | 32 | break; |
322 | 0 | case MAY_BE_NEVER: |
323 | 0 | name = ZSTR_KNOWN(ZEND_STR_NEVER); |
324 | 0 | break; |
325 | 50 | case MAY_BE_OBJECT: |
326 | 50 | name = ZSTR_KNOWN(ZEND_STR_OBJECT); |
327 | 50 | break; |
328 | 130 | case MAY_BE_ANY: |
329 | 130 | name = ZSTR_KNOWN(ZEND_STR_MIXED); |
330 | 130 | break; |
331 | 7 | case MAY_BE_CALLABLE: |
332 | 7 | return zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); |
333 | 93 | case MAY_BE_ARRAY: |
334 | 93 | return zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); |
335 | 8 | case MAY_BE_STATIC: |
336 | 8 | return zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC); |
337 | 0 | default: |
338 | 0 | ZEND_UNREACHABLE(); |
339 | 810 | } |
340 | | |
341 | 702 | zend_ast *ast = zend_ast_create_zval_from_str(name); |
342 | 702 | ast->attr = ZEND_NAME_NOT_FQ; |
343 | | |
344 | 702 | return ast; |
345 | 810 | } |
346 | | |
347 | | static zend_ast *zp_type_name_to_ast(zend_string *name) |
348 | 41 | { |
349 | 41 | zend_ast *ast = zend_ast_create_zval_from_str(name); |
350 | | |
351 | 41 | if (zend_get_class_fetch_type(name) != ZEND_FETCH_CLASS_DEFAULT) { |
352 | 8 | ast->attr = ZEND_NAME_NOT_FQ; |
353 | 33 | } else { |
354 | 33 | ast->attr = ZEND_NAME_FQ; |
355 | 33 | } |
356 | | |
357 | 41 | return ast; |
358 | 41 | } |
359 | | |
360 | | static zend_ast *zp_type_to_ast(const zend_type type) |
361 | 1.73k | { |
362 | 1.73k | if (!ZEND_TYPE_IS_SET(type)) { |
363 | 905 | return NULL; |
364 | 905 | } |
365 | | |
366 | 832 | if (ZEND_TYPE_IS_UNION(type) |
367 | 832 | || (ZEND_TYPE_IS_COMPLEX(type) && ZEND_TYPE_PURE_MASK(type)) |
368 | 830 | || (ZEND_TYPE_PURE_MASK(type) && !zp_is_simple_type(ZEND_TYPE_PURE_MASK(type)))) { |
369 | | /* This is a union type */ |
370 | | |
371 | 19 | zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_UNION); |
372 | | |
373 | | /* Add complex types if any */ |
374 | 19 | if (ZEND_TYPE_HAS_LIST(type)) { |
375 | 0 | ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), const zend_type *type_ptr) { |
376 | 0 | type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr)); |
377 | 0 | } ZEND_TYPE_LIST_FOREACH_END(); |
378 | 19 | } else if (ZEND_TYPE_HAS_NAME(type)) { |
379 | 2 | zend_ast *name_ast = zp_type_name_to_ast( |
380 | 2 | zend_string_copy(ZEND_TYPE_NAME(type))); |
381 | 2 | type_ast = zend_ast_list_add(type_ast, name_ast); |
382 | 17 | } else { |
383 | 17 | ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type)); |
384 | 17 | } |
385 | | |
386 | | /* Add simple types if any */ |
387 | 19 | uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); |
388 | | |
389 | | /* IS_TRUE|IS_FALSE is represented as a single bool node */ |
390 | 19 | if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { |
391 | 0 | type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(MAY_BE_BOOL)); |
392 | 0 | type_mask &= ~MAY_BE_BOOL; |
393 | 0 | } |
394 | 361 | for (uint32_t may_be_type = 1; may_be_type < _ZEND_TYPE_MAY_BE_MASK; may_be_type <<= 1) { |
395 | 342 | if (type_mask & may_be_type) { |
396 | 36 | type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(may_be_type)); |
397 | 36 | } |
398 | 342 | } |
399 | | |
400 | 19 | return type_ast; |
401 | 19 | } |
402 | | |
403 | 813 | if (ZEND_TYPE_IS_INTERSECTION(type)) { |
404 | 0 | ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); |
405 | 0 | zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_INTERSECTION); |
406 | 0 | ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), const zend_type *type_ptr) { |
407 | 0 | type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr)); |
408 | 0 | } ZEND_TYPE_LIST_FOREACH_END(); |
409 | 0 | return type_ast; |
410 | 0 | } |
411 | | |
412 | 813 | if (ZEND_TYPE_HAS_NAME(type)) { |
413 | 39 | ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type)); |
414 | 39 | zend_ast *type_ast = zp_type_name_to_ast( |
415 | 39 | zend_string_copy(ZEND_TYPE_NAME(type))); |
416 | 39 | return type_ast; |
417 | 39 | } |
418 | | |
419 | 774 | ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type)); |
420 | | |
421 | 774 | uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); |
422 | 774 | ZEND_ASSERT(zp_is_simple_type(type_mask)); |
423 | | |
424 | 774 | return zp_simple_type_to_ast(type_mask); |
425 | 774 | } |
426 | | |
427 | | static zend_result zp_get_param_default_value(zval *result, zend_function *function, uint32_t arg_offset) |
428 | 212 | { |
429 | 212 | ZEND_ASSERT(arg_offset < function->common.num_args); |
430 | | |
431 | 212 | if (function->type == ZEND_USER_FUNCTION) { |
432 | 196 | zend_op *opline = &function->op_array.opcodes[arg_offset]; |
433 | 196 | if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) { |
434 | 196 | ZVAL_COPY(result, RT_CONSTANT(opline, opline->op2)); |
435 | 196 | return SUCCESS; |
436 | 196 | } |
437 | 0 | ZEND_ASSERT(opline->opcode == ZEND_RECV); |
438 | 16 | } else { |
439 | 16 | if (function->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { |
440 | 6 | goto error; |
441 | 6 | } |
442 | | |
443 | 10 | const zend_arg_info *arg_info = &function->internal_function.arg_info[arg_offset]; |
444 | | |
445 | 10 | if (zend_get_default_from_internal_arg_info(result, arg_info) == SUCCESS) { |
446 | 0 | return SUCCESS; |
447 | 0 | } |
448 | 10 | } |
449 | | |
450 | 16 | error: |
451 | 16 | zend_argument_error_ex(zend_ce_argument_count_error, function, arg_offset + 1, |
452 | 16 | "must be passed explicitly, because the default value is not known"); |
453 | | |
454 | 16 | return FAILURE; |
455 | 212 | } |
456 | | |
457 | | static bool zp_arg_must_be_sent_by_ref(const zend_function *function, uint32_t arg_num) |
458 | 2.47k | { |
459 | 2.47k | if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) { |
460 | 2.45k | if (QUICK_ARG_MUST_BE_SENT_BY_REF(function, arg_num)) { |
461 | 51 | return true; |
462 | 51 | } |
463 | 2.45k | } else if (ARG_MUST_BE_SENT_BY_REF(function, arg_num)) { |
464 | 0 | return true; |
465 | 0 | } |
466 | 2.42k | return false; |
467 | 2.47k | } |
468 | | |
469 | | static zend_ast *zp_attribute_to_ast(zend_attribute *attribute) |
470 | 41 | { |
471 | 41 | zend_ast *args_ast; |
472 | 41 | if (attribute->argc) { |
473 | 8 | args_ast = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); |
474 | 16 | for (uint32_t i = 0; i < attribute->argc; i++) { |
475 | 8 | zval *zv = &attribute->args[i].value; |
476 | 8 | Z_TRY_ADDREF_P(zv); |
477 | 8 | zend_ast *arg_ast = zend_ast_create_zval(zv); |
478 | 8 | if (attribute->args[i].name) { |
479 | 2 | arg_ast = zend_ast_create(ZEND_AST_NAMED_ARG, |
480 | 2 | zend_ast_create_zval_from_str( |
481 | 2 | zend_string_copy(attribute->args[i].name)), |
482 | 2 | arg_ast); |
483 | 2 | } |
484 | 8 | args_ast = zend_ast_list_add(args_ast, arg_ast); |
485 | 8 | } |
486 | 33 | } else { |
487 | 33 | args_ast = NULL; |
488 | 33 | } |
489 | 41 | return zend_ast_create(ZEND_AST_ATTRIBUTE, |
490 | 41 | zend_ast_create_zval_from_str(zend_string_copy(attribute->name)), |
491 | 41 | args_ast); |
492 | 41 | } |
493 | | |
494 | | static zend_ast *zp_param_attributes_to_ast(zend_function *function, |
495 | | uint32_t offset) |
496 | 1.48k | { |
497 | 1.48k | zend_ast *attributes_ast = NULL; |
498 | 1.48k | if (!function->common.attributes) { |
499 | 1.41k | return NULL; |
500 | 1.41k | } |
501 | | |
502 | | /* Inherit the SensitiveParameter attribute */ |
503 | 66 | zend_attribute *attr = zend_get_parameter_attribute_str( |
504 | 66 | function->common.attributes, |
505 | 66 | "sensitiveparameter", strlen("sensitiveparameter"), offset); |
506 | 66 | if (attr) { |
507 | 26 | attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP, |
508 | 26 | zp_attribute_to_ast(attr)); |
509 | 26 | attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST, |
510 | 26 | attributes_ast); |
511 | 26 | } |
512 | | |
513 | 66 | return attributes_ast; |
514 | 1.48k | } |
515 | | |
516 | | /* Generate the AST for calling the actual function */ |
517 | | static zend_ast *zp_compile_forwarding_call( |
518 | | zval *this_ptr, zend_function *function, |
519 | | uint32_t argc, zval *argv, zend_array *extra_named_params, |
520 | | zp_names *var_names, bool uses_variadic_placeholder, uint32_t num_args, |
521 | | zend_class_entry *called_scope, zend_type return_type, |
522 | | bool forward_superfluous_args, |
523 | | zend_ast *stmts_ast, uint32_t const_args) |
524 | 1.28k | { |
525 | 1.28k | bool is_assert = zend_string_equals(function->common.function_name, |
526 | 1.28k | ZSTR_KNOWN(ZEND_STR_ASSERT)); |
527 | | |
528 | 1.28k | zend_ast *args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); |
529 | 1.28k | zend_ast *call_ast = NULL; |
530 | | |
531 | 1.28k | if (is_assert) { |
532 | | /* We are going to call assert() dynamically (via call_user_func), |
533 | | * otherwise assert() would print the generated AST on failure, which is |
534 | | * irrelevant. */ |
535 | 18 | args_ast = zend_ast_list_add(args_ast, |
536 | 18 | zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ASSERT))); |
537 | 18 | } |
538 | | |
539 | | /* Generate positional arguments */ |
540 | 3.78k | for (uint32_t offset = 0; offset < argc; offset++) { |
541 | 2.52k | if (Z_ISUNDEF(argv[offset])) { |
542 | | /* Argument was not passed. Pass its default value. */ |
543 | 59 | if (offset < function->common.required_num_args) { |
544 | | /* Required param was not passed. This can happen due to named |
545 | | * args. Using the same exception CE and message as |
546 | | * zend_handle_undef_args(). */ |
547 | 14 | zend_argument_error_ex(zend_ce_argument_count_error, function, offset + 1, "not passed"); |
548 | 14 | goto error; |
549 | 14 | } |
550 | 45 | zval default_value; |
551 | 45 | if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) { |
552 | 5 | ZEND_ASSERT(EG(exception)); |
553 | 5 | goto error; |
554 | 5 | } |
555 | 40 | zend_ast *default_value_ast; |
556 | 40 | if (Z_TYPE(default_value) == IS_CONSTANT_AST) { |
557 | | /* Must dup AST because we are going to destroy it */ |
558 | 28 | default_value_ast = zend_ast_dup(Z_ASTVAL(default_value)); |
559 | 28 | zval_ptr_dtor_nogc(&default_value); |
560 | 28 | } else { |
561 | 12 | default_value_ast = zend_ast_create_zval(&default_value); |
562 | 12 | } |
563 | 40 | args_ast = zend_ast_list_add(args_ast, default_value_ast); |
564 | 2.47k | } else if (zp_is_const_arg(const_args, offset)) { |
565 | 235 | ZEND_ASSERT(Z_TYPE(argv[offset]) < IS_OBJECT); |
566 | 235 | ZEND_ASSERT(!Z_REFCOUNTED(argv[offset])); |
567 | | |
568 | | /* This argument never changes, so we can burn it into the op_array |
569 | | * and check its type ahead of time. */ |
570 | | |
571 | 235 | zend_arg_info *arg_info; |
572 | 235 | if (offset < function->common.num_args) { |
573 | 227 | arg_info = &function->common.arg_info[offset]; |
574 | 227 | } else if (function->common.fn_flags & ZEND_ACC_VARIADIC) { |
575 | 8 | arg_info = &function->common.arg_info[function->common.num_args]; |
576 | 8 | } else { |
577 | 0 | arg_info = NULL; |
578 | 0 | } |
579 | 235 | if (arg_info && ZEND_TYPE_IS_SET(arg_info->type) |
580 | 30 | && UNEXPECTED(!zend_check_type_ex(&arg_info->type, &argv[offset], |
581 | 235 | /* current_frame */ true, /* is_internal */ false))) { |
582 | 10 | zend_string *need_msg = zend_type_to_string_resolved(arg_info->type, |
583 | 10 | function->common.scope); |
584 | 10 | zend_argument_type_error_ex(function, offset + 1, |
585 | 10 | "must be of type %s, %s given", |
586 | 10 | ZSTR_VAL(need_msg), zend_zval_value_name(&argv[offset])); |
587 | 10 | zend_string_release(need_msg); |
588 | 10 | goto error; |
589 | 10 | } |
590 | | |
591 | 225 | args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&argv[offset])); |
592 | 2.23k | } else { |
593 | 2.23k | args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR, |
594 | 2.23k | zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset])))); |
595 | 2.23k | } |
596 | 2.52k | } |
597 | | /* Use unpacking to pass extra named params */ |
598 | 1.25k | if (extra_named_params) { |
599 | 87 | args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, |
600 | 87 | zend_ast_create(ZEND_AST_VAR, |
601 | 87 | zend_ast_create_zval_from_str(zend_string_copy(var_names->extra_named_params))))); |
602 | 87 | } |
603 | 1.25k | if (uses_variadic_placeholder) { |
604 | 588 | if (function->common.fn_flags & ZEND_ACC_VARIADIC) { |
605 | | /* Pass variadic params */ |
606 | 164 | args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK, |
607 | 164 | zend_ast_create(ZEND_AST_VAR, |
608 | 164 | zend_ast_create_zval_from_str(zend_string_copy(var_names->variadic_param))))); |
609 | 424 | } else if (forward_superfluous_args) { |
610 | | /* When a '...' placeholder is used, and the underlying function is |
611 | | * not variadic, superfluous arguments are forwarded. |
612 | | * Add a ...array_slice(func_get_args(), n) argument, which should |
613 | | * be compiled as ZEND_AST_UNPACK + ZEND_FUNC_GET_ARGS. */ |
614 | | |
615 | 212 | zend_ast *func_get_args_name_ast = zend_ast_create_zval_from_str( |
616 | 212 | zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_GET_ARGS))); |
617 | 212 | func_get_args_name_ast->attr = ZEND_NAME_FQ; |
618 | | |
619 | 212 | zend_ast *array_slice_name_ast = zend_ast_create_zval_from_str( |
620 | 212 | zend_string_copy(ZSTR_KNOWN(ZEND_STR_ARRAY_SLICE))); |
621 | 212 | array_slice_name_ast->attr = ZEND_NAME_FQ; |
622 | | |
623 | 212 | args_ast = zend_ast_list_add(args_ast, |
624 | 212 | zend_ast_create(ZEND_AST_UNPACK, |
625 | 212 | zend_ast_create(ZEND_AST_CALL, |
626 | 212 | array_slice_name_ast, |
627 | 212 | zend_ast_create_list(2, ZEND_AST_ARG_LIST, |
628 | 212 | zend_ast_create(ZEND_AST_CALL, |
629 | 212 | func_get_args_name_ast, |
630 | 212 | zend_ast_create_list(0, ZEND_AST_ARG_LIST)), |
631 | 212 | zend_ast_create_zval_from_long(num_args))))); |
632 | 212 | } |
633 | 588 | } |
634 | | |
635 | 1.25k | if (is_assert) { |
636 | 18 | zend_ast *func_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CALL_USER_FUNC)); |
637 | 18 | func_name_ast->attr = ZEND_NAME_FQ; |
638 | 18 | call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast); |
639 | 1.24k | } else if (function->common.fn_flags & ZEND_ACC_CLOSURE) { |
640 | 185 | zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR, |
641 | 185 | zend_ast_create_zval_from_str(zend_string_copy(var_names->inner_closure))); |
642 | 185 | call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast); |
643 | 1.05k | } else if (Z_TYPE_P(this_ptr) == IS_OBJECT) { |
644 | 172 | zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR, |
645 | 172 | zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS))); |
646 | 172 | zend_ast *method_name_ast = zend_ast_create_zval_from_str( |
647 | 172 | zend_string_copy(function->common.function_name)); |
648 | 172 | call_ast = zend_ast_create(ZEND_AST_METHOD_CALL, this_ast, |
649 | 172 | method_name_ast, args_ast); |
650 | 883 | } else if (called_scope) { |
651 | 75 | zend_ast *class_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_STATIC)); |
652 | 75 | class_name_ast->attr = ZEND_NAME_NOT_FQ; |
653 | 75 | zend_ast *method_name_ast = zend_ast_create_zval_from_str( |
654 | 75 | zend_string_copy(function->common.function_name)); |
655 | 75 | call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_name_ast, |
656 | 75 | method_name_ast, args_ast); |
657 | 808 | } else { |
658 | 808 | zend_ast *func_name_ast = zend_ast_create_zval_from_str(zend_string_copy(function->common.function_name)); |
659 | 808 | func_name_ast->attr = ZEND_NAME_FQ; |
660 | 808 | call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast); |
661 | 808 | } |
662 | | |
663 | | /* Void functions can not 'return $expr' */ |
664 | 1.25k | if (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_VOID) { |
665 | 27 | stmts_ast = zend_ast_list_add(stmts_ast, call_ast); |
666 | 1.23k | } else { |
667 | 1.23k | zend_ast *return_ast = zend_ast_create(ZEND_AST_RETURN, call_ast); |
668 | 1.23k | stmts_ast = zend_ast_list_add(stmts_ast, return_ast); |
669 | 1.23k | } |
670 | | |
671 | 1.25k | return stmts_ast; |
672 | | |
673 | 29 | error: |
674 | 29 | zend_ast_destroy(args_ast); |
675 | 29 | zend_ast_destroy(call_ast); |
676 | 29 | return NULL; |
677 | 1.28k | } |
678 | | |
679 | | static uint32_t zp_compute_num_required(const zend_function *function, |
680 | 268 | uint32_t orig_offset, uint32_t new_offset, uint32_t num_required) { |
681 | 268 | if (orig_offset < function->common.num_args) { |
682 | 268 | if (orig_offset < function->common.required_num_args) { |
683 | 101 | num_required = MAX(num_required, new_offset + 1); |
684 | 101 | } |
685 | 268 | } else { |
686 | 0 | ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC); |
687 | | /* Placeholders that run into the variadic portion become |
688 | | * required and make all params before them required */ |
689 | 0 | ZEND_ASSERT(orig_offset >= num_required); |
690 | 0 | num_required = new_offset + 1; |
691 | 0 | } |
692 | | |
693 | 268 | return num_required; |
694 | 268 | } |
695 | | |
696 | | /* Compile PFA to an op_array */ |
697 | | static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, |
698 | | uint32_t argc, zval *argv, zend_array *extra_named_params, |
699 | | const zend_array *named_positions, |
700 | | zend_string *declaring_filename, |
701 | | const uint32_t *declaring_lineno_ptr, void **cache_slot, |
702 | 1.19k | zend_string *pfa_name, uint32_t flags, uint32_t const_args) { |
703 | | |
704 | 1.19k | zend_op_array *op_array = NULL; |
705 | | |
706 | 1.19k | if (UNEXPECTED(function->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)) { |
707 | 34 | zend_throw_error(NULL, "Cannot call %pS() dynamically", function->common.function_name); |
708 | 34 | return NULL; |
709 | 34 | } |
710 | | |
711 | 1.19k | bool uses_variadic_placeholder = flags & ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; |
712 | | |
713 | 1.15k | if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) { |
714 | 71 | ZEND_ASSERT(EG(exception)); |
715 | 71 | return NULL; |
716 | 71 | } |
717 | | |
718 | 1.08k | zend_class_entry *called_scope; |
719 | 1.08k | if (Z_TYPE_P(this_ptr) == IS_OBJECT) { |
720 | 170 | called_scope = Z_OBJCE_P(this_ptr); |
721 | 916 | } else { |
722 | 916 | called_scope = Z_CE_P(this_ptr); |
723 | 916 | } |
724 | | |
725 | | /* CG(ast_arena) is usually NULL, so we can't just make a snapshot */ |
726 | 1.08k | zend_arena *orig_ast_arena = CG(ast_arena); |
727 | 1.08k | CG(ast_arena) = zend_arena_create(1024 * 4); |
728 | | |
729 | 1.08k | uint32_t orig_lineno = CG(zend_lineno); |
730 | 1.08k | CG(zend_lineno) = zend_get_executed_lineno(); |
731 | | |
732 | 1.08k | uint32_t new_argc = argc; |
733 | | |
734 | 1.08k | if (uses_variadic_placeholder) { |
735 | | /* A variadic placeholder generates an implicit positional placeholder |
736 | | * for any unspecified arg, so make room for those. */ |
737 | 387 | new_argc = MAX(new_argc, function->common.num_args); |
738 | 387 | } |
739 | | |
740 | 1.08k | zval *tmp = zend_arena_alloc(&CG(ast_arena), zend_safe_address_guarded(new_argc, sizeof(*tmp), 0)); |
741 | 1.08k | memcpy(tmp, argv, zend_safe_address_guarded(argc, sizeof(*tmp), 0)); |
742 | 1.08k | argv = tmp; |
743 | | |
744 | | /* Compute param positions and number of required args, add implicit |
745 | | * placeholders. |
746 | | * |
747 | | * Parameters are placed in the following order: |
748 | | * - Positional placeholders |
749 | | * - Then named placeholders in their syntax order |
750 | | * - Then implicit placeholders added by '...' |
751 | | */ |
752 | 1.08k | uint32_t num_params = 0; |
753 | 1.08k | uint32_t num_required = 0; |
754 | 1.08k | uint32_t *arg_to_param_offset_map = zend_arena_alloc(&CG(ast_arena), |
755 | 1.08k | zend_safe_address_guarded(new_argc, sizeof(*arg_to_param_offset_map), 0)); |
756 | 1.08k | { |
757 | 1.08k | uint32_t num_positional = 0; |
758 | | |
759 | | /* First, we handle explicit placeholders */ |
760 | 3.04k | for (uint32_t arg_offset = 0; arg_offset < argc; arg_offset++) { |
761 | 1.95k | if (!Z_IS_PLACEHOLDER_P(&argv[arg_offset])) { |
762 | 894 | continue; |
763 | 894 | } |
764 | | |
765 | 1.06k | num_params++; |
766 | | |
767 | 1.06k | zend_arg_info *arg_info = &function->common.arg_info[MIN(arg_offset, function->common.num_args)]; |
768 | 1.06k | zval *named_pos = named_positions ? zend_hash_find(named_positions, arg_info->name) : NULL; |
769 | 1.06k | uint32_t param_offset; |
770 | 1.06k | if (named_pos) { |
771 | | /* Placeholder is sent as named arg. 'num_positional' is fixed |
772 | | * at this point. */ |
773 | 186 | param_offset = num_positional + Z_LVAL_P(named_pos); |
774 | 878 | } else { |
775 | | /* Placeholder is sent as positional */ |
776 | 878 | param_offset = num_positional++; |
777 | 878 | } |
778 | | |
779 | 1.06k | arg_to_param_offset_map[arg_offset] = param_offset; |
780 | 1.06k | } |
781 | | |
782 | 1.08k | num_required = num_params; |
783 | | |
784 | | /* Handle implicit placeholders added by '...' */ |
785 | 1.08k | if (uses_variadic_placeholder) { |
786 | 1.10k | for (uint32_t arg_offset = 0; arg_offset < new_argc; arg_offset++) { |
787 | 718 | if (arg_offset < argc && !Z_ISUNDEF(argv[arg_offset])) { |
788 | 450 | continue; |
789 | 450 | } |
790 | | |
791 | | /* Unspecified parameters become placeholders */ |
792 | 268 | Z_TYPE_INFO(argv[arg_offset]) = _IS_PLACEHOLDER; |
793 | | |
794 | 268 | num_params++; |
795 | | |
796 | 268 | uint32_t param_offset = num_params - 1; |
797 | | |
798 | 268 | arg_to_param_offset_map[arg_offset] = param_offset; |
799 | | |
800 | 268 | num_required = zp_compute_num_required(function, |
801 | 268 | arg_offset, param_offset, num_required); |
802 | 268 | } |
803 | 387 | } |
804 | 1.08k | } |
805 | | |
806 | 1.08k | argc = new_argc; |
807 | | |
808 | | /* Assign variable names */ |
809 | | |
810 | 1.08k | zp_names *var_names = zp_assign_names(argc, argv, function, |
811 | 1.08k | uses_variadic_placeholder, extra_named_params, const_args); |
812 | | |
813 | | /* Generate AST */ |
814 | | |
815 | 1.08k | zend_ast *lexical_vars_ast = zend_ast_create_list(0, ZEND_AST_CLOSURE_USES); |
816 | 1.08k | zend_ast *params_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); |
817 | 1.08k | zend_ast *return_type_ast = NULL; |
818 | 1.08k | zend_ast *stmts_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); |
819 | 1.08k | zend_ast *attributes_ast = NULL; |
820 | | |
821 | | /* Generate AST for params and lexical vars */ |
822 | 1.08k | { |
823 | | /* The inner Closure, if any, is assumed to be the first lexical var by |
824 | | * do_closure_bind(). */ |
825 | 1.08k | if (function->common.fn_flags & ZEND_ACC_CLOSURE) { |
826 | 148 | zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( |
827 | 148 | zend_string_copy(var_names->inner_closure)); |
828 | 148 | lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); |
829 | 148 | } |
830 | | |
831 | 1.08k | zend_ast **params = zend_arena_calloc(&CG(ast_arena), num_params, sizeof(*params)); |
832 | 3.17k | for (uint32_t offset = 0; offset < argc; offset++) { |
833 | 2.09k | if (Z_IS_PLACEHOLDER_P(&argv[offset])) { |
834 | 1.33k | zend_arg_info *arg_info = &function->common.arg_info[MIN(offset, function->common.num_args)]; |
835 | | |
836 | 1.33k | int param_flags = 0; |
837 | 1.33k | if (zp_arg_must_be_sent_by_ref(function, offset+1)) { |
838 | 19 | param_flags |= ZEND_PARAM_REF; |
839 | 19 | } |
840 | | |
841 | 1.33k | uint32_t param_offset = arg_to_param_offset_map[offset]; |
842 | 1.33k | zend_ast *param_type_ast = zp_type_to_ast(arg_info->type); |
843 | 1.33k | zend_ast *default_value_ast = NULL; |
844 | 1.33k | if (param_offset >= num_required) { |
845 | 167 | zval default_value; |
846 | 167 | if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) { |
847 | 27 | for (uint32_t i = 0; i < num_params; i++) { |
848 | 16 | zend_ast_destroy(params[i]); |
849 | 16 | } |
850 | 11 | goto error; |
851 | 11 | } |
852 | 156 | default_value_ast = zend_ast_create_zval(&default_value); |
853 | 156 | } |
854 | | |
855 | 1.32k | ZEND_ASSERT(offset < function->common.num_args || (function->common.fn_flags & ZEND_ACC_VARIADIC)); |
856 | | |
857 | 1.32k | zend_ast *attributes_ast = zp_param_attributes_to_ast(function, MIN(offset, function->common.num_args)); |
858 | 1.32k | params[param_offset] = zend_ast_create_ex(ZEND_AST_PARAM, |
859 | 1.32k | param_flags, param_type_ast, |
860 | 1.32k | zend_ast_create_zval_from_str( |
861 | 1.32k | zend_string_copy(var_names->params[offset])), |
862 | 1.32k | default_value_ast, attributes_ast, NULL, NULL); |
863 | | |
864 | 1.32k | } else if (!Z_ISUNDEF(argv[offset])) { |
865 | 708 | if (zp_is_const_arg(const_args, offset)) { |
866 | | /* Will be burned into the op_array */ |
867 | 477 | } else { |
868 | 477 | zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( |
869 | 477 | zend_string_copy(var_names->params[offset])); |
870 | 477 | if (zp_arg_must_be_sent_by_ref(function, offset+1)) { |
871 | 9 | lexical_var_ast->attr = ZEND_BIND_REF; |
872 | 9 | } |
873 | 477 | lexical_vars_ast = zend_ast_list_add( |
874 | 477 | lexical_vars_ast, lexical_var_ast); |
875 | 477 | } |
876 | 708 | } |
877 | 2.09k | } |
878 | | |
879 | 2.39k | for (uint32_t i = 0; i < num_params; i++) { |
880 | 1.31k | params_ast = zend_ast_list_add(params_ast, params[i]); |
881 | 1.31k | } |
882 | 1.07k | } |
883 | | |
884 | 1.07k | if (extra_named_params) { |
885 | 87 | zend_ast *lexical_var_ast = zend_ast_create_zval_from_str( |
886 | 87 | zend_string_copy(var_names->extra_named_params)); |
887 | 87 | lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast); |
888 | 87 | } |
889 | | |
890 | | /* If we have a variadic placeholder and the underlying function is |
891 | | * variadic, add a variadic param. */ |
892 | 1.07k | if (uses_variadic_placeholder |
893 | 376 | && (function->common.fn_flags & ZEND_ACC_VARIADIC)) { |
894 | 164 | zend_arg_info *arg_info = &function->common.arg_info[function->common.num_args]; |
895 | 164 | int param_flags = ZEND_PARAM_VARIADIC; |
896 | 164 | if (zp_arg_must_be_sent_by_ref(function, function->common.num_args+1)) { |
897 | 11 | param_flags |= ZEND_PARAM_REF; |
898 | 11 | } |
899 | 164 | zend_ast *param_type_ast = zp_type_to_ast(arg_info->type); |
900 | 164 | zend_ast *attributes_ast = zp_param_attributes_to_ast(function, function->common.num_args); |
901 | 164 | params_ast = zend_ast_list_add(params_ast, zend_ast_create_ex(ZEND_AST_PARAM, |
902 | 164 | param_flags, param_type_ast, |
903 | 164 | zend_ast_create_zval_from_str( |
904 | 164 | zend_string_copy(var_names->variadic_param)), |
905 | 164 | NULL, attributes_ast, NULL, NULL)); |
906 | 164 | } |
907 | | |
908 | 1.07k | zend_type return_type = {0}; |
909 | 1.07k | if (function->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { |
910 | 241 | return_type = (function->common.arg_info-1)->type; |
911 | 241 | return_type_ast = zp_type_to_ast(return_type); |
912 | 241 | } |
913 | | |
914 | | /** |
915 | | * Generate function body. |
916 | | * |
917 | | * If we may need to forward superflous arguments, do that conditionally, as |
918 | | * it's faster: |
919 | | * |
920 | | * if (func_num_args() <= n) { |
921 | | * // normal call |
922 | | * } else { |
923 | | * // call with superflous arg forwarding |
924 | | * } |
925 | | * |
926 | | * The func_num_args() call should be compiled to a single FUNC_NUM_ARGS op. |
927 | | */ |
928 | | |
929 | 1.07k | if (uses_variadic_placeholder && !(function->common.fn_flags & ZEND_ACC_VARIADIC)) { |
930 | 212 | zend_ast *no_forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); |
931 | 212 | zend_ast *forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); |
932 | | |
933 | 212 | no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function, |
934 | 212 | argc, argv, extra_named_params, |
935 | 212 | var_names, uses_variadic_placeholder, num_params, |
936 | 212 | called_scope, return_type, false, no_forwarding_ast, const_args); |
937 | | |
938 | 212 | if (!no_forwarding_ast) { |
939 | 0 | ZEND_ASSERT(EG(exception)); |
940 | 0 | goto error; |
941 | 0 | } |
942 | | |
943 | 212 | forwarding_ast = zp_compile_forwarding_call(this_ptr, function, |
944 | 212 | argc, argv, extra_named_params, |
945 | 212 | var_names, uses_variadic_placeholder, num_params, |
946 | 212 | called_scope, return_type, true, forwarding_ast, const_args); |
947 | | |
948 | 212 | if (!forwarding_ast) { |
949 | 0 | ZEND_ASSERT(EG(exception)); |
950 | 0 | zend_ast_destroy(no_forwarding_ast); |
951 | 0 | goto error; |
952 | 0 | } |
953 | | |
954 | 212 | zend_ast *func_num_args_name_ast = zend_ast_create_zval_from_str( |
955 | 212 | zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_NUM_ARGS))); |
956 | 212 | func_num_args_name_ast->attr = ZEND_NAME_FQ; |
957 | | |
958 | 212 | stmts_ast = zend_ast_list_add(stmts_ast, |
959 | 212 | zend_ast_create_list(2, ZEND_AST_IF, |
960 | 212 | zend_ast_create(ZEND_AST_IF_ELEM, |
961 | 212 | zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL, |
962 | 212 | zend_ast_create(ZEND_AST_CALL, func_num_args_name_ast, |
963 | 212 | zend_ast_create_list(0, ZEND_AST_ARG_LIST)), |
964 | 212 | zend_ast_create_zval_from_long(num_params)), |
965 | 212 | no_forwarding_ast), |
966 | 212 | zend_ast_create(ZEND_AST_IF_ELEM, |
967 | 212 | NULL, |
968 | 212 | forwarding_ast))); |
969 | 863 | } else { |
970 | 863 | stmts_ast = zp_compile_forwarding_call(this_ptr, function, |
971 | 863 | argc, argv, extra_named_params, |
972 | 863 | var_names, uses_variadic_placeholder, num_params, |
973 | 863 | called_scope, return_type, false, stmts_ast, const_args); |
974 | | |
975 | 863 | if (!stmts_ast) { |
976 | 29 | ZEND_ASSERT(EG(exception)); |
977 | 29 | goto error; |
978 | 29 | } |
979 | 863 | } |
980 | | |
981 | | /* Inherit the NoDiscard attribute */ |
982 | 1.04k | if (function->common.attributes) { |
983 | 31 | zend_attribute *attr = zend_get_attribute_str( |
984 | 31 | function->common.attributes, "nodiscard", strlen("nodiscard")); |
985 | 31 | if (attr) { |
986 | 15 | attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP, |
987 | 15 | zp_attribute_to_ast(attr)); |
988 | 15 | attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST, |
989 | 15 | attributes_ast); |
990 | 15 | } |
991 | 31 | } |
992 | | |
993 | 1.04k | int closure_flags = function->common.fn_flags & ZEND_ACC_RETURN_REFERENCE; |
994 | 1.04k | zend_ast *closure_ast = zend_ast_create_decl(ZEND_AST_CLOSURE, |
995 | 1.04k | closure_flags, CG(zend_lineno), NULL, |
996 | 1.04k | NULL, params_ast, lexical_vars_ast, stmts_ast, |
997 | 1.04k | return_type_ast, attributes_ast); |
998 | | |
999 | 1.04k | if (Z_TYPE_P(this_ptr) != IS_OBJECT && !zp_is_non_static_closure(function)) { |
1000 | 821 | ((zend_ast_decl*)closure_ast)->flags |= ZEND_ACC_STATIC; |
1001 | 821 | } |
1002 | | |
1003 | 1.04k | #if ZEND_DEBUG |
1004 | 1.04k | { |
1005 | 1.04k | const char *tmp = getenv("DUMP_PFA_AST"); |
1006 | 1.04k | if (tmp && ZEND_ATOL(tmp)) { |
1007 | 0 | zend_string *str = zend_ast_export("", closure_ast, ""); |
1008 | 0 | fprintf(stderr, "PFA AST: %s\n", ZSTR_VAL(str)); |
1009 | 0 | zend_string_release(str); |
1010 | 0 | } |
1011 | 1.04k | } |
1012 | 1.04k | #endif |
1013 | | |
1014 | 1.04k | op_array = zend_accel_compile_pfa(closure_ast, declaring_filename, |
1015 | 1.04k | declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); |
1016 | | |
1017 | 1.04k | zend_ast_destroy(closure_ast); |
1018 | | |
1019 | 1.08k | clean: |
1020 | 1.08k | zp_names_dtor(var_names, argc); |
1021 | 1.08k | zend_arena_destroy(CG(ast_arena)); |
1022 | 1.08k | CG(ast_arena) = orig_ast_arena; |
1023 | 1.08k | CG(zend_lineno) = orig_lineno; |
1024 | | |
1025 | 1.08k | return op_array; |
1026 | | |
1027 | 40 | error: |
1028 | 40 | zend_ast_destroy(lexical_vars_ast); |
1029 | 40 | zend_ast_destroy(params_ast); |
1030 | 40 | zend_ast_destroy(return_type_ast); |
1031 | 40 | zend_ast_destroy(stmts_ast); |
1032 | 40 | zend_ast_destroy(attributes_ast); |
1033 | 40 | goto clean; |
1034 | 1.04k | } |
1035 | | |
1036 | | /* Get the op_array of a PFA from caches or compile it */ |
1037 | | static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, |
1038 | | uint32_t argc, zval *argv, zend_array *extra_named_params, |
1039 | | const zend_array *named_positions, |
1040 | | zend_string *declaring_filename, |
1041 | | const uint32_t *declaring_lineno_ptr, void **cache_slot, |
1042 | 1.47k | zend_string *pfa_name, uint32_t flags, uint32_t const_args) { |
1043 | | |
1044 | 1.47k | if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION |
1045 | 1.47k | ? cache_slot[0] == function |
1046 | 1.47k | : cache_slot[0] == function->op_array.opcodes)) { |
1047 | 0 | ZEND_ASSERT(!(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)); |
1048 | 0 | return cache_slot[1]; |
1049 | 0 | } |
1050 | | |
1051 | 1.47k | const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function, |
1052 | 1.47k | flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); |
1053 | | |
1054 | 1.47k | if (UNEXPECTED(!op_array)) { |
1055 | 1.19k | op_array = zp_compile(this_ptr, function, argc, argv, |
1056 | 1.19k | extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr, |
1057 | 1.19k | cache_slot, pfa_name, flags, const_args); |
1058 | 1.19k | } |
1059 | | |
1060 | 1.47k | if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) { |
1061 | 1.30k | cache_slot[0] = function->type == ZEND_INTERNAL_FUNCTION |
1062 | 1.30k | ? (void*)function |
1063 | 1.30k | : (void*)function->op_array.opcodes; |
1064 | 1.30k | cache_slot[1] = (zend_op_array*)op_array; |
1065 | 1.30k | } |
1066 | | |
1067 | 1.47k | return op_array; |
1068 | 1.47k | } |
1069 | | |
1070 | | static void zp_free_unbound_args(uint32_t start, uint32_t argc, zval *argv) |
1071 | 177 | { |
1072 | 536 | for (uint32_t offset = start; offset < argc; offset++) { |
1073 | 359 | zval_ptr_dtor_nogc(&argv[offset]); |
1074 | 359 | } |
1075 | 177 | } |
1076 | | |
1077 | | /* Bind pre-bound arguments as lexical vars */ |
1078 | | static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *argv, |
1079 | 1.33k | zend_array *extra_named_params, uint32_t const_args) { |
1080 | | |
1081 | 1.33k | zend_arg_info *arg_infos = function->common.arg_info; |
1082 | 1.33k | uint32_t bind_offset = 0; |
1083 | | |
1084 | 1.33k | if (function->common.fn_flags & ZEND_ACC_CLOSURE) { |
1085 | 174 | zval var; |
1086 | 174 | ZVAL_OBJ(&var, ZEND_CLOSURE_OBJECT(function)); |
1087 | 174 | Z_ADDREF(var); |
1088 | 174 | zend_closure_bind_var_ex(result, bind_offset, &var); |
1089 | 174 | bind_offset += sizeof(Bucket); |
1090 | 174 | } |
1091 | | |
1092 | 3.58k | for (uint32_t offset = 0; offset < argc; offset++) { |
1093 | 2.28k | zval *var = &argv[offset]; |
1094 | 2.28k | if (Z_IS_PLACEHOLDER_P(var) || Z_ISUNDEF_P(var) || zp_is_const_arg(const_args, offset)) { |
1095 | 1.74k | continue; |
1096 | 1.74k | } |
1097 | 537 | zend_arg_info *arg_info; |
1098 | 537 | if (offset < function->common.num_args) { |
1099 | 470 | arg_info = &arg_infos[offset]; |
1100 | 470 | } else if (function->common.fn_flags & ZEND_ACC_VARIADIC) { |
1101 | 67 | arg_info = &arg_infos[function->common.num_args]; |
1102 | 67 | } else { |
1103 | 0 | arg_info = NULL; |
1104 | 0 | } |
1105 | 537 | if (arg_info && ZEND_TYPE_IS_SET(arg_info->type) |
1106 | 243 | && UNEXPECTED(!zend_check_type_ex(&arg_info->type, var, |
1107 | 537 | /* current_frame */ true, /* is_internal */ false))) { |
1108 | 32 | zend_string *need_msg = zend_type_to_string_resolved(arg_info->type, |
1109 | 32 | function->common.scope); |
1110 | 32 | zend_argument_type_error_ex(function, offset + 1, |
1111 | 32 | "must be of type %s, %s given", |
1112 | 32 | ZSTR_VAL(need_msg), zend_zval_value_name(var)); |
1113 | 32 | zend_string_release(need_msg); |
1114 | 32 | zval_ptr_dtor(result); |
1115 | 32 | ZVAL_NULL(result); |
1116 | 32 | zp_free_unbound_args(offset, argc, argv); |
1117 | 32 | return; |
1118 | 32 | } |
1119 | 505 | ZEND_ASSERT(zp_arg_must_be_sent_by_ref(function, offset+1) ? Z_ISREF_P(var) : !Z_ISREF_P(var)); |
1120 | 505 | zend_closure_bind_var_ex(result, bind_offset, var); |
1121 | 505 | bind_offset += sizeof(Bucket); |
1122 | 505 | } |
1123 | | |
1124 | 1.30k | if (extra_named_params) { |
1125 | 98 | zval var; |
1126 | 98 | ZVAL_ARR(&var, extra_named_params); |
1127 | 98 | Z_ADDREF(var); |
1128 | 98 | zend_closure_bind_var_ex(result, bind_offset, &var); |
1129 | 98 | } |
1130 | 1.30k | } |
1131 | | |
1132 | | void zend_partial_create(zval *result, zend_class_entry *scope, zval *this_ptr, zend_function *function, |
1133 | | uint32_t argc, zval *argv, zend_array *extra_named_params, |
1134 | | const zend_array *named_positions, |
1135 | | zend_string *declaring_filename, |
1136 | | const uint32_t *declaring_lineno_ptr, void **cache_slot, |
1137 | 1.47k | zend_string *pfa_name, uint32_t flags, uint32_t const_args) { |
1138 | | |
1139 | 1.47k | ZEND_ASSERT(pfa_name); |
1140 | | |
1141 | 1.47k | const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, |
1142 | 1.47k | extra_named_params, named_positions, |
1143 | 1.47k | declaring_filename, declaring_lineno_ptr, |
1144 | 1.47k | cache_slot, pfa_name, flags, const_args); |
1145 | | |
1146 | 1.47k | if (UNEXPECTED(!op_array)) { |
1147 | 145 | ZEND_ASSERT(EG(exception)); |
1148 | 145 | zp_free_unbound_args(0, argc, argv); |
1149 | 145 | ZVAL_NULL(result); |
1150 | 145 | return; |
1151 | 145 | } |
1152 | | |
1153 | 1.33k | zend_class_entry *called_scope; |
1154 | 1.33k | zend_object *object; |
1155 | | |
1156 | 1.33k | if (Z_TYPE_P(this_ptr) == IS_OBJECT) { |
1157 | 221 | called_scope = Z_OBJCE_P(this_ptr); |
1158 | 1.11k | } else { |
1159 | 1.11k | called_scope = Z_CE_P(this_ptr); |
1160 | 1.11k | } |
1161 | | |
1162 | 1.33k | if (Z_TYPE_P(this_ptr) == IS_OBJECT && !zp_is_static_closure(function)) { |
1163 | 221 | object = Z_OBJ_P(this_ptr); |
1164 | 1.11k | } else { |
1165 | 1.11k | object = NULL; |
1166 | 1.11k | } |
1167 | | |
1168 | | |
1169 | | /* We conveniently use the function's scope for the scope of the generated closure as this allows const exprs |
1170 | | * referencing self:: or parent:: to behave normally without rewriting them. |
1171 | | * This affects method resolution for magic methods, so use the actual scope for them. */ |
1172 | 1.33k | if (!(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { |
1173 | 1.22k | scope = function->common.scope; |
1174 | 1.22k | } |
1175 | | |
1176 | 1.33k | zend_create_partial_closure(result, (zend_function*)op_array, |
1177 | 1.33k | scope, called_scope, object, |
1178 | 1.33k | (function->common.fn_flags & ZEND_ACC_CLOSURE) != 0); |
1179 | | |
1180 | 1.33k | zp_bind(result, function, argc, argv, extra_named_params, const_args); |
1181 | 1.33k | } |
1182 | | |
1183 | | void zend_partial_op_array_dtor(zval *pDest) |
1184 | 114 | { |
1185 | 114 | destroy_op_array(Z_PTR_P(pDest)); |
1186 | 114 | } |