mirror of
https://github.com/php/php-src.git
synced 2026-10-28 17:56:50 +08:00
1189 lines
39 KiB
C
1189 lines
39 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Zend Engine |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright © Zend Technologies Ltd., a subsidiary company of |
|
|
| Perforce Software, Inc., and Contributors. |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to the Modified BSD License that is |
|
|
| bundled with this package in the file LICENSE, and is available |
|
|
| through the World Wide Web at <https://www.php.net/license/>. |
|
|
| |
|
|
| SPDX-License-Identifier: BSD-3-Clause |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Arnaud Le Blanc <arnaud.lb@gmail.com> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* Partial Function Application:
|
|
*
|
|
* A partial application is compiled to the usual sequence of function call
|
|
* opcodes (INIT_FCALL, SEND_VAR, etc), but the sequence ends with a
|
|
* CALLABLE_CONVERT_PARTIAL opcode instead of DO_FCALL, similarly to
|
|
* first class callables. Placeholders are compiled to SEND_PLACEHOLDER opcodes:
|
|
*
|
|
* $f = f($a, ?)
|
|
*
|
|
* 0001 INIT_FCALL f
|
|
* 0002 SEND_VAR CV($a)
|
|
* 0003 SEND_PLACEHOLDER
|
|
* 0004 CV($f) = CALLABLE_CONVERT_PARTIAL
|
|
*
|
|
* SEND_PLACEHOLDER sets the argument slot type to _IS_PLACEHOLDER.
|
|
*
|
|
* CALLABLE_CONVERT_PARTIAL uses the information available on the stack to
|
|
* create a Closure and return it, consuming the stack frame in the process
|
|
* like an internal function call.
|
|
*
|
|
* We create the Closure by generating the relevant AST and compiling it to an
|
|
* op_array. The op_array is cached in the Opcache SHM and inline caches.
|
|
*
|
|
* This file implements the Closure generation logic
|
|
* (see zend_partial_create(), zp_compile()).
|
|
*/
|
|
|
|
#include "zend.h"
|
|
#include "zend_API.h"
|
|
#include "zend_arena.h"
|
|
#include "zend_ast.h"
|
|
#include "zend_compile.h"
|
|
#include "zend_closures.h"
|
|
#include "zend_attributes.h"
|
|
#include "zend_exceptions.h"
|
|
#include "zend_partial.h"
|
|
#include "ext/opcache/ZendAccelerator.h"
|
|
|
|
static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) {
|
|
return Z_TYPE_P(p) == _IS_PLACEHOLDER;
|
|
}
|
|
|
|
static zend_always_inline bool zp_is_static_closure(const zend_function *function) {
|
|
return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE));
|
|
}
|
|
|
|
static zend_always_inline bool zp_is_non_static_closure(const zend_function *function) {
|
|
return ((function->common.fn_flags & (ZEND_ACC_STATIC|ZEND_ACC_CLOSURE)) == ZEND_ACC_CLOSURE);
|
|
}
|
|
|
|
/* Whether argument at offset 'offset' is const. Such arguments can be burned into the generated op_array */
|
|
static inline bool zp_is_const_arg(uint32_t const_args, uint32_t offset) {
|
|
if (offset < sizeof(const_args) * CHAR_BIT) {
|
|
return const_args & (UINT32_C(1) << offset);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static zend_never_inline ZEND_COLD void zp_args_underflow(
|
|
const zend_function *function, uint32_t args, uint32_t expected)
|
|
{
|
|
zend_string *symbol = get_function_or_method_name(function);
|
|
const char *limit = function->common.num_args <= function->common.required_num_args ?
|
|
"exactly" : "at least";
|
|
|
|
zend_argument_count_error(
|
|
"Partial application of %s() expects %s %d arguments, %d given",
|
|
ZSTR_VAL(symbol), limit, expected, args);
|
|
|
|
zend_string_release(symbol);
|
|
}
|
|
|
|
static zend_never_inline ZEND_COLD void zp_args_overflow(
|
|
const zend_function *function, uint32_t args, uint32_t expected)
|
|
{
|
|
zend_string *symbol = get_function_or_method_name(function);
|
|
|
|
zend_argument_count_error(
|
|
"Partial application of %s() expects at most %d arguments, %d given",
|
|
ZSTR_VAL(symbol), expected, args);
|
|
|
|
zend_string_release(symbol);
|
|
}
|
|
|
|
static zend_result zp_args_check(const zend_function *function,
|
|
uint32_t argc, const zval *argv,
|
|
const zend_array *extra_named_args,
|
|
bool uses_variadic_placeholder) {
|
|
|
|
if (extra_named_args) {
|
|
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(extra_named_args, zend_string *key, zval *arg) {
|
|
if (UNEXPECTED(Z_IS_PLACEHOLDER_P(arg))) {
|
|
zend_throw_error(NULL,
|
|
"Cannot use named placeholder for unknown or variadic parameter $%s",
|
|
ZSTR_VAL(key));
|
|
return FAILURE;
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
|
|
if (argc < function->common.required_num_args) {
|
|
if (uses_variadic_placeholder) {
|
|
/* Missing args will be turned into placeholders */
|
|
return SUCCESS;
|
|
}
|
|
|
|
zp_args_underflow(function, argc, function->common.required_num_args);
|
|
return FAILURE;
|
|
} else if (argc > function->common.num_args
|
|
&& !(function->common.fn_flags & ZEND_ACC_VARIADIC)) {
|
|
zp_args_overflow(function, argc, function->common.num_args);
|
|
return FAILURE;
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
typedef struct zp_names {
|
|
zend_string *variadic_param;
|
|
zend_string *extra_named_params;
|
|
zend_string *inner_closure;
|
|
zend_string *params[1]; /* argc elements */
|
|
} zp_names;
|
|
|
|
static bool zp_name_exists(const zp_names *names, uint32_t argc, const zend_string *name)
|
|
{
|
|
for (uint32_t i = 0; i < argc; i++) {
|
|
if (names->params[i] && zend_string_equals(names->params[i], name)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (names->variadic_param && zend_string_equals(names->variadic_param, name)) {
|
|
return true;
|
|
}
|
|
if (names->extra_named_params && zend_string_equals(names->extra_named_params, name)) {
|
|
return true;
|
|
}
|
|
if (names->inner_closure && zend_string_equals(names->inner_closure, name)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void zp_names_dtor(zp_names *names, uint32_t argc)
|
|
{
|
|
for (uint32_t i = 0; i < argc; i++) {
|
|
if (names->params[i]) {
|
|
zend_string_release(names->params[i]);
|
|
}
|
|
}
|
|
if (names->variadic_param) {
|
|
zend_string_release(names->variadic_param);
|
|
}
|
|
if (names->extra_named_params) {
|
|
zend_string_release(names->extra_named_params);
|
|
}
|
|
if (names->inner_closure) {
|
|
zend_string_release(names->inner_closure);
|
|
}
|
|
}
|
|
|
|
static zend_string *zp_get_func_param_name(const zend_function *function, uint32_t arg_offset)
|
|
{
|
|
return zend_string_copy(function->common.arg_info[arg_offset].name);
|
|
}
|
|
|
|
/* Assign a name for every variable that will be used in the generated closure,
|
|
* including params and used vars. */
|
|
static zp_names *zp_assign_names(uint32_t argc, zval *argv,
|
|
zend_function *function, bool variadic_partial,
|
|
zend_array *extra_named_params, uint32_t const_args)
|
|
{
|
|
zp_names *names = zend_arena_calloc(&CG(ast_arena),
|
|
1, zend_safe_address_guarded(argc, sizeof(*names->params), offsetof(zp_names, params)));
|
|
|
|
/* Assign names for params. We never rename those. */
|
|
for (uint32_t offset = 0, num_args = MIN(argc, function->common.num_args);
|
|
offset < num_args; offset++) {
|
|
if (Z_IS_PLACEHOLDER_P(&argv[offset])) {
|
|
names->params[offset] = zp_get_func_param_name(function, offset);
|
|
}
|
|
}
|
|
|
|
/* Assign name for the variadic param. Never renamed. */
|
|
if (variadic_partial && (function->common.fn_flags & ZEND_ACC_VARIADIC)) {
|
|
names->variadic_param = zp_get_func_param_name(function, function->common.num_args);
|
|
}
|
|
|
|
/* Assign names for placeholders that bind to the variadic param:
|
|
*
|
|
* function f($a, ...$args) {}
|
|
* f(?, ?, ...); // The second placeholder binds into the variadic param.
|
|
*
|
|
* By default these are named $origNameN with N the offset from the
|
|
* variadic param. In case of clash we increment N until a free name is
|
|
* found. */
|
|
for (uint32_t offset = function->common.num_args; offset < argc; offset++) {
|
|
ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC);
|
|
if (!Z_IS_PLACEHOLDER_P(&argv[offset])) {
|
|
continue;
|
|
}
|
|
zend_string *orig_name = zp_get_func_param_name(function, function->common.num_args);
|
|
zend_string *new_name;
|
|
for (uint32_t n = 0;; n++) {
|
|
new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n);
|
|
if (!zp_name_exists(names, argc, new_name)) {
|
|
break;
|
|
}
|
|
zend_string_release(new_name);
|
|
}
|
|
names->params[offset] = new_name;
|
|
zend_string_release(orig_name);
|
|
}
|
|
|
|
/* Assign names for pre-bound params (lexical vars).
|
|
* There may be clashes, we ensure to generate unique names. */
|
|
for (uint32_t offset = 0; offset < argc; offset++) {
|
|
if (Z_IS_PLACEHOLDER_P(&argv[offset]) || Z_ISUNDEF(argv[offset]) || zp_is_const_arg(const_args, offset)) {
|
|
continue;
|
|
}
|
|
uint32_t n = 2;
|
|
zend_string *orig_name = zp_get_func_param_name(function, MIN(offset, function->common.num_args));
|
|
zend_string *new_name = zend_string_copy(orig_name);
|
|
while (zp_name_exists(names, argc, new_name)) {
|
|
zend_string_release(new_name);
|
|
new_name = zend_strpprintf_unchecked(0, "%S%" PRIu32, orig_name, n);
|
|
n++;
|
|
}
|
|
names->params[offset] = new_name;
|
|
zend_string_release(orig_name);
|
|
}
|
|
|
|
/* Assign name for $extra_named_params */
|
|
if (extra_named_params) {
|
|
uint32_t n = 2;
|
|
zend_string *new_name = ZSTR_INIT_LITERAL("extra_named_params", 0);
|
|
while (zp_name_exists(names, argc, new_name)) {
|
|
zend_string_release(new_name);
|
|
new_name = zend_strpprintf(0, "%s%" PRIu32, "extra_named_params", n);
|
|
n++;
|
|
}
|
|
names->extra_named_params = new_name;
|
|
}
|
|
|
|
/* Assign name for $fn */
|
|
if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
|
|
uint32_t n = 2;
|
|
zend_string *new_name = ZSTR_INIT_LITERAL("fn", 0);
|
|
while (zp_name_exists(names, argc, new_name)) {
|
|
zend_string_release(new_name);
|
|
new_name = zend_strpprintf(0, "%s%" PRIu32, "fn", n);
|
|
n++;
|
|
}
|
|
names->inner_closure = new_name;
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
ZEND_ATTRIBUTE_CONST static inline bool zp_is_power_of_two(uint32_t x)
|
|
{
|
|
return (x > 0) && !(x & (x - 1));
|
|
}
|
|
|
|
ZEND_ATTRIBUTE_CONST static bool zp_is_simple_type(uint32_t type_mask)
|
|
{
|
|
ZEND_ASSERT(!(type_mask & ~_ZEND_TYPE_MAY_BE_MASK));
|
|
|
|
return zp_is_power_of_two(type_mask)
|
|
|| type_mask == MAY_BE_BOOL
|
|
|| type_mask == MAY_BE_ANY;
|
|
}
|
|
|
|
static zend_ast *zp_simple_type_to_ast(uint32_t type)
|
|
{
|
|
zend_string *name;
|
|
|
|
switch (type) {
|
|
case MAY_BE_NULL:
|
|
name = ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE);
|
|
break;
|
|
case MAY_BE_TRUE:
|
|
name = ZSTR_KNOWN(ZEND_STR_TRUE);
|
|
break;
|
|
case MAY_BE_FALSE:
|
|
name = ZSTR_KNOWN(ZEND_STR_FALSE);
|
|
break;
|
|
case MAY_BE_LONG:
|
|
name = ZSTR_KNOWN(ZEND_STR_INT);
|
|
break;
|
|
case MAY_BE_DOUBLE:
|
|
name = ZSTR_KNOWN(ZEND_STR_FLOAT);
|
|
break;
|
|
case MAY_BE_STRING:
|
|
name = ZSTR_KNOWN(ZEND_STR_STRING);
|
|
break;
|
|
case MAY_BE_BOOL:
|
|
name = ZSTR_KNOWN(ZEND_STR_BOOL);
|
|
break;
|
|
case MAY_BE_VOID:
|
|
name = ZSTR_KNOWN(ZEND_STR_VOID);
|
|
break;
|
|
case MAY_BE_NEVER:
|
|
name = ZSTR_KNOWN(ZEND_STR_NEVER);
|
|
break;
|
|
case MAY_BE_OBJECT:
|
|
name = ZSTR_KNOWN(ZEND_STR_OBJECT);
|
|
break;
|
|
case MAY_BE_ANY:
|
|
name = ZSTR_KNOWN(ZEND_STR_MIXED);
|
|
break;
|
|
case MAY_BE_CALLABLE:
|
|
return zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE);
|
|
case MAY_BE_ARRAY:
|
|
return zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY);
|
|
case MAY_BE_STATIC:
|
|
return zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC);
|
|
default:
|
|
ZEND_UNREACHABLE();
|
|
}
|
|
|
|
zend_ast *ast = zend_ast_create_zval_from_str(name);
|
|
ast->attr = ZEND_NAME_NOT_FQ;
|
|
|
|
return ast;
|
|
}
|
|
|
|
static zend_ast *zp_type_name_to_ast(zend_string *name)
|
|
{
|
|
zend_ast *ast = zend_ast_create_zval_from_str(name);
|
|
|
|
if (zend_get_class_fetch_type(name) != ZEND_FETCH_CLASS_DEFAULT) {
|
|
ast->attr = ZEND_NAME_NOT_FQ;
|
|
} else {
|
|
ast->attr = ZEND_NAME_FQ;
|
|
}
|
|
|
|
return ast;
|
|
}
|
|
|
|
static zend_ast *zp_type_to_ast(const zend_type type)
|
|
{
|
|
if (!ZEND_TYPE_IS_SET(type)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (ZEND_TYPE_IS_UNION(type)
|
|
|| (ZEND_TYPE_IS_COMPLEX(type) && ZEND_TYPE_PURE_MASK(type))
|
|
|| (ZEND_TYPE_PURE_MASK(type) && !zp_is_simple_type(ZEND_TYPE_PURE_MASK(type)))) {
|
|
/* This is a union type */
|
|
|
|
zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_UNION);
|
|
|
|
/* Add complex types if any */
|
|
if (ZEND_TYPE_HAS_LIST(type)) {
|
|
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), const zend_type *type_ptr) {
|
|
type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr));
|
|
} ZEND_TYPE_LIST_FOREACH_END();
|
|
} else if (ZEND_TYPE_HAS_NAME(type)) {
|
|
zend_ast *name_ast = zp_type_name_to_ast(
|
|
zend_string_copy(ZEND_TYPE_NAME(type)));
|
|
type_ast = zend_ast_list_add(type_ast, name_ast);
|
|
} else {
|
|
ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type));
|
|
}
|
|
|
|
/* Add simple types if any */
|
|
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
|
|
|
/* IS_TRUE|IS_FALSE is represented as a single bool node */
|
|
if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
|
|
type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(MAY_BE_BOOL));
|
|
type_mask &= ~MAY_BE_BOOL;
|
|
}
|
|
for (uint32_t may_be_type = 1; may_be_type < _ZEND_TYPE_MAY_BE_MASK; may_be_type <<= 1) {
|
|
if (type_mask & may_be_type) {
|
|
type_ast = zend_ast_list_add(type_ast, zp_simple_type_to_ast(may_be_type));
|
|
}
|
|
}
|
|
|
|
return type_ast;
|
|
}
|
|
|
|
if (ZEND_TYPE_IS_INTERSECTION(type)) {
|
|
ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type));
|
|
zend_ast *type_ast = zend_ast_create_list(0, ZEND_AST_TYPE_INTERSECTION);
|
|
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), const zend_type *type_ptr) {
|
|
type_ast = zend_ast_list_add(type_ast, zp_type_to_ast(*type_ptr));
|
|
} ZEND_TYPE_LIST_FOREACH_END();
|
|
return type_ast;
|
|
}
|
|
|
|
if (ZEND_TYPE_HAS_NAME(type)) {
|
|
ZEND_ASSERT(!ZEND_TYPE_PURE_MASK(type));
|
|
zend_ast *type_ast = zp_type_name_to_ast(
|
|
zend_string_copy(ZEND_TYPE_NAME(type)));
|
|
return type_ast;
|
|
}
|
|
|
|
ZEND_ASSERT(!ZEND_TYPE_IS_COMPLEX(type));
|
|
|
|
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
|
ZEND_ASSERT(zp_is_simple_type(type_mask));
|
|
|
|
return zp_simple_type_to_ast(type_mask);
|
|
}
|
|
|
|
static zend_result zp_get_param_default_value(zval *result, zend_function *function, uint32_t arg_offset)
|
|
{
|
|
ZEND_ASSERT(arg_offset < function->common.num_args);
|
|
|
|
if (function->type == ZEND_USER_FUNCTION) {
|
|
zend_op *opline = &function->op_array.opcodes[arg_offset];
|
|
if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) {
|
|
ZVAL_COPY(result, RT_CONSTANT(opline, opline->op2));
|
|
return SUCCESS;
|
|
}
|
|
ZEND_ASSERT(opline->opcode == ZEND_RECV);
|
|
} else {
|
|
if (function->common.fn_flags & ZEND_ACC_USER_ARG_INFO) {
|
|
goto error;
|
|
}
|
|
|
|
const zend_arg_info *arg_info = &function->internal_function.arg_info[arg_offset];
|
|
|
|
if (zend_get_default_from_internal_arg_info(result, arg_info) == SUCCESS) {
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
|
|
error:
|
|
zend_argument_error_ex(zend_ce_argument_count_error, function, arg_offset + 1,
|
|
"must be passed explicitly, because the default value is not known");
|
|
|
|
return FAILURE;
|
|
}
|
|
|
|
static bool zp_arg_must_be_sent_by_ref(const zend_function *function, uint32_t arg_num)
|
|
{
|
|
if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) {
|
|
if (QUICK_ARG_MUST_BE_SENT_BY_REF(function, arg_num)) {
|
|
return true;
|
|
}
|
|
} else if (ARG_MUST_BE_SENT_BY_REF(function, arg_num)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static zend_ast *zp_attribute_to_ast(zend_attribute *attribute)
|
|
{
|
|
zend_ast *args_ast;
|
|
if (attribute->argc) {
|
|
args_ast = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST);
|
|
for (uint32_t i = 0; i < attribute->argc; i++) {
|
|
zval *zv = &attribute->args[i].value;
|
|
Z_TRY_ADDREF_P(zv);
|
|
zend_ast *arg_ast = zend_ast_create_zval(zv);
|
|
if (attribute->args[i].name) {
|
|
arg_ast = zend_ast_create(ZEND_AST_NAMED_ARG,
|
|
zend_ast_create_zval_from_str(
|
|
zend_string_copy(attribute->args[i].name)),
|
|
arg_ast);
|
|
}
|
|
args_ast = zend_ast_list_add(args_ast, arg_ast);
|
|
}
|
|
} else {
|
|
args_ast = NULL;
|
|
}
|
|
return zend_ast_create(ZEND_AST_ATTRIBUTE,
|
|
zend_ast_create_zval_from_str(zend_string_copy(attribute->name)),
|
|
args_ast);
|
|
}
|
|
|
|
static zend_ast *zp_param_attributes_to_ast(zend_function *function,
|
|
uint32_t offset)
|
|
{
|
|
zend_ast *attributes_ast = NULL;
|
|
if (!function->common.attributes) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Inherit the SensitiveParameter attribute */
|
|
zend_attribute *attr = zend_get_parameter_attribute_str(
|
|
function->common.attributes,
|
|
"sensitiveparameter", strlen("sensitiveparameter"), offset);
|
|
if (attr) {
|
|
attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP,
|
|
zp_attribute_to_ast(attr));
|
|
attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST,
|
|
attributes_ast);
|
|
}
|
|
|
|
return attributes_ast;
|
|
}
|
|
|
|
/* Generate the AST for calling the actual function */
|
|
static zend_ast *zp_compile_forwarding_call(
|
|
zval *this_ptr, zend_function *function,
|
|
uint32_t argc, zval *argv, zend_array *extra_named_params,
|
|
zp_names *var_names, bool uses_variadic_placeholder, uint32_t num_args,
|
|
zend_class_entry *called_scope, zend_type return_type,
|
|
bool forward_superfluous_args,
|
|
zend_ast *stmts_ast, uint32_t const_args)
|
|
{
|
|
bool is_assert = zend_string_equals(function->common.function_name,
|
|
ZSTR_KNOWN(ZEND_STR_ASSERT));
|
|
|
|
zend_ast *args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
|
|
zend_ast *call_ast = NULL;
|
|
|
|
if (is_assert) {
|
|
/* We are going to call assert() dynamically (via call_user_func),
|
|
* otherwise assert() would print the generated AST on failure, which is
|
|
* irrelevant. */
|
|
args_ast = zend_ast_list_add(args_ast,
|
|
zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ASSERT)));
|
|
}
|
|
|
|
/* Generate positional arguments */
|
|
for (uint32_t offset = 0; offset < argc; offset++) {
|
|
if (Z_ISUNDEF(argv[offset])) {
|
|
/* Argument was not passed. Pass its default value. */
|
|
if (offset < function->common.required_num_args) {
|
|
/* Required param was not passed. This can happen due to named
|
|
* args. Using the same exception CE and message as
|
|
* zend_handle_undef_args(). */
|
|
zend_argument_error_ex(zend_ce_argument_count_error, function, offset + 1, "not passed");
|
|
goto error;
|
|
}
|
|
zval default_value;
|
|
if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) {
|
|
ZEND_ASSERT(EG(exception));
|
|
goto error;
|
|
}
|
|
zend_ast *default_value_ast;
|
|
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
|
|
/* Must dup AST because we are going to destroy it */
|
|
default_value_ast = zend_ast_dup(Z_ASTVAL(default_value));
|
|
zval_ptr_dtor_nogc(&default_value);
|
|
} else {
|
|
default_value_ast = zend_ast_create_zval(&default_value);
|
|
}
|
|
args_ast = zend_ast_list_add(args_ast, default_value_ast);
|
|
} else if (zp_is_const_arg(const_args, offset)) {
|
|
ZEND_ASSERT(Z_TYPE(argv[offset]) < IS_OBJECT);
|
|
ZEND_ASSERT(!Z_REFCOUNTED(argv[offset]));
|
|
|
|
/* This argument never changes, so we can burn it into the op_array
|
|
* and check its type ahead of time.
|
|
* Work with a value copy because a scalar type check may coerce this value. */
|
|
zval value;
|
|
ZVAL_COPY_VALUE(&value, &argv[offset]);
|
|
|
|
zend_arg_info *arg_info;
|
|
if (offset < function->common.num_args) {
|
|
arg_info = &function->common.arg_info[offset];
|
|
} else if (function->common.fn_flags & ZEND_ACC_VARIADIC) {
|
|
arg_info = &function->common.arg_info[function->common.num_args];
|
|
} else {
|
|
arg_info = NULL;
|
|
}
|
|
if (arg_info && ZEND_TYPE_IS_SET(arg_info->type)
|
|
&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, &value,
|
|
/* current_frame */ true, /* is_internal */ false))) {
|
|
zend_string *need_msg = zend_type_to_string_resolved(arg_info->type,
|
|
function->common.scope);
|
|
zend_argument_type_error_ex(function, offset + 1,
|
|
"must be of type %s, %s given",
|
|
ZSTR_VAL(need_msg), zend_zval_value_name(&value));
|
|
zend_string_release(need_msg);
|
|
goto error;
|
|
}
|
|
|
|
args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&value));
|
|
} else {
|
|
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,
|
|
zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset]))));
|
|
}
|
|
}
|
|
/* Use unpacking to pass extra named params */
|
|
if (extra_named_params) {
|
|
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
|
|
zend_ast_create(ZEND_AST_VAR,
|
|
zend_ast_create_zval_from_str(zend_string_copy(var_names->extra_named_params)))));
|
|
}
|
|
if (uses_variadic_placeholder) {
|
|
if (function->common.fn_flags & ZEND_ACC_VARIADIC) {
|
|
/* Pass variadic params */
|
|
args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_UNPACK,
|
|
zend_ast_create(ZEND_AST_VAR,
|
|
zend_ast_create_zval_from_str(zend_string_copy(var_names->variadic_param)))));
|
|
} else if (forward_superfluous_args) {
|
|
/* When a '...' placeholder is used, and the underlying function is
|
|
* not variadic, superfluous arguments are forwarded.
|
|
* Add a ...array_slice(func_get_args(), n) argument, which should
|
|
* be compiled as ZEND_AST_UNPACK + ZEND_FUNC_GET_ARGS. */
|
|
|
|
zend_ast *func_get_args_name_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_GET_ARGS)));
|
|
func_get_args_name_ast->attr = ZEND_NAME_FQ;
|
|
|
|
zend_ast *array_slice_name_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(ZSTR_KNOWN(ZEND_STR_ARRAY_SLICE)));
|
|
array_slice_name_ast->attr = ZEND_NAME_FQ;
|
|
|
|
args_ast = zend_ast_list_add(args_ast,
|
|
zend_ast_create(ZEND_AST_UNPACK,
|
|
zend_ast_create(ZEND_AST_CALL,
|
|
array_slice_name_ast,
|
|
zend_ast_create_list(2, ZEND_AST_ARG_LIST,
|
|
zend_ast_create(ZEND_AST_CALL,
|
|
func_get_args_name_ast,
|
|
zend_ast_create_list(0, ZEND_AST_ARG_LIST)),
|
|
zend_ast_create_zval_from_long(num_args)))));
|
|
}
|
|
}
|
|
|
|
if (is_assert) {
|
|
zend_ast *func_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CALL_USER_FUNC));
|
|
func_name_ast->attr = ZEND_NAME_FQ;
|
|
call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast);
|
|
} else if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
|
|
zend_ast *fn_ast = zend_ast_create(ZEND_AST_VAR,
|
|
zend_ast_create_zval_from_str(zend_string_copy(var_names->inner_closure)));
|
|
call_ast = zend_ast_create(ZEND_AST_CALL, fn_ast, args_ast);
|
|
} else if (Z_TYPE_P(this_ptr) == IS_OBJECT) {
|
|
zend_ast *this_ast = zend_ast_create(ZEND_AST_VAR,
|
|
zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THIS)));
|
|
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(function->common.function_name));
|
|
call_ast = zend_ast_create(ZEND_AST_METHOD_CALL, this_ast,
|
|
method_name_ast, args_ast);
|
|
} else if (called_scope) {
|
|
zend_ast *class_name_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_STATIC));
|
|
class_name_ast->attr = ZEND_NAME_NOT_FQ;
|
|
zend_ast *method_name_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(function->common.function_name));
|
|
call_ast = zend_ast_create(ZEND_AST_STATIC_CALL, class_name_ast,
|
|
method_name_ast, args_ast);
|
|
} else {
|
|
zend_ast *func_name_ast = zend_ast_create_zval_from_str(zend_string_copy(function->common.function_name));
|
|
func_name_ast->attr = ZEND_NAME_FQ;
|
|
call_ast = zend_ast_create(ZEND_AST_CALL, func_name_ast, args_ast);
|
|
}
|
|
|
|
/* Void functions can not 'return $expr' */
|
|
if (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_VOID) {
|
|
stmts_ast = zend_ast_list_add(stmts_ast, call_ast);
|
|
} else {
|
|
zend_ast *return_ast = zend_ast_create(ZEND_AST_RETURN, call_ast);
|
|
stmts_ast = zend_ast_list_add(stmts_ast, return_ast);
|
|
}
|
|
|
|
return stmts_ast;
|
|
|
|
error:
|
|
zend_ast_destroy(args_ast);
|
|
zend_ast_destroy(call_ast);
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t zp_compute_num_required(const zend_function *function,
|
|
uint32_t orig_offset, uint32_t new_offset, uint32_t num_required) {
|
|
if (orig_offset < function->common.num_args) {
|
|
if (orig_offset < function->common.required_num_args) {
|
|
num_required = MAX(num_required, new_offset + 1);
|
|
}
|
|
} else {
|
|
ZEND_ASSERT(function->common.fn_flags & ZEND_ACC_VARIADIC);
|
|
/* Placeholders that run into the variadic portion become
|
|
* required and make all params before them required */
|
|
ZEND_ASSERT(orig_offset >= num_required);
|
|
num_required = new_offset + 1;
|
|
}
|
|
|
|
return num_required;
|
|
}
|
|
|
|
/* Compile PFA to an op_array */
|
|
static zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
|
|
uint32_t argc, zval *argv, zend_array *extra_named_params,
|
|
const zend_array *named_positions,
|
|
zend_string *declaring_filename,
|
|
const uint32_t *declaring_lineno_ptr, void **cache_slot,
|
|
zend_string *pfa_name, uint32_t flags, uint32_t const_args) {
|
|
|
|
zend_op_array *op_array = NULL;
|
|
|
|
if (UNEXPECTED(function->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)) {
|
|
zend_throw_error(NULL, "Cannot call %pS() dynamically", function->common.function_name);
|
|
return NULL;
|
|
}
|
|
|
|
bool uses_variadic_placeholder = flags & ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER;
|
|
|
|
if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) {
|
|
ZEND_ASSERT(EG(exception));
|
|
return NULL;
|
|
}
|
|
|
|
zend_class_entry *called_scope;
|
|
if (Z_TYPE_P(this_ptr) == IS_OBJECT) {
|
|
called_scope = Z_OBJCE_P(this_ptr);
|
|
} else {
|
|
called_scope = Z_CE_P(this_ptr);
|
|
}
|
|
|
|
/* CG(ast_arena) is usually NULL, so we can't just make a snapshot */
|
|
zend_arena *orig_ast_arena = CG(ast_arena);
|
|
CG(ast_arena) = zend_arena_create(1024 * 4);
|
|
|
|
uint32_t orig_lineno = CG(zend_lineno);
|
|
CG(zend_lineno) = zend_get_executed_lineno();
|
|
|
|
uint32_t new_argc = argc;
|
|
|
|
if (uses_variadic_placeholder) {
|
|
/* A variadic placeholder generates an implicit positional placeholder
|
|
* for any unspecified arg, so make room for those. */
|
|
new_argc = MAX(new_argc, function->common.num_args);
|
|
}
|
|
|
|
zval *tmp = zend_arena_alloc(&CG(ast_arena), zend_safe_address_guarded(new_argc, sizeof(*tmp), 0));
|
|
memcpy(tmp, argv, zend_safe_address_guarded(argc, sizeof(*tmp), 0));
|
|
argv = tmp;
|
|
|
|
/* Compute param positions and number of required args, add implicit
|
|
* placeholders.
|
|
*
|
|
* Parameters are placed in the following order:
|
|
* - Positional placeholders
|
|
* - Then named placeholders in their syntax order
|
|
* - Then implicit placeholders added by '...'
|
|
*/
|
|
uint32_t num_params = 0;
|
|
uint32_t num_required = 0;
|
|
uint32_t *arg_to_param_offset_map = zend_arena_alloc(&CG(ast_arena),
|
|
zend_safe_address_guarded(new_argc, sizeof(*arg_to_param_offset_map), 0));
|
|
{
|
|
uint32_t num_positional = 0;
|
|
|
|
/* First, we handle explicit placeholders */
|
|
for (uint32_t arg_offset = 0; arg_offset < argc; arg_offset++) {
|
|
if (!Z_IS_PLACEHOLDER_P(&argv[arg_offset])) {
|
|
continue;
|
|
}
|
|
|
|
num_params++;
|
|
|
|
zend_arg_info *arg_info = &function->common.arg_info[MIN(arg_offset, function->common.num_args)];
|
|
zval *named_pos = named_positions ? zend_hash_find(named_positions, arg_info->name) : NULL;
|
|
uint32_t param_offset;
|
|
if (named_pos) {
|
|
/* Placeholder is sent as named arg. 'num_positional' is fixed
|
|
* at this point. */
|
|
param_offset = num_positional + Z_LVAL_P(named_pos);
|
|
} else {
|
|
/* Placeholder is sent as positional */
|
|
param_offset = num_positional++;
|
|
}
|
|
|
|
arg_to_param_offset_map[arg_offset] = param_offset;
|
|
}
|
|
|
|
num_required = num_params;
|
|
|
|
/* Handle implicit placeholders added by '...' */
|
|
if (uses_variadic_placeholder) {
|
|
for (uint32_t arg_offset = 0; arg_offset < new_argc; arg_offset++) {
|
|
if (arg_offset < argc && !Z_ISUNDEF(argv[arg_offset])) {
|
|
continue;
|
|
}
|
|
|
|
/* Unspecified parameters become placeholders */
|
|
Z_TYPE_INFO(argv[arg_offset]) = _IS_PLACEHOLDER;
|
|
|
|
num_params++;
|
|
|
|
uint32_t param_offset = num_params - 1;
|
|
|
|
arg_to_param_offset_map[arg_offset] = param_offset;
|
|
|
|
num_required = zp_compute_num_required(function,
|
|
arg_offset, param_offset, num_required);
|
|
}
|
|
}
|
|
}
|
|
|
|
argc = new_argc;
|
|
|
|
/* Assign variable names */
|
|
|
|
zp_names *var_names = zp_assign_names(argc, argv, function,
|
|
uses_variadic_placeholder, extra_named_params, const_args);
|
|
|
|
/* Generate AST */
|
|
|
|
zend_ast *lexical_vars_ast = zend_ast_create_list(0, ZEND_AST_CLOSURE_USES);
|
|
zend_ast *params_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST);
|
|
zend_ast *return_type_ast = NULL;
|
|
zend_ast *stmts_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST);
|
|
zend_ast *attributes_ast = NULL;
|
|
|
|
/* Generate AST for params and lexical vars */
|
|
{
|
|
/* The inner Closure, if any, is assumed to be the first lexical var by
|
|
* do_closure_bind(). */
|
|
if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
|
|
zend_ast *lexical_var_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(var_names->inner_closure));
|
|
lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast);
|
|
}
|
|
|
|
zend_ast **params = zend_arena_calloc(&CG(ast_arena), num_params, sizeof(*params));
|
|
for (uint32_t offset = 0; offset < argc; offset++) {
|
|
if (Z_IS_PLACEHOLDER_P(&argv[offset])) {
|
|
zend_arg_info *arg_info = &function->common.arg_info[MIN(offset, function->common.num_args)];
|
|
|
|
int param_flags = 0;
|
|
if (zp_arg_must_be_sent_by_ref(function, offset+1)) {
|
|
param_flags |= ZEND_PARAM_REF;
|
|
}
|
|
|
|
uint32_t param_offset = arg_to_param_offset_map[offset];
|
|
zend_ast *param_type_ast = zp_type_to_ast(arg_info->type);
|
|
zend_ast *default_value_ast = NULL;
|
|
if (param_offset >= num_required) {
|
|
zval default_value;
|
|
if (zp_get_param_default_value(&default_value, function, offset) == FAILURE) {
|
|
for (uint32_t i = 0; i < num_params; i++) {
|
|
zend_ast_destroy(params[i]);
|
|
}
|
|
goto error;
|
|
}
|
|
default_value_ast = zend_ast_create_zval(&default_value);
|
|
}
|
|
|
|
ZEND_ASSERT(offset < function->common.num_args || (function->common.fn_flags & ZEND_ACC_VARIADIC));
|
|
|
|
zend_ast *attributes_ast = zp_param_attributes_to_ast(function, MIN(offset, function->common.num_args));
|
|
params[param_offset] = zend_ast_create_ex(ZEND_AST_PARAM,
|
|
param_flags, param_type_ast,
|
|
zend_ast_create_zval_from_str(
|
|
zend_string_copy(var_names->params[offset])),
|
|
default_value_ast, attributes_ast, NULL, NULL);
|
|
|
|
} else if (!Z_ISUNDEF(argv[offset])) {
|
|
if (zp_is_const_arg(const_args, offset)) {
|
|
/* Will be burned into the op_array */
|
|
} else {
|
|
zend_ast *lexical_var_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(var_names->params[offset]));
|
|
if (zp_arg_must_be_sent_by_ref(function, offset+1)) {
|
|
lexical_var_ast->attr = ZEND_BIND_REF;
|
|
}
|
|
lexical_vars_ast = zend_ast_list_add(
|
|
lexical_vars_ast, lexical_var_ast);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < num_params; i++) {
|
|
params_ast = zend_ast_list_add(params_ast, params[i]);
|
|
}
|
|
}
|
|
|
|
if (extra_named_params) {
|
|
zend_ast *lexical_var_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(var_names->extra_named_params));
|
|
lexical_vars_ast = zend_ast_list_add(lexical_vars_ast, lexical_var_ast);
|
|
}
|
|
|
|
/* If we have a variadic placeholder and the underlying function is
|
|
* variadic, add a variadic param. */
|
|
if (uses_variadic_placeholder
|
|
&& (function->common.fn_flags & ZEND_ACC_VARIADIC)) {
|
|
zend_arg_info *arg_info = &function->common.arg_info[function->common.num_args];
|
|
int param_flags = ZEND_PARAM_VARIADIC;
|
|
if (zp_arg_must_be_sent_by_ref(function, function->common.num_args+1)) {
|
|
param_flags |= ZEND_PARAM_REF;
|
|
}
|
|
zend_ast *param_type_ast = zp_type_to_ast(arg_info->type);
|
|
zend_ast *attributes_ast = zp_param_attributes_to_ast(function, function->common.num_args);
|
|
params_ast = zend_ast_list_add(params_ast, zend_ast_create_ex(ZEND_AST_PARAM,
|
|
param_flags, param_type_ast,
|
|
zend_ast_create_zval_from_str(
|
|
zend_string_copy(var_names->variadic_param)),
|
|
NULL, attributes_ast, NULL, NULL));
|
|
}
|
|
|
|
zend_type return_type = {0};
|
|
if (function->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
|
|
return_type = (function->common.arg_info-1)->type;
|
|
return_type_ast = zp_type_to_ast(return_type);
|
|
}
|
|
|
|
/**
|
|
* Generate function body.
|
|
*
|
|
* If we may need to forward superfluous arguments, do that conditionally, as
|
|
* it's faster:
|
|
*
|
|
* if (func_num_args() <= n) {
|
|
* // normal call
|
|
* } else {
|
|
* // call with superfluous arg forwarding
|
|
* }
|
|
*
|
|
* The func_num_args() call should be compiled to a single FUNC_NUM_ARGS op.
|
|
*/
|
|
|
|
if (uses_variadic_placeholder && !(function->common.fn_flags & ZEND_ACC_VARIADIC)) {
|
|
zend_ast *no_forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST);
|
|
zend_ast *forwarding_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST);
|
|
|
|
no_forwarding_ast = zp_compile_forwarding_call(this_ptr, function,
|
|
argc, argv, extra_named_params,
|
|
var_names, uses_variadic_placeholder, num_params,
|
|
called_scope, return_type, false, no_forwarding_ast, const_args);
|
|
|
|
if (!no_forwarding_ast) {
|
|
ZEND_ASSERT(EG(exception));
|
|
goto error;
|
|
}
|
|
|
|
forwarding_ast = zp_compile_forwarding_call(this_ptr, function,
|
|
argc, argv, extra_named_params,
|
|
var_names, uses_variadic_placeholder, num_params,
|
|
called_scope, return_type, true, forwarding_ast, const_args);
|
|
|
|
if (!forwarding_ast) {
|
|
ZEND_ASSERT(EG(exception));
|
|
zend_ast_destroy(no_forwarding_ast);
|
|
goto error;
|
|
}
|
|
|
|
zend_ast *func_num_args_name_ast = zend_ast_create_zval_from_str(
|
|
zend_string_copy(ZSTR_KNOWN(ZEND_STR_FUNC_NUM_ARGS)));
|
|
func_num_args_name_ast->attr = ZEND_NAME_FQ;
|
|
|
|
stmts_ast = zend_ast_list_add(stmts_ast,
|
|
zend_ast_create_list(2, ZEND_AST_IF,
|
|
zend_ast_create(ZEND_AST_IF_ELEM,
|
|
zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL,
|
|
zend_ast_create(ZEND_AST_CALL, func_num_args_name_ast,
|
|
zend_ast_create_list(0, ZEND_AST_ARG_LIST)),
|
|
zend_ast_create_zval_from_long(num_params)),
|
|
no_forwarding_ast),
|
|
zend_ast_create(ZEND_AST_IF_ELEM,
|
|
NULL,
|
|
forwarding_ast)));
|
|
} else {
|
|
stmts_ast = zp_compile_forwarding_call(this_ptr, function,
|
|
argc, argv, extra_named_params,
|
|
var_names, uses_variadic_placeholder, num_params,
|
|
called_scope, return_type, false, stmts_ast, const_args);
|
|
|
|
if (!stmts_ast) {
|
|
ZEND_ASSERT(EG(exception));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Inherit the NoDiscard attribute */
|
|
if (function->common.attributes) {
|
|
zend_attribute *attr = zend_get_attribute_str(
|
|
function->common.attributes, "nodiscard", strlen("nodiscard"));
|
|
if (attr) {
|
|
attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_GROUP,
|
|
zp_attribute_to_ast(attr));
|
|
attributes_ast = zend_ast_create_list(1, ZEND_AST_ATTRIBUTE_LIST,
|
|
attributes_ast);
|
|
}
|
|
}
|
|
|
|
int closure_flags = function->common.fn_flags & ZEND_ACC_RETURN_REFERENCE;
|
|
zend_ast *closure_ast = zend_ast_create_decl(ZEND_AST_CLOSURE,
|
|
closure_flags, CG(zend_lineno), NULL,
|
|
NULL, params_ast, lexical_vars_ast, stmts_ast,
|
|
return_type_ast, attributes_ast);
|
|
|
|
if (Z_TYPE_P(this_ptr) != IS_OBJECT && !zp_is_non_static_closure(function)) {
|
|
((zend_ast_decl*)closure_ast)->flags |= ZEND_ACC_STATIC;
|
|
}
|
|
|
|
#if ZEND_DEBUG
|
|
{
|
|
const char *tmp = getenv("DUMP_PFA_AST");
|
|
if (tmp && ZEND_ATOL(tmp)) {
|
|
zend_string *str = zend_ast_export("", closure_ast, "");
|
|
fprintf(stderr, "PFA AST: %s\n", ZSTR_VAL(str));
|
|
zend_string_release(str);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Takes ownership of closure_ast */
|
|
op_array = zend_accel_compile_pfa(closure_ast, declaring_filename,
|
|
declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);
|
|
|
|
clean:
|
|
zp_names_dtor(var_names, argc);
|
|
zend_arena_destroy(CG(ast_arena));
|
|
CG(ast_arena) = orig_ast_arena;
|
|
CG(zend_lineno) = orig_lineno;
|
|
|
|
return op_array;
|
|
|
|
error:
|
|
zend_ast_destroy(lexical_vars_ast);
|
|
zend_ast_destroy(params_ast);
|
|
zend_ast_destroy(return_type_ast);
|
|
zend_ast_destroy(stmts_ast);
|
|
zend_ast_destroy(attributes_ast);
|
|
goto clean;
|
|
}
|
|
|
|
/* Get the op_array of a PFA from caches or compile it */
|
|
static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function,
|
|
uint32_t argc, zval *argv, zend_array *extra_named_params,
|
|
const zend_array *named_positions,
|
|
zend_string *declaring_filename,
|
|
const uint32_t *declaring_lineno_ptr, void **cache_slot,
|
|
zend_string *pfa_name, uint32_t flags, uint32_t const_args) {
|
|
|
|
if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION
|
|
? cache_slot[0] == function
|
|
: cache_slot[0] == function->op_array.opcodes)) {
|
|
ZEND_ASSERT(!(function->common.fn_flags & ZEND_ACC_NEVER_CACHE));
|
|
return cache_slot[1];
|
|
}
|
|
|
|
const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function,
|
|
flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);
|
|
|
|
if (UNEXPECTED(!op_array)) {
|
|
op_array = zp_compile(this_ptr, function, argc, argv,
|
|
extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr,
|
|
cache_slot, pfa_name, flags, const_args);
|
|
}
|
|
|
|
if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) {
|
|
cache_slot[0] = function->type == ZEND_INTERNAL_FUNCTION
|
|
? (void*)function
|
|
: (void*)function->op_array.opcodes;
|
|
cache_slot[1] = (zend_op_array*)op_array;
|
|
}
|
|
|
|
return op_array;
|
|
}
|
|
|
|
static void zp_free_unbound_args(uint32_t start, uint32_t argc, zval *argv)
|
|
{
|
|
for (uint32_t offset = start; offset < argc; offset++) {
|
|
zval_ptr_dtor_nogc(&argv[offset]);
|
|
}
|
|
}
|
|
|
|
/* Bind pre-bound arguments as lexical vars */
|
|
static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *argv,
|
|
zend_array *extra_named_params, uint32_t const_args) {
|
|
|
|
zend_arg_info *arg_infos = function->common.arg_info;
|
|
uint32_t bind_offset = 0;
|
|
|
|
if (function->common.fn_flags & ZEND_ACC_CLOSURE) {
|
|
zval var;
|
|
ZVAL_OBJ(&var, ZEND_CLOSURE_OBJECT(function));
|
|
Z_ADDREF(var);
|
|
zend_closure_bind_var_ex(Z_OBJ_P(result), bind_offset, &var);
|
|
bind_offset += sizeof(Bucket);
|
|
}
|
|
|
|
for (uint32_t offset = 0; offset < argc; offset++) {
|
|
zval *var = &argv[offset];
|
|
if (Z_IS_PLACEHOLDER_P(var) || Z_ISUNDEF_P(var) || zp_is_const_arg(const_args, offset)) {
|
|
continue;
|
|
}
|
|
zend_arg_info *arg_info;
|
|
if (offset < function->common.num_args) {
|
|
arg_info = &arg_infos[offset];
|
|
} else if (function->common.fn_flags & ZEND_ACC_VARIADIC) {
|
|
arg_info = &arg_infos[function->common.num_args];
|
|
} else {
|
|
arg_info = NULL;
|
|
}
|
|
if (arg_info && ZEND_TYPE_IS_SET(arg_info->type)
|
|
&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, var,
|
|
/* current_frame */ true, /* is_internal */ false))) {
|
|
zend_string *need_msg = zend_type_to_string_resolved(arg_info->type,
|
|
function->common.scope);
|
|
zend_argument_type_error_ex(function, offset + 1,
|
|
"must be of type %s, %s given",
|
|
ZSTR_VAL(need_msg), zend_zval_value_name(var));
|
|
zend_string_release(need_msg);
|
|
zval_ptr_dtor(result);
|
|
ZVAL_NULL(result);
|
|
zp_free_unbound_args(offset, argc, argv);
|
|
return;
|
|
}
|
|
ZEND_ASSERT(zp_arg_must_be_sent_by_ref(function, offset+1) ? Z_ISREF_P(var) : !Z_ISREF_P(var));
|
|
zend_closure_bind_var_ex(Z_OBJ_P(result), bind_offset, var);
|
|
bind_offset += sizeof(Bucket);
|
|
}
|
|
|
|
if (extra_named_params) {
|
|
zval var;
|
|
ZVAL_ARR(&var, extra_named_params);
|
|
Z_ADDREF(var);
|
|
zend_closure_bind_var_ex(Z_OBJ_P(result), bind_offset, &var);
|
|
}
|
|
}
|
|
|
|
void zend_partial_create(zval *result, zend_class_entry *scope, zval *this_ptr, zend_function *function,
|
|
uint32_t argc, zval *argv, zend_array *extra_named_params,
|
|
const zend_array *named_positions,
|
|
zend_string *declaring_filename,
|
|
const uint32_t *declaring_lineno_ptr, void **cache_slot,
|
|
zend_string *pfa_name, uint32_t flags, uint32_t const_args) {
|
|
|
|
ZEND_ASSERT(pfa_name);
|
|
|
|
const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv,
|
|
extra_named_params, named_positions,
|
|
declaring_filename, declaring_lineno_ptr,
|
|
cache_slot, pfa_name, flags, const_args);
|
|
|
|
if (UNEXPECTED(!op_array)) {
|
|
ZEND_ASSERT(EG(exception));
|
|
zp_free_unbound_args(0, argc, argv);
|
|
ZVAL_NULL(result);
|
|
return;
|
|
}
|
|
|
|
zend_class_entry *called_scope;
|
|
zend_object *object;
|
|
|
|
if (Z_TYPE_P(this_ptr) == IS_OBJECT) {
|
|
called_scope = Z_OBJCE_P(this_ptr);
|
|
} else {
|
|
called_scope = Z_CE_P(this_ptr);
|
|
}
|
|
|
|
if (Z_TYPE_P(this_ptr) == IS_OBJECT && !zp_is_static_closure(function)) {
|
|
object = Z_OBJ_P(this_ptr);
|
|
} else {
|
|
object = NULL;
|
|
}
|
|
|
|
|
|
/* We conveniently use the function's scope for the scope of the generated closure as this allows const exprs
|
|
* referencing self:: or parent:: to behave normally without rewriting them.
|
|
* This affects method resolution for magic methods, so use the actual scope for them. */
|
|
if (!(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
|
|
scope = function->common.scope;
|
|
}
|
|
|
|
zend_create_partial_closure(result, (zend_function*)op_array,
|
|
scope, called_scope, object,
|
|
(function->common.fn_flags & ZEND_ACC_CLOSURE) != 0);
|
|
|
|
zp_bind(result, function, argc, argv, extra_named_params, const_args);
|
|
}
|
|
|
|
void zend_partial_op_array_dtor(zval *pDest)
|
|
{
|
|
destroy_op_array(Z_PTR_P(pDest));
|
|
}
|