Skip to content

Commit a374230

Browse files
committed
Add support for internal enums
This adds support for internal enums with the same basic approach as userland enums. Enum values are stored as CONSTANT_AST and objects created during constant updating at runtime. This means that we need to use mutable_data for internal enums. This just adds basic support and APIs, it does not include the stubs integration from #7212. Closes GH-7302.
1 parent ff8e04a commit a374230

File tree

11 files changed

+250
-12
lines changed

11 files changed

+250
-12
lines changed

Zend/tests/enum/internal_enums.phpt

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
Internal enums
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
var_dump($bar = ZendTestUnitEnum::Bar);
9+
var_dump($bar === ZendTestUnitEnum::Bar);
10+
var_dump($bar instanceof UnitEnum);
11+
12+
var_dump($foo = zend_get_unit_enum());
13+
var_dump($foo === ZendTestUnitEnum::Foo);
14+
15+
var_dump(ZendTestUnitEnum::cases());
16+
echo "\n";
17+
18+
var_dump($foo = ZendTestStringEnum::Foo);
19+
var_dump($foo instanceof BackedEnum);
20+
var_dump(ZendTestStringEnum::Foo->value);
21+
var_dump($bar = ZendTestStringEnum::from("Test2"));
22+
var_dump($bar === ZendTestStringEnum::Bar);
23+
var_dump(ZendTestStringEnum::tryFrom("Test3"));
24+
var_dump(ZendTestStringEnum::cases());
25+
26+
var_dump($s = serialize($foo));
27+
var_dump(unserialize($s));
28+
var_dump(unserialize($s) === $foo);
29+
30+
?>
31+
--EXPECT--
32+
enum(ZendTestUnitEnum::Bar)
33+
bool(true)
34+
bool(true)
35+
enum(ZendTestUnitEnum::Foo)
36+
bool(true)
37+
array(2) {
38+
[0]=>
39+
enum(ZendTestUnitEnum::Foo)
40+
[1]=>
41+
enum(ZendTestUnitEnum::Bar)
42+
}
43+
44+
enum(ZendTestStringEnum::Foo)
45+
bool(true)
46+
string(5) "Test1"
47+
enum(ZendTestStringEnum::Bar)
48+
bool(true)
49+
NULL
50+
array(2) {
51+
[0]=>
52+
enum(ZendTestStringEnum::Foo)
53+
[1]=>
54+
enum(ZendTestStringEnum::Bar)
55+
}
56+
string(30) "E:22:"ZendTestStringEnum:Foo";"
57+
enum(ZendTestStringEnum::Foo)
58+
bool(true)

Zend/zend_API.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,6 @@ static zend_class_mutable_data *zend_allocate_mutable_data(zend_class_entry *cla
12981298
{
12991299
zend_class_mutable_data *mutable_data;
13001300

1301-
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
13021301
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);
13031302
ZEND_ASSERT(ZEND_MAP_PTR_GET_IMM(class_type->mutable_data) == NULL);
13041303

@@ -1331,7 +1330,6 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_
13311330
_zend_hash_append_ptr(constants_table, key, c);
13321331
} ZEND_HASH_FOREACH_END();
13331332

1334-
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE);
13351333
ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL);
13361334

13371335
mutable_data = ZEND_MAP_PTR_GET_IMM(class_type->mutable_data);
@@ -4365,6 +4363,9 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
43654363
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
43664364
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
43674365
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
4366+
if (ce->type == ZEND_INTERNAL_CLASS && !ZEND_MAP_PTR(ce->mutable_data)) {
4367+
ZEND_MAP_PTR_NEW(ce->mutable_data);
4368+
}
43684369
}
43694370

43704371
if (!zend_hash_add_ptr(&ce->constants_table, name, c)) {

Zend/zend_API.h

+2-4
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
416416
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);
417417

