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