Skip to content

Reference runtime-defined functions from literals #5593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 12 additions & 30 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1071,21 +1071,11 @@ static zend_never_inline ZEND_COLD ZEND_NORETURN void do_bind_function_error(zen
}
}

ZEND_API int do_bind_function(zval *lcname) /* {{{ */
ZEND_API int do_bind_function(zend_function *func, zval *lcname) /* {{{ */
{
zend_function *function;
zval *rtd_key, *zv;

rtd_key = lcname + 1;
zv = zend_hash_find_ex(EG(function_table), Z_STR_P(rtd_key), 1);
if (UNEXPECTED(!zv)) {
do_bind_function_error(Z_STR_P(lcname), NULL, 0);
return FAILURE;
}
function = (zend_function*)Z_PTR_P(zv);
zv = zend_hash_set_bucket_key(EG(function_table), (Bucket*)zv, Z_STR_P(lcname));
if (UNEXPECTED(!zv)) {
do_bind_function_error(Z_STR_P(lcname), &function->op_array, 0);
zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func);
if (UNEXPECTED(!added_func)) {
do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0);
return FAILURE;
}
return SUCCESS;
Expand Down Expand Up @@ -6176,7 +6166,7 @@ zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string *name,

static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, zend_bool toplevel) /* {{{ */
{
zend_string *unqualified_name, *name, *lcname, *key;
zend_string *unqualified_name, *name, *lcname;
zend_op *opline;

unqualified_name = decl->name;
Expand Down Expand Up @@ -6212,24 +6202,16 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as
return;
}

key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
if (!zend_hash_add_ptr(CG(function_table), key, op_array)) {
zend_error_noreturn(E_ERROR,
"Runtime definition key collision for function %s. This is a bug", ZSTR_VAL(name));
}
znode func_node;
func_node.op_type = IS_CONST;
ZVAL_NEW_FUNC_REF(&func_node.u.constant, (zend_function *) op_array);

if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL);
opline->extended_value = zend_alloc_cache_slot();
opline->op1_type = IS_CONST;
LITERAL_STR(opline->op1, key);
zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, &func_node, NULL);
} else {
opline = get_next_op();
opline->opcode = ZEND_DECLARE_FUNCTION;
opline->op1_type = IS_CONST;
LITERAL_STR(opline->op1, zend_string_copy(lcname));
/* RTD key is placed after lcname literal in op1 */
zend_add_literal_string(&key);
opline = zend_emit_op(NULL, ZEND_DECLARE_FUNCTION, &func_node, NULL);
opline->op2_type = IS_CONST;
LITERAL_STR(opline->op2, zend_string_copy(lcname));
}
zend_string_release_ex(lcname, 0);
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ zend_bool zend_handle_encoding_declaration(zend_ast *ast);
/* parser-driven code generators */
void zend_do_free(znode *op1);

ZEND_API int do_bind_function(zval *lcname);
ZEND_API int do_bind_function(zend_function *func, zval *lcname);
ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name);
ZEND_API uint32_t zend_build_delayed_early_binding_list(const zend_op_array *op_array);
ZEND_API void zend_do_delayed_early_binding(zend_op_array *op_array, uint32_t first_early_binding_opline);
Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_type_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#define MAY_BE_ARRAY_KEY_STRING (1<<22)
#define MAY_BE_ARRAY_KEY_ANY (MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_KEY_STRING)

#define MAY_BE_CLASS (1<<23)
#define MAY_BE_SPECIAL (1<<23)
#define MAY_BE_CLASS MAY_BE_SPECIAL

#endif /* ZEND_TYPE_INFO_H */
21 changes: 21 additions & 0 deletions Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ typedef struct _zend_object zend_object;
typedef struct _zend_resource zend_resource;
typedef struct _zend_reference zend_reference;
typedef struct _zend_ast_ref zend_ast_ref;
typedef struct _zend_func_ref zend_func_ref;
typedef struct _zend_ast zend_ast;

