Coverage Report

Created: 2026-09-14 06:25

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