Skip to content

Commit 4f4c602

Browse files
committed
Add support for internal enums
1 parent 36d2dd0 commit 4f4c602

File tree

12 files changed

+233
-23
lines changed

12 files changed

+233
-23
lines changed

Zend/tests/enum/internal_enums.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
11+
var_dump($foo = zend_get_unit_enum());
12+
var_dump($foo === ZendTestUnitEnum::Foo);
13+
14+
var_dump(ZendTestUnitEnum::cases());
15+
16+
var_dump(ZendTestStringEnum::Foo);
17+
var_dump(ZendTestStringEnum::Foo->value);
18+
var_dump(ZendTestStringEnum::from("Test2"));
19+
var_dump(ZendTestStringEnum::cases());
20+
21+
?>
22+
--EXPECT--
23+
enum(ZendTestUnitEnum::Bar)
24+
bool(true)
25+
enum(ZendTestUnitEnum::Foo)
26+
bool(true)
27+
array(2) {
28+
[0]=>
29+
enum(ZendTestUnitEnum::Foo)
30+
[1]=>
31+
enum(ZendTestUnitEnum::Bar)
32+
}
33+
enum(ZendTestStringEnum::Foo)
34+
string(5) "Test1"
35+
enum(ZendTestStringEnum::Bar)
36+
array(2) {
37+
[0]=>
38+
enum(ZendTestStringEnum::Foo)
39+
[1]=>
40+
enum(ZendTestStringEnum::Bar)
41+
}

Zend/zend_API.c

Lines changed: 4 additions & 3 deletions
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);
@@ -4024,7 +4022,7 @@ ZEND_API const char *zend_get_module_version(const char *module_name) /* {{{ */
40244022
}
40254023
/* }}} */
40264024

4027-
static inline zend_string *zval_make_interned_string(zval *zv) /* {{{ */
4025+
zend_string *zval_make_interned_string(zval *zv)
40284026
{
40294027
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
40304028
Z_STR_P(zv) = zend_new_interned_string(Z_STR_P(zv));
@@ -4375,6 +4373,9 @@ ZEND_API zend_class_constant *zend_declare_class_constant_ex(zend_class_entry *c
43754373
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
43764374
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
43774375
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
4376+
if (ce->type == ZEND_INTERNAL_CLASS && !ZEND_MAP_PTR(ce->mutable_data)) {
4377+
ZEND_MAP_PTR_NEW(ce->mutable_data);
4378+
}
43784379
}
43794380

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

Zend/zend_API.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ ZEND_API bool zend_make_callable(zval *callable, zend_string **callable_name);
392392
ZEND_API const char *zend_get_module_version(const char *module_name);
393393
ZEND_API int zend_get_module_started(const char *module_name);
394394

395+
zend_string *zval_make_interned_string(zval *zv);
395396
ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type);
396397

397398
ZEND_API void zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment);
@@ -416,8 +417,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
416417
ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);
417418

418419
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)) {
420+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) {
421421
zend_class_mutable_data *mutable_data =
422422
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
423423
if (mutable_data && mutable_data->constants_table) {
@@ -431,8 +431,7 @@ static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry
431431
}
432432

433433
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)) {
434+
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES) && ZEND_MAP_PTR(ce->mutable_data)) {
436435
zend_class_mutable_data *mutable_data =
437436
(zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
438437
return mutable_data->default_properties_table;

Zend/zend_ast.c

Lines changed: 0 additions & 4 deletions
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

Lines changed: 4 additions & 0 deletions
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_compile.c

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -501,16 +501,6 @@ static int lookup_cv(zend_string *name) /* {{{ */{
501501
}
502502
/* }}} */
503503

504-
static inline zend_string *zval_make_interned_string(zval *zv) /* {{{ */
505-
{
506-
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
507-
Z_STR_P(zv) = zend_new_interned_string(Z_STR_P(zv));
508-
if (ZSTR_IS_INTERNED(Z_STR_P(zv))) {
509-
Z_TYPE_FLAGS_P(zv) = 0;
510-
}
511-
return Z_STR_P(zv);
512-
}
513-
514504
/* Common part of zend_add_literal and zend_append_individual_literal */
515505
static inline void zend_insert_literal(zend_op_array *op_array, zval *zv, int literal_position) /* {{{ */
516506
{

Zend/zend_enum.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,138 @@ 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 size = sizeof(zend_ast_ref) + zend_ast_size(3) + 3 * sizeof(zend_ast_zval);
421+
char *p = malloc(size);
422+
zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
423+
GC_SET_REFCOUNT(ref, 1);
424+
GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;
425+
426+
zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
427+
ast->kind = ZEND_AST_CONST_ENUM_INIT;
428+
ast->attr = 0;
429+
ast->lineno = 0;
430+
431+
ast->child[0] = (zend_ast *) p; p += sizeof(zend_ast_zval);
432+
ast->child[0]->kind = ZEND_AST_ZVAL;
433+
ast->child[0]->attr = 0;
434+
ZEND_ASSERT(ZSTR_IS_INTERNED(class_name));
435+
ZVAL_STR(zend_ast_get_zval(ast->child[0]), class_name);
436+
437+
ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
438+
ast->child[1]->kind = ZEND_AST_ZVAL;
439+
ast->child[1]->attr = 0;
440+
ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
441+
ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
442+
443+
if (value) {
444+
ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
445+
ast->child[2]->kind = ZEND_AST_ZVAL;
446+
ast->child[2]->attr = 0;
447+
ZEND_ASSERT(!Z_REFCOUNTED_P(value));
448+
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
449+
} else {
450+
ast->child[2] = NULL;
451+
}
452+
453+
return ref;
454+
}
455+
456+
ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
457+
{
458+
if (value) {
459+
ZEND_ASSERT(ce->enum_backing_type == Z_TYPE_P(value));
460+
if (Z_TYPE_P(value) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(value))) {
461+
zval_make_interned_string(value);
462+
}
463+
464+
zval case_name_zv;
465+
ZVAL_STR(&case_name_zv, case_name);
466+
if (Z_TYPE_P(value) == IS_LONG) {
467+
zend_hash_index_add_new(ce->backed_enum_table, Z_LVAL_P(value), &case_name_zv);
468+
} else {
469+
zend_hash_add_new(ce->backed_enum_table, Z_STR_P(value), &case_name_zv);
470+
}
471+
} else {
472+
ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
473+
}
474+
475+
zval ast_zv;
476+
Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
477+
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
478+
zend_class_constant *c = zend_declare_class_constant_ex(
479+
ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
480+
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
481+
}
482+
483+
ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value)
484+
{
485+
zend_string *name_str = zend_string_init_interned(name, strlen(name), 1);
486+
zend_enum_add_case(ce, name_str, value);
487+
zend_string_release(name_str);
488+
}
489+
490+
ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) {
491+
zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name);
492+
ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE);
493+
494+
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
495+
if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) {
496+
ZEND_UNREACHABLE();
497+
}
498+
}
499+
ZEND_ASSERT(Z_TYPE(c->value) == IS_OBJECT);
500+
return Z_OBJ(c->value);
501+
}
502+
503+
ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name) {
504+
zend_string *name_str = zend_string_init(name, strlen(name), 0);
505+
zend_object *result = zend_enum_get_case(ce, name_str);
506+
zend_string_release(name_str);
507+
return result;
508+
}

Zend/zend_enum.h

Lines changed: 7 additions & 0 deletions
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

Lines changed: 11 additions & 1 deletion
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

Lines changed: 21 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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 {

0 commit comments

Comments
 (0)