typedef int (*compare_func_t)(const void *, const void *);
Expand Down Expand Up @@ -293,6 +294,7 @@ typedef union _zend_value {
void *ptr;
zend_class_entry *ce;
zend_function *func;
zend_func_ref *func_ref;
struct {
uint32_t w1;
uint32_t w2;
Expand Down Expand Up @@ -514,6 +516,11 @@ struct _zend_ast_ref {
/*zend_ast ast; zend_ast follows the zend_ast_ref structure */
};

struct _zend_func_ref {
zend_refcounted_h gc;
zend_function *func;
};

/* Regular data types: Must be in sync with zend_variables.c. */
#define IS_UNDEF 0
#define IS_NULL 1
Expand All @@ -527,6 +534,7 @@ struct _zend_ast_ref {
#define IS_RESOURCE 9
#define IS_REFERENCE 10
#define IS_CONSTANT_AST 11 /* Constant expressions */
#define IS_FUNC_REF 12

/* Fake types used only for type hinting.
* These are allowed to overlap with the types below. */
Expand Down Expand Up @@ -682,6 +690,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
#define IS_REFERENCE_EX (IS_REFERENCE | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))

#define IS_CONSTANT_AST_EX (IS_CONSTANT_AST | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))
#define IS_FUNC_REF_EX (IS_FUNC_REF | (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT))

/* string flags (zval.value->gc.u.flags) */
#define IS_STR_INTERNED GC_IMMUTABLE /* interned string */
Expand Down Expand Up @@ -859,6 +868,9 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
#define Z_FUNC(zval) (zval).value.func
#define Z_FUNC_P(zval_p) Z_FUNC(*(zval_p))

#define Z_FUNC_REF(zval) (zval).value.func_ref
#define Z_FUNC_REF_P(zval_p) Z_FUNC_REF(*(zval_p))

#define Z_PTR(zval) (zval).value.ptr
#define Z_PTR_P(zval_p) Z_PTR(*(zval_p))

Expand Down Expand Up @@ -1082,6 +1094,15 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
Z_TYPE_INFO_P(z) = _IS_ERROR; \
} while (0)

#define ZVAL_NEW_FUNC_REF(z, _func) do { \
zend_func_ref *_ref = emalloc(sizeof(zend_func_ref)); \
GC_SET_REFCOUNT(_ref, 1); \
GC_TYPE_INFO(_ref) = IS_FUNC_REF; \
_ref->func = (_func); \
Z_FUNC_REF_P(z) = _ref; \
Z_TYPE_INFO_P(z) = IS_FUNC_REF_EX; \
} while (0)

#define Z_REFCOUNT_P(pz) zval_refcount_p(pz)
#define Z_SET_REFCOUNT_P(pz, rc) zval_set_refcount_p(pz, rc)
#define Z_ADDREF_P(pz) zval_addref_p(pz)
Expand Down
12 changes: 10 additions & 2 deletions Zend/zend_variables.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ static void ZEND_FASTCALL zend_string_destroy(zend_string *str);
#endif
static void ZEND_FASTCALL zend_reference_destroy(zend_reference *ref);
static void ZEND_FASTCALL zend_empty_destroy(zend_reference *ref);
static void ZEND_FASTCALL zend_func_ref_destroy(zend_func_ref *ref);

typedef void (ZEND_FASTCALL *zend_rc_dtor_func_t)(zend_refcounted *p);

Expand All @@ -48,12 +49,13 @@ static const zend_rc_dtor_func_t zend_rc_dtor_func[] = {
/* IS_OBJECT */ (zend_rc_dtor_func_t)zend_objects_store_del,
/* IS_RESOURCE */ (zend_rc_dtor_func_t)zend_list_free,
/* IS_REFERENCE */ (zend_rc_dtor_func_t)zend_reference_destroy,
/* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy
/* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy,
/* IS_FUNC_REF */ (zend_rc_dtor_func_t)zend_func_ref_destroy,
};

ZEND_API void ZEND_FASTCALL rc_dtor_func(zend_refcounted *p)
{
ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST);
ZEND_ASSERT(GC_TYPE(p) <= IS_FUNC_REF);
zend_rc_dtor_func[GC_TYPE(p)](p);
}