418418
static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) {
419-
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS)
420-
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
419+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) {
421420
zend_class_mutable_data *mutable_data =
422421
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
423422
if (mutable_data && mutable_data->constants_table) {
@@ -431,8 +430,7 @@ static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry
431430
}
432431

433432
static zend_always_inline zval *zend_class_default_properties_table(zend_class_entry *ce) {
434-
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES)
435-
&& (ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
433+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES) && ZEND_MAP_PTR(ce->mutable_data)) {
436434
zend_class_mutable_data *mutable_data =
437435
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
438436
return mutable_data->default_properties_table;

Zend/zend_ast.c

-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ static inline void *zend_ast_realloc(void *old, size_t old_size, size_t new_size
3838
return new;
3939
}
4040

41-
static inline size_t zend_ast_size(uint32_t children) {
42-
return sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
43-
}
44-
4541
static inline size_t zend_ast_list_size(uint32_t children) {
4642
return sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
4743
}

Zend/zend_ast.h

+4
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast);
307307
typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context);
308308
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context);
309309

310+
static zend_always_inline size_t zend_ast_size(uint32_t children) {
311+
return sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
312+
}
313+
310314
static zend_always_inline bool zend_ast_is_special(zend_ast *ast) {
311315
return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1;
312316
}

Zend/zend_enum.c

+137
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,140 @@ void zend_enum_register_props(zend_class_entry *ce)
371371
zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC, NULL, value_type);
372372
}
373373
}
374+
375+
static const zend_function_entry unit_enum_methods[] = {
376+
ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
377+
ZEND_FE_END
378+
};
379+
380+
static const zend_function_entry backed_enum_methods[] = {
381+
ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
382+
ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
383+
ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
384+
ZEND_FE_END
385+
};
386+
387+
ZEND_API zend_class_entry *zend_register_internal_enum(
388+
const char *name, zend_uchar type, zend_function_entry *functions)
389+
{
390+
ZEND_ASSERT(type == IS_UNDEF || type == IS_LONG || type == IS_STRING);
391+
392+
zend_class_entry tmp_ce;
393+
INIT_CLASS_ENTRY_EX(tmp_ce, name, strlen(name), functions);
394+
395+
zend_class_entry *ce = zend_register_internal_class(&tmp_ce);
396+
ce->ce_flags |= ZEND_ACC_ENUM;
397+
ce->enum_backing_type = type;
398+
if (type != IS_UNDEF) {
399+
ce->backed_enum_table = pemalloc(sizeof(HashTable), 1);
400+
zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1);
401+
}
402+
403+
zend_enum_register_props(ce);
404+
if (type == IS_UNDEF) {
405+
zend_register_functions(
406+
ce, unit_enum_methods, &ce->function_table, EG(current_module)->type);
407+
zend_class_implements(ce, 1, zend_ce_unit_enum);
408+
} else {
409+
zend_register_functions(
410+
ce, backed_enum_methods, &ce->function_table, EG(current_module)->type);
411+
zend_class_implements(ce, 1, zend_ce_backed_enum);
412+
}
413+
414+
return ce;
415+
}
416+
417+
static zend_ast_ref *create_enum_case_ast(
418+
zend_string *class_name, zend_string *case_name, zval *value) {
419+
// TODO: Use custom node type for enum cases?
420+
size_t num_children = value ? 3 : 2;
421+
size_t size = sizeof(zend_ast_ref) + zend_ast_size(num_children)
422+
+ num_children * sizeof(zend_ast_zval);
423+
char *p = pemalloc(size, 1);
424+
zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
425+
GC_SET_REFCOUNT(ref, 1);
426+
GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;
427+
428+
zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
429+
ast->kind = ZEND_AST_CONST_ENUM_INIT;
430+
ast->attr = 0;
431+
ast->lineno = 0;
432+
433+
ast->child[0] = (zend_ast *) p; p += sizeof(zend_ast_zval);
434+
ast->child[0]->kind = ZEND_AST_ZVAL;
435+
ast->child[0]->attr = 0;
436+
ZEND_ASSERT(ZSTR_IS_INTERNED(class_name));
437+
ZVAL_STR(zend_ast_get_zval(ast->child[0]), class_name);
438+
439+
ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
440+
ast->child[1]->kind = ZEND_AST_ZVAL;
441+
ast->child[1]->attr = 0;
442+
ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
443+
ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
444+
445+
if (value) {
446+
ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
447+
ast->child[2]->kind = ZEND_AST_ZVAL;
448+
ast->child[2]->attr = 0;
449+
ZEND_ASSERT(!Z_REFCOUNTED_P(value));
450+
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
451+
} else {
452+
ast->child[2] = NULL;
453+
}
454+
455+
return ref;
456+
}
457+
458+
ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
459+
{
460+
if (value) {
461+
ZEND_ASSERT(ce->enum_backing_type == Z_TYPE_P(value));
462+
if (Z_TYPE_P(value) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(value))) {
463+
zval_make_interned_string(value);
464+
}
465+
466+
zval case_name_zv;
467+
ZVAL_STR(&case_name_zv, case_name);
468+
if (Z_TYPE_P(value) == IS_LONG) {
469+
zend_hash_index_add_new(ce->backed_enum_table, Z_LVAL_P(value), &case_name_zv);
470+
} else {
471+
zend_hash_add_new(ce->backed_enum_table, Z_STR_P(value), &case_name_zv);
472+
}
473+
} else {
474+
ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
475+
}
476+
477+
zval ast_zv;
478+
Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
479+
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
480+
zend_class_constant *c = zend_declare_class_constant_ex(
481+
ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
482+
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
483+
}
484+
485+
ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value)
486+
{
487+
zend_string *name_str = zend_string_init_interned(name, strlen(name), 1);
488+
zend_enum_add_case(ce, name_str, value);
489+
zend_string_release(name_str);
490+
}
491+
492+
ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) {
493+
zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name);
494+
ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE);
495+
496+
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
497+
if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) {
498+
ZEND_UNREACHABLE();
499+
}
500+
}
501+
ZEND_ASSERT(Z_TYPE(c->value) == IS_OBJECT);
502+
return Z_OBJ(c->value);
503+
}
504+
505+
ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name) {
506+
zend_string *name_str = zend_string_init(name, strlen(name), 0);
507+
zend_object *result = zend_enum_get_case(ce, name_str);
508+
zend_string_release(name_str);
509+
return result;
510+
}

