Coverage Report

Created: 2026-10-28 06:20

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