Expand All @@ -79,6 +81,12 @@ static void ZEND_FASTCALL zend_empty_destroy(zend_reference *ref)
{
}

static void ZEND_FASTCALL zend_func_ref_destroy(zend_func_ref *ref)
{
destroy_zend_function(ref->func);
efree_size(ref, sizeof(zend_func_ref));
}

ZEND_API void zval_ptr_dtor(zval *zval_ptr) /* {{{ */
{
i_zval_ptr_dtor(zval_ptr);
Expand Down
16 changes: 4 additions & 12 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -7188,7 +7188,9 @@ ZEND_VM_HANDLER(141, ZEND_DECLARE_FUNCTION, ANY, ANY)
USE_OPLINE

SAVE_OPLINE();
do_bind_function(RT_CONSTANT(opline, opline->op1));
do_bind_function(
Z_FUNC_REF_P(RT_CONSTANT(opline, opline->op1))->func,
RT_CONSTANT(opline, opline->op2));
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

Expand Down Expand Up @@ -7451,20 +7453,10 @@ ZEND_VM_HANDLER(143, ZEND_DECLARE_CONST, CONST, CONST)
ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, UNUSED, CACHE_SLOT)
{
USE_OPLINE
zend_function *func;
zval *zfunc;
zend_function *func = Z_FUNC_REF_P(RT_CONSTANT(opline, opline->op1))->func;
zval *object;
zend_class_entry *called_scope;

func = CACHED_PTR(opline->extended_value);
if (UNEXPECTED(func == NULL)) {
zfunc = zend_hash_find_ex(EG(function_table), Z_STR_P(RT_CONSTANT(opline, opline->op1)), 1);
ZEND_ASSERT(zfunc != NULL);
func = Z_FUNC_P(zfunc);
ZEND_ASSERT(func->type == ZEND_USER_FUNCTION);
CACHE_PTR(opline->extended_value, func);
}

if (Z_TYPE(EX(This)) == IS_OBJECT) {
called_scope = Z_OBJCE(EX(This));
if (UNEXPECTED((func->common.fn_flags & ZEND_ACC_STATIC) ||
Expand Down
16 changes: 4 additions & 12 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -2439,7 +2439,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_FUNCTION_SPEC_HANDLER(
USE_OPLINE

SAVE_OPLINE();
do_bind_function(RT_CONSTANT(opline, opline->op1));
do_bind_function(
Z_FUNC_REF_P(RT_CONSTANT(opline, opline->op1))->func,
RT_CONSTANT(opline, opline->op2));
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

Expand Down Expand Up @@ -9100,20 +9102,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_U
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_function *func;
zval *zfunc;
zend_function *func = Z_FUNC_REF_P(RT_CONSTANT(opline, opline->op1))->func;
zval *object;
zend_class_entry *called_scope;

func = CACHED_PTR(opline->extended_value);
if (UNEXPECTED(func == NULL)) {
zfunc = zend_hash_find_ex(EG(function_table), Z_STR_P(RT_CONSTANT(opline, opline->op1)), 1);
ZEND_ASSERT(zfunc != NULL);
func = Z_FUNC_P(zfunc);
ZEND_ASSERT(func->type == ZEND_USER_FUNCTION);
CACHE_PTR(opline->extended_value, func);
}

if (Z_TYPE(EX(This)) == IS_OBJECT) {
called_scope = Z_OBJCE(EX(This));
if (UNEXPECTED((func->common.fn_flags & ZEND_ACC_STATIC) ||
Expand Down
4 changes: 0 additions & 4 deletions ext/opcache/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_RECV_INIT:
LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1);
break;
case ZEND_DECLARE_FUNCTION:
LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
break;
case ZEND_DECLARE_CLASS:
case ZEND_DECLARE_CLASS_DELAYED:
LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2);
Expand Down Expand Up @@ -773,7 +770,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
bind_var_slot[opline->op2.constant] = opline->extended_value;
}
break;
case ZEND_DECLARE_LAMBDA_FUNCTION:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_CLASS_DELAYED:
opline->extended_value = cache_size;
Expand Down
3 changes: 3 additions & 0 deletions ext/opcache/Optimizer/zend_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ void zend_dump_const(const zval *zv)
case IS_ARRAY:
fprintf(stderr, " array(...)");
break;
case IS_FUNC_REF:
fprintf(stderr, " func(%p)", Z_FUNC_REF_P(zv)->func);
break;
default:
fprintf(stderr, " zval(type=%d)", Z_TYPE_P(zv));
break;
Expand Down
2 changes: 2 additions & 0 deletions ext/opcache/Optimizer/zend_inference.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ DEFINE_SSA_OP_RANGE_OVERFLOW(op2)
static zend_always_inline uint32_t _const_op_type(const zval *zv) {
if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
} else if (Z_TYPE_P(zv) == IS_FUNC_REF) {
return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_SPECIAL;
} else if (Z_TYPE_P(zv) == IS_ARRAY) {
HashTable *ht = Z_ARRVAL_P(zv);
uint32_t tmp = MAY_BE_ARRAY;
Expand Down
34 changes: 12 additions & 22 deletions ext/opcache/zend_accelerator_util_funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -438,20 +438,7 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source)
ZEND_ASSERT(p->key);
t = zend_hash_find_ex(target, p->key, 1);
if (UNEXPECTED(t != NULL)) {
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
/* Runtime definition key. There are two circumstances under which the key can
* already be defined:
* 1. The file has been re-included without being changed in the meantime. In
* this case we can keep the old value, because we know that the definition
* hasn't changed.
* 2. The file has been changed in the meantime, but the RTD key ends up colliding.
* This would be a bug.
* As we can't distinguish these cases, we assume that it is 1. and keep the old
* value. */
continue;
} else {
goto failure;
}
goto failure;
} else {
_zend_hash_append_ptr(target, p->key, Z_PTR(p->val));
}
Expand Down Expand Up @@ -490,12 +477,7 @@ static void zend_accel_function_hash_copy_from_shm(HashTable *target, HashTable
ZEND_ASSERT(p->key);
t = zend_hash_find_ex(target, p->key, 1);
if (UNEXPECTED(t != NULL)) {
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
/* See comment in zend_accel_function_hash_copy(). */
continue;
} else {
goto failure;
}
goto failure;
} else {
_zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1);
}
Expand Down Expand Up @@ -534,7 +516,15 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source)
t = zend_hash_find_ex(target, p->key, 1);
if (UNEXPECTED(t != NULL)) {
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
/* See comment in zend_accel_function_hash_copy(). */
/* Runtime definition key. There are two circumstances under which the key can
* already be defined:
* 1. The file has been re-included without being changed in the meantime. In
* this case we can keep the old value, because we know that the definition
* hasn't changed.
* 2. The file has been changed in the meantime, but the RTD key ends up colliding.
* This would be a bug.
* As we can't distinguish these cases, we assume that it is 1. and keep the old
* value. */
continue;
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
zend_class_entry *ce1 = Z_PTR(p->val);
Expand Down Expand Up @@ -571,7 +561,7 @@ static void zend_accel_class_hash_copy_from_shm(HashTable *target, HashTable *so
t = zend_hash_find_ex(target, p->key, 1);
if (UNEXPECTED(t != NULL)) {
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
/* See comment in zend_accel_function_hash_copy(). */
/* See comment in zend_accel_class_hash_copy(). */
continue;
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
zend_class_entry *ce1 = Z_PTR(p->val);
Expand Down
Loading