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