Zend/zend_enum.h

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ void zend_verify_enum(zend_class_entry *ce);
3434
void zend_enum_register_funcs(zend_class_entry *ce);
3535
void zend_enum_register_props(zend_class_entry *ce);
3636

37+
ZEND_API zend_class_entry *zend_register_internal_enum(
38+
const char *name, zend_uchar type, zend_function_entry *functions);
39+
ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value);
40+
ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value);
41+
ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name);
42+
ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name);
43+
3744
static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj)
3845
{
3946
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);

Zend/zend_opcode.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ ZEND_API void destroy_zend_class(zval *zv)
397397
}
398398
break;
399399
case ZEND_INTERNAL_CLASS:
400+
if (ce->backed_enum_table) {
401+
zend_hash_release(ce->backed_enum_table);
402+
}
400403
if (ce->default_properties_table) {
401404
zval *p = ce->default_properties_table;
402405
zval *end = p + ce->default_properties_count;
@@ -442,7 +445,14 @@ ZEND_API void destroy_zend_class(zval *zv)
442445

443446
ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) {
444447
if (c->ce == ce) {
445-
zval_internal_ptr_dtor(&c->value);
448+
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
449+
/* We marked this as IMMUTABLE, but do need to free it when the
450+
* class is destroyed. */
451+
ZEND_ASSERT(Z_ASTVAL(c->value)->kind == ZEND_AST_CONST_ENUM_INIT);
452+
free(Z_AST(c->value));
453+
} else {
454+
zval_internal_ptr_dtor(&c->value);
455+
}
446456
if (c->doc_comment) {
447457
zend_string_release_ex(c->doc_comment, 1);
448458
}

ext/zend_test/test.c

+21
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "observer.h"
2727
#include "fiber.h"
2828
#include "zend_attributes.h"
29+
#include "zend_enum.h"
2930
#include "Zend/Optimizer/zend_optimizer.h"
3031

3132
ZEND_DECLARE_MODULE_GLOBALS(zend_test)
@@ -38,6 +39,8 @@ static zend_class_entry *zend_test_attribute;
3839
static zend_class_entry *zend_test_ns_foo_class;
3940
static zend_class_entry *zend_test_ns2_foo_class;
4041
static zend_class_entry *zend_test_ns2_ns_foo_class;
42+
static zend_class_entry *zend_test_unit_enum;
43+
static zend_class_entry *zend_test_string_enum;
4144
static zend_object_handlers zend_test_class_handlers;
4245

4346
static ZEND_FUNCTION(zend_test_func)
@@ -227,6 +230,13 @@ static ZEND_FUNCTION(zend_iterable)
227230
ZEND_PARSE_PARAMETERS_END();
228231
}
229232

