Skip to content

Commit 841bfe9

Browse files
committed
feat(zend, opcache, reflection): complete pre-erasure side table and resolve type-parameter refs
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent 6bde68d commit 841bfe9

8 files changed

Lines changed: 474 additions & 24 deletions

File tree

Zend/zend_compile.c

Lines changed: 186 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,11 @@ void zend_init_compiler_data_structures(void) /* {{{ */
438438
}
439439
/* }}} */
440440

441-
static void zend_generic_scope_push(zend_generic_parameter_list *params) /* {{{ */
441+
static void zend_generic_scope_push(zend_generic_parameter_list *params, uint8_t origin) /* {{{ */
442442
{
443443
zend_generic_scope_entry *entry = emalloc(sizeof(zend_generic_scope_entry));
444444
entry->params = params;
445+
entry->origin = origin;
445446
entry->outer = CG(generic_scope);
446447
CG(generic_scope) = entry;
447448
}
@@ -456,12 +457,15 @@ static void zend_generic_scope_pop(void) /* {{{ */
456457
}
457458
/* }}} */
458459

459-
static zend_generic_parameter *zend_generic_lookup(const zend_string *name) /* {{{ */
460+
static zend_generic_parameter *zend_generic_lookup_full(
461+
const zend_string *name, uint8_t *origin_out, uint32_t *index_out) /* {{{ */
460462
{
461463
for (zend_generic_scope_entry *e = CG(generic_scope); e; e = e->outer) {
462464
zend_generic_parameter_list *params = e->params;
463465
for (uint32_t i = 0; i < params->count; i++) {
464466
if (zend_string_equals(params->parameters[i].name, name)) {
467+
if (origin_out) *origin_out = e->origin;
468+
if (index_out) *index_out = i;
465469
return &params->parameters[i];
466470
}
467471
}
@@ -470,6 +474,12 @@ static zend_generic_parameter *zend_generic_lookup(const zend_string *name) /* {
470474
}
471475
/* }}} */
472476

477+
static zend_generic_parameter *zend_generic_lookup(const zend_string *name) /* {{{ */
478+
{
479+
return zend_generic_lookup_full(name, NULL, NULL);
480+
}
481+
/* }}} */
482+
473483
static zend_generic_parameter_list *zend_compile_generic_type_parameter_list(zend_ast *list_ast) /* {{{ */
474484
{
475485
if (!list_ast) {
@@ -509,6 +519,139 @@ static zend_generic_parameter_list *zend_compile_generic_type_parameter_list(zen
509519
}
510520
/* }}} */
511521

522+
/* Returns true if the AST contains any generic-aware constructs (a type-parameter
523+
* reference or a named type with type arguments) that need pre-erasure capture. */
524+
static bool zend_type_ast_has_generic_content(zend_ast *ast)
525+
{
526+
if (!ast) return false;
527+
zend_ast_attr orig = ast->attr;
528+
ast->attr &= ~ZEND_TYPE_NULLABLE;
529+
bool result = false;
530+
531+
if (ast->kind == ZEND_AST_GENERIC_NAMED_TYPE) {
532+
result = true;
533+
} else if (ast->kind == ZEND_AST_TYPE_UNION || ast->kind == ZEND_AST_TYPE_INTERSECTION) {
534+
zend_ast_list *list = zend_ast_get_list(ast);
535+
for (uint32_t i = 0; i < list->children; i++) {
536+
if (zend_type_ast_has_generic_content(list->child[i])) {
537+
result = true;
538+
break;
539+
}
540+
}
541+
} else if (ast->kind == ZEND_AST_ZVAL) {
542+
if ((ast->attr & ZEND_NAME_NOT_FQ) == ZEND_NAME_NOT_FQ) {
543+
const zval *zv = zend_ast_get_zval(ast);
544+
if (Z_TYPE_P(zv) == IS_STRING && zend_generic_lookup(Z_STR_P(zv))) {
545+
result = true;
546+
}
547+
}
548+
}
549+
550+
ast->attr = orig;
551+
return result;
552+
}
553+
554+
/* Build a pre-erasure zend_type from a type-expression AST. The returned type
555+
* may carry type-parameter references (TYPE_PARAMETER bit) or named-with-args
556+
* payloads (NAMED_WITH_ARGS bit). Used only for the side table; the runtime
557+
* never sees this form. */
558+
static zend_type zend_compile_pre_erasure_typename(zend_ast *ast)
559+
{
560+
bool is_marked_nullable = ast->attr & ZEND_TYPE_NULLABLE;
561+
zend_ast_attr orig_attr = ast->attr;
562+
if (is_marked_nullable) {
563+
ast->attr &= ~ZEND_TYPE_NULLABLE;
564+
}
565+
566+
zend_type result = ZEND_TYPE_INIT_NONE(0);
567+
568+
if (ast->kind == ZEND_AST_TYPE_UNION || ast->kind == ZEND_AST_TYPE_INTERSECTION) {
569+
zend_ast_list *list = zend_ast_get_list(ast);
570+
zend_type_list *type_list = emalloc(ZEND_TYPE_LIST_SIZE(list->children));
571+
type_list->num_types = list->children;
572+
for (uint32_t i = 0; i < list->children; i++) {
573+
type_list->types[i] = zend_compile_pre_erasure_typename(list->child[i]);
574+
}
575+
ZEND_TYPE_SET_PTR(result, type_list);
576+
ZEND_TYPE_FULL_MASK(result) |= _ZEND_TYPE_LIST_BIT |
577+
(ast->kind == ZEND_AST_TYPE_UNION ? _ZEND_TYPE_UNION_BIT : _ZEND_TYPE_INTERSECTION_BIT);
578+
} else if (ast->kind == ZEND_AST_GENERIC_NAMED_TYPE) {
579+
zend_ast *name_ast = ast->child[0];
580+
zend_ast_list *args_list = zend_ast_get_list(ast->child[1]);
581+
zend_type_named_with_args *payload = emalloc(ZEND_TYPE_NAMED_WITH_ARGS_SIZE(args_list->children));
582+
payload->name = zend_string_copy(zval_make_interned_string(zend_ast_get_zval(name_ast)));
583+
payload->name_attr = name_ast->attr;
584+
payload->count = args_list->children;
585+
for (uint32_t i = 0; i < args_list->children; i++) {
586+
payload->args[i] = zend_compile_pre_erasure_typename(args_list->child[i]);
587+
}
588+
ZEND_TYPE_SET_PTR(result, payload);
589+
ZEND_TYPE_FULL_MASK(result) |= _ZEND_TYPE_NAMED_WITH_ARGS_BIT;
590+
} else if (ast->kind == ZEND_AST_TYPE) {
591+
/* Builtin pseudo-type: same as erased. */
592+
result = (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
593+
} else if (ast->kind == ZEND_AST_ZVAL) {
594+
uint8_t origin;
595+
uint32_t index;
596+
zend_generic_parameter *param = NULL;
597+
if ((ast->attr & ZEND_NAME_NOT_FQ) == ZEND_NAME_NOT_FQ) {
598+
const zval *zv = zend_ast_get_zval(ast);
599+
if (Z_TYPE_P(zv) == IS_STRING) {
600+
param = zend_generic_lookup_full(Z_STR_P(zv), &origin, &index);
601+
}
602+
}
603+
if (param) {
604+
zend_type_parameter_ref *ref = emalloc(sizeof(*ref));
605+
ref->name = zend_string_copy(param->name);
606+
ref->index = index;
607+
ref->origin = origin;
608+
ZEND_TYPE_SET_PTR(result, ref);
609+
ZEND_TYPE_FULL_MASK(result) |= _ZEND_TYPE_TYPE_PARAMETER_BIT;
610+
} else {
611+
zend_string *name = zval_make_interned_string(zend_ast_get_zval(ast));
612+
uint8_t code = zend_lookup_builtin_type_by_name(name);
613+
if (code != 0) {
614+
if (code == IS_ITERABLE) {
615+
zend_type iterable = (zend_type) ZEND_TYPE_INIT_CLASS_MASK(
616+
ZSTR_KNOWN(ZEND_STR_TRAVERSABLE),
617+
(MAY_BE_ARRAY|_ZEND_TYPE_ITERABLE_BIT));
618+
result = iterable;
619+
} else {
620+
result = (zend_type) ZEND_TYPE_INIT_CODE(code, 0, 0);
621+
}
622+
} else {
623+
zend_string_addref(name);
624+
result = (zend_type) ZEND_TYPE_INIT_CLASS(name, 0, 0);
625+
}
626+
}
627+
} else {
628+
ZEND_UNREACHABLE();
629+
}
630+
631+
if (is_marked_nullable) {
632+
ZEND_TYPE_FULL_MASK(result) |= _ZEND_TYPE_NULLABLE_BIT;
633+
}
634+
ast->attr = orig_attr;
635+
return result;
636+
}
637+
638+
/* Ensure op_array->generic_types or ce->generic_types is allocated. */
639+
static zend_generic_type_table *zend_generic_get_or_create_op_array_table(zend_op_array *op_array)
640+
{
641+
if (!op_array->generic_types) {
642+
op_array->generic_types = zend_generic_type_table_alloc();
643+
}
644+
return op_array->generic_types;
645+
}
646+
647+
static zend_generic_type_table *zend_generic_get_or_create_class_table(zend_class_entry *ce)
648+
{
649+
if (!ce->generic_types) {
650+
ce->generic_types = zend_generic_type_table_alloc();
651+
}
652+
return ce->generic_types;
653+
}
654+
512655
static zend_generic_parameter *zend_generic_lookup_name(const zend_ast *ast) /* {{{ */
513656
{
514657
if (!CG(generic_scope) || ast->kind != ZEND_AST_ZVAL) {
@@ -7499,7 +7642,22 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74997642
zend_generic_parameter *param = zend_generic_lookup_name(ast);
75007643
if (param) {
75017644
if (ZEND_TYPE_IS_SET(param->bound)) {
7502-
return param->bound;
7645+
zend_type result = param->bound;
7646+
if (ZEND_TYPE_HAS_NAME(result)) {
7647+
zend_string_addref(ZEND_TYPE_NAME(result));
7648+
} else if (ZEND_TYPE_HAS_LIST(result)) {
7649+
zend_type_list *orig_list = ZEND_TYPE_LIST(result);
7650+
size_t list_size = ZEND_TYPE_LIST_SIZE(orig_list->num_types);
7651+
zend_type_list *copy = emalloc(list_size);
7652+
memcpy(copy, orig_list, list_size);
7653+
for (uint32_t i = 0; i < copy->num_types; i++) {
7654+
if (ZEND_TYPE_HAS_NAME(copy->types[i])) {
7655+
zend_string_addref(ZEND_TYPE_NAME(copy->types[i]));
7656+
}
7657+
}
7658+
ZEND_TYPE_SET_PTR(result, copy);
7659+
}
7660+
return result;
75037661
}
75047662

75057663
return (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ANY);
@@ -8163,6 +8321,11 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
81638321
arg_infos->type = zend_compile_typename(return_type_ast);
81648322
ZEND_TYPE_FULL_MASK(arg_infos->type) |= _ZEND_ARG_INFO_FLAGS(
81658323
(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0, /* is_variadic */ 0, /* is_tentative */ 0);
8324+
if (zend_type_ast_has_generic_content(return_type_ast)) {
8325+
zend_type pre = zend_compile_pre_erasure_typename(return_type_ast);
8326+
zend_generic_type_table_set_return(
8327+
zend_generic_get_or_create_op_array_table(op_array), pre);
8328+
}
81668329
} else {
81678330
arg_infos->type = (zend_type) ZEND_TYPE_INIT_CODE(fallback_return_type, 0, 0);
81688331
}
@@ -8281,6 +8444,14 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
82818444

82828445
op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
82838446
arg_info->type = zend_compile_typename_ex(type_ast, force_nullable, &forced_allow_nullable);
8447+
if (zend_type_ast_has_generic_content(type_ast)) {
8448+
zend_type pre = zend_compile_pre_erasure_typename(type_ast);
8449+
if (force_nullable || forced_allow_nullable) {
8450+
ZEND_TYPE_FULL_MASK(pre) |= _ZEND_TYPE_NULLABLE_BIT;
8451+
}
8452+
zend_generic_type_table_set_parameter(
8453+
zend_generic_get_or_create_op_array_table(op_array), i, pre);
8454+
}
82848455
if (forced_allow_nullable) {
82858456
zend_string *func_name = get_function_or_method_name((zend_function *) op_array);
82868457
zend_error(E_DEPRECATED,
@@ -8379,6 +8550,11 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
83798550
zend_type type = ZEND_TYPE_INIT_NONE(0);
83808551
if (type_ast) {
83818552
type = zend_compile_typename(type_ast);
8553+
if (zend_type_ast_has_generic_content(type_ast)) {
8554+
zend_type pre = zend_compile_pre_erasure_typename(type_ast);
8555+
zend_generic_type_table_set_property(
8556+
zend_generic_get_or_create_class_table(scope), name, pre);
8557+
}
83828558
}
83838559

83848560
/* Don't give the property an explicit default value. For typed properties this means
@@ -8988,7 +9164,7 @@ static zend_op_array *zend_compile_func_decl_ex(
89889164
* See GENERICS.md §6.9. */
89899165
if (generic_params_ast) {
89909166
op_array->generic_parameters = zend_compile_generic_type_parameter_list(generic_params_ast);
8991-
zend_generic_scope_push(op_array->generic_parameters);
9167+
zend_generic_scope_push(op_array->generic_parameters, /* origin */ 1);
89929168
generic_scope_pushed = true;
89939169
}
89949170

@@ -9341,6 +9517,11 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f
93419517

93429518
if (type_ast) {
93439519
type = zend_compile_typename(type_ast);
9520+
if (zend_type_ast_has_generic_content(type_ast)) {
9521+
zend_type pre = zend_compile_pre_erasure_typename(type_ast);
9522+
zend_generic_type_table_set_property(
9523+
zend_generic_get_or_create_class_table(ce), name, pre);
9524+
}
93449525

93459526
if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NEVER|MAY_BE_CALLABLE)) {
93469527
zend_string *str = zend_type_to_string(type);
@@ -9772,7 +9953,7 @@ static void zend_compile_class_decl(znode *result, const zend_ast *ast, bool top
97729953
if (decl->child[5]) {
97739954
ZEND_ASSERT(!(decl->flags & ZEND_ACC_ANON_CLASS));
97749955
ce->generic_parameters = zend_compile_generic_type_parameter_list(decl->child[5]);
9775-
zend_generic_scope_push(ce->generic_parameters);
9956+
zend_generic_scope_push(ce->generic_parameters, /* origin */ 0);
97769957
}
97779958

97789959
if (extends_ast) {

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ typedef struct _zend_generic_type_table {
149149
/* Compile-time linked stack of in-scope generic type parameters. */
150150
typedef struct _zend_generic_scope_entry {
151151
zend_generic_parameter_list *params;
152+
uint8_t origin; /* 0 = class/interface/trait, 1 = function/method/closure/arrow-fn */
152153
struct _zend_generic_scope_entry *outer;
153154
} zend_generic_scope_entry;
154155

Zend/zend_opcode.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,15 @@ ZEND_API void zend_type_release(zend_type type, bool persistent) {
121121
if (!ZEND_TYPE_USES_ARENA(type)) {
122122
pefree(ZEND_TYPE_LIST(type), persistent);
123123
}
124+
} else if (ZEND_TYPE_HAS_NAMED_WITH_ARGS(type)) {
125+
zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(type);
126+
if (named->name) {
127+
zend_string_release(named->name);
128+
}
129+
for (uint32_t i = 0; i < named->count; i++) {
130+
zend_type_release(named->args[i], persistent);
131+
}
132+
pefree(named, persistent);
124133
} else if (ZEND_TYPE_HAS_TYPE_PARAMETER(type)) {
125134
zend_type_parameter_ref *ref = ZEND_TYPE_TYPE_PARAMETER(type);
126135
if (ref->name) {

Zend/zend_types.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,29 @@ typedef struct _zend_type_arguments {
140140
zend_type arguments[1];
141141
} zend_type_arguments;
142142

143+
/* Pre-erasure named type with type arguments. */
144+
typedef struct _zend_type_named_with_args {
145+
zend_string *name; /* class name */
146+
uint32_t name_attr; /* ZEND_NAME_NOT_FQ / ZEND_NAME_FQ / ZEND_NAME_RELATIVE */
147+
uint32_t count; /* number of type arguments */
148+
zend_type args[1]; /* flexible array of pre-erasure type arguments */
149+
} zend_type_named_with_args;
150+
151+
#define ZEND_TYPE_NAMED_WITH_ARGS_SIZE(count) \
152+
(sizeof(zend_type_named_with_args) + ((count) - 1) * sizeof(zend_type))
153+
143154
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26
144-
#define _ZEND_TYPE_MASK ((1u << 26) - 1)
155+
#define _ZEND_TYPE_MASK (((1u << 26) - 1) | _ZEND_TYPE_NAMED_WITH_ARGS_BIT)
156+
/* Pre-erasure named type with type arguments. Side-table only. */
157+
#define _ZEND_TYPE_NAMED_WITH_ARGS_BIT (1u << 31)
145158
/* Generic type-parameter reference. */
146159
#define _ZEND_TYPE_TYPE_PARAMETER_BIT (1u << 25)
147160
/* Only one of these bits may be set. */
148161
#define _ZEND_TYPE_NAME_BIT (1u << 24)
149162
// Used to signify that type.ptr is not a `zend_string*` but a `const char*`,
150163
#define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23)
151164
#define _ZEND_TYPE_LIST_BIT (1u << 22)
152-
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_TYPE_PARAMETER_BIT)
165+
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_TYPE_PARAMETER_BIT|_ZEND_TYPE_NAMED_WITH_ARGS_BIT)
153166
/* For BC behaviour with iterable type */
154167
#define _ZEND_TYPE_ITERABLE_BIT (1u << 21)
155168
/* Whether the type list is arena allocated */
@@ -186,6 +199,12 @@ typedef struct _zend_type_arguments {
186199
#define ZEND_TYPE_TYPE_PARAMETER(t) \
187200
((zend_type_parameter_ref *) (t).ptr)
188201

202+
#define ZEND_TYPE_HAS_NAMED_WITH_ARGS(t) \
203+
((((t).type_mask) & _ZEND_TYPE_NAMED_WITH_ARGS_BIT) != 0)
204+
205+
#define ZEND_TYPE_NAMED_WITH_ARGS(t) \
206+
((zend_type_named_with_args *) (t).ptr)
207+
189208
#define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \
190209
((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0)
191210

ext/opcache/zend_file_cache.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,19 @@ static void zend_file_cache_serialize_type(
482482
return;
483483
}
484484

485+
if (ZEND_TYPE_HAS_NAMED_WITH_ARGS(*type)) {
486+
zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(*type);
487+
SERIALIZE_PTR(named);
488+
ZEND_TYPE_SET_PTR(*type, named);
489+
UNSERIALIZE_PTR(named);
490+
SERIALIZE_STR(named->name);
491+
for (uint32_t i = 0; i < named->count; i++) {
492+
zend_file_cache_serialize_type(&named->args[i], script, info, buf);
493+
}
494+
495+
return;
496+
}
497+
485498
if (ZEND_TYPE_HAS_LIST(*type)) {
486499
zend_type_list *list = ZEND_TYPE_LIST(*type);
487500
SERIALIZE_PTR(list);
@@ -1493,6 +1506,18 @@ static void zend_file_cache_unserialize_type(
14931506
return;
14941507
}
14951508

1509+
if (ZEND_TYPE_HAS_NAMED_WITH_ARGS(*type)) {
1510+
zend_type_named_with_args *named = ZEND_TYPE_NAMED_WITH_ARGS(*type);
1511+
UNSERIALIZE_PTR(named);
1512+
ZEND_TYPE_SET_PTR(*type, named);
1513+
UNSERIALIZE_STR(named->name);
1514+
for (uint32_t i = 0; i < named->count; i++) {
1515+
zend_file_cache_unserialize_type(&named->args[i], scope, script, buf);
1516+
}
1517+
1518+
return;
1519+
}
1520+
14961521
if (ZEND_TYPE_HAS_LIST(*type)) {
14971522
zend_type_list *list = ZEND_TYPE_LIST(*type);
14981523
UNSERIALIZE_PTR(list);

0 commit comments

Comments
 (0)