233+
static ZEND_FUNCTION(zend_get_unit_enum)
234+
{
235+
ZEND_PARSE_PARAMETERS_NONE();
236+
237+
RETURN_OBJ_COPY(zend_enum_get_case_cstr(zend_test_unit_enum, "Foo"));
238+
}
239+
230240
static ZEND_FUNCTION(namespaced_func)
231241
{
232242
ZEND_PARSE_PARAMETERS_NONE();
@@ -384,6 +394,17 @@ PHP_MINIT_FUNCTION(zend_test)
384394
zend_test_ns2_foo_class = register_class_ZendTestNS2_Foo();
385395
zend_test_ns2_ns_foo_class = register_class_ZendTestNS2_ZendSubNS_Foo();
386396

397+
zend_test_unit_enum = zend_register_internal_enum("ZendTestUnitEnum", IS_UNDEF, NULL);
398+
zend_enum_add_case_cstr(zend_test_unit_enum, "Foo", NULL);
399+
zend_enum_add_case_cstr(zend_test_unit_enum, "Bar", NULL);
400+
401+
zval val;
402+
zend_test_string_enum = zend_register_internal_enum("ZendTestStringEnum", IS_STRING, NULL);
403+
ZVAL_PSTRINGL(&val, "Test1", sizeof("Test1")-1);
404+
zend_enum_add_case_cstr(zend_test_string_enum, "Foo", &val);
405+
ZVAL_PSTRINGL(&val, "Test2", sizeof("Test2")-1);
406+
zend_enum_add_case_cstr(zend_test_string_enum, "Bar", &val);
407+
387408
// Loading via dl() not supported with the observer API
388409
if (type != MODULE_TEMPORARY) {
389410
REGISTER_INI_ENTRIES();

ext/zend_test/test.stub.php

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ function zend_string_or_stdclass_or_null($param): stdClass|string|null {}
7474

7575
function zend_iterable(iterable $arg1, ?iterable $arg2 = null): void {}
7676

77+
function zend_get_unit_enum(): ZendTestUnitEnum {}
7778
}
7879

7980
namespace ZendTestNS {

ext/zend_test/test_arginfo.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 2a1f8ff8205507259ba19bd379a07b390bc525cd */
2+
* Stub hash: 93bb8b9120e510e8c3afc29dc0a5d47cb6b5f10e */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
55
ZEND_END_ARG_INFO()
@@ -51,6 +51,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable, 0, 1, IS_VOID, 0)
5151
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null")
5252
ZEND_END_ARG_INFO()
5353

54+
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_zend_get_unit_enum, 0, 0, ZendTestUnitEnum, 0)
55+
ZEND_END_ARG_INFO()
56+
5457
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
5558
ZEND_END_ARG_INFO()
5659

@@ -91,6 +94,7 @@ static ZEND_FUNCTION(zend_string_or_object_or_null);
9194
static ZEND_FUNCTION(zend_string_or_stdclass);
9295
static ZEND_FUNCTION(zend_string_or_stdclass_or_null);
9396
static ZEND_FUNCTION(zend_iterable);
97+
static ZEND_FUNCTION(zend_get_unit_enum);
9498
static ZEND_FUNCTION(namespaced_func);
9599
static ZEND_METHOD(_ZendTestClass, is_object);
96100
static ZEND_METHOD(_ZendTestClass, __toString);
@@ -117,6 +121,7 @@ static const zend_function_entry ext_functions[] = {
117121
ZEND_FE(zend_string_or_stdclass, arginfo_zend_string_or_stdclass)
118122
ZEND_FE(zend_string_or_stdclass_or_null, arginfo_zend_string_or_stdclass_or_null)
119123
ZEND_FE(zend_iterable, arginfo_zend_iterable)
124+
ZEND_FE(zend_get_unit_enum, arginfo_zend_get_unit_enum)
120125
ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
121126
ZEND_FE_END
122127
};

0 commit comments

Comments
